Open felpsio opened 5 years ago
"For what I'm building I need this feature" => Did you manage to have this feature?
https://firebase.google.com/docs/firestore/reference/rest/v1/projects.databases.documents/beginTransaction https://firebase.google.com/docs/firestore/reference/rest/v1/projects.databases.documents/commit
I see that on the Firebase Rest API document there are 2 methods above, which might help you somehow.
If you did integrate those APIs, please create a PR
Thanks @piavgh, actually seems that we need both methods to make the transaction work. The first one to create and the second to make it happens.
I'm working without transaction for now. While I have few data it's ok. But as the database gets larger it can become a problem
Duplicate of #65
This is not a duplicate, a batch write is not the same as transaction.
Thanks, I must have missed the related text in batched write documentation:
The documents.batchWrite method does not apply the write operations atomically and can apply them out of order. Method does not allow more than one write per document. Each write succeeds or fails independently.
Transactions are useful for atomic operations. In my case I need to update user creadits for the add-on.
I've implemented this feature for my project. As this library's language is typesctipt, my code is not compatible. If someone wants to install transactions, here's how:
Firestore.gs
this.transformDocument_ = FirestoreWrite.prototype.transformDocument_;
//
// original class code...
//
/**
* https://firebase.google.com/docs/firestore/reference/rest/v1beta1/Write#FieldTransform
*
* @typedef {Object} FieldTransform
* @property {String} fieldPath - path to field, use dots for nested fields: "parent.kid"
*
* // Union field transform_type can be only one of the following:
* @property {Number} [increment]
* @property {Number} [maximum]
* @property {Number} [minimum]
* @property {Array} [appendMissingElements] - for arrays
* @property {Array} [removeAllFromArray] - for arrays
*/
/**
* Transform document using transactions.
* https://firebase.google.com/docs/firestore/manage-data/transactions
*
* @param {String} path - path to the document in format: "collection/documentId"
* @param {Array<FieldTransform>} fieldTransforms
* @param {Request} request
*
*/
transformDocument(path, fieldTransforms) {
const baseUrl = this.baseUrl.slice(0, -1) + ':';
const request = new Request(baseUrl, this.authToken);
return this.transformDocument_(request, path, this.basePath, fieldTransforms);
}
I've decided to add the code to "Write" class instead of creating a new class:
Write.gs
/**
* Transform document using transactions.
* https://firebase.google.com/docs/firestore/manage-data/transactions
*
* @param {Request} request
* @param {String} path
* * @param {Array<FieldTransform>} fieldTransforms
* @param {String} basePath
*
*/
transformDocument_(request, path, basePath, fieldTransforms) {
// API documents
// https://firebase.google.com/docs/firestore/reference/rest/v1beta1/projects.databases.documents/beginTransaction
// https://firebase.google.com/docs/firestore/reference/rest/v1beta1/projects.databases.documents/commit
const paypoadBeginTransaction = {
"options": {
"readWrite": {}
}
}
const transactionData = request.post('beginTransaction', paypoadBeginTransaction);
const transactionId = transactionData.transaction;
const write = {
"currentDocument": {
"exists": true // the target document must exist
},
"transform": {
"document": basePath + path,
fieldTransforms
},
}
const payloadCommit = {
"writes": [write],
"transaction": transactionId
}
const result = request.post('commit', payloadCommit);
return result;
}
Usage:
function test_transformDocument() {
/** @type FieldTransform */
var fieldTransform = {
fieldPath: 'credits.gpt5',
increment: {integer_value: -5}
}
var result = transformDocument_('test/max', [fieldTransform]);
console.log(JSON.stringify(result));
}
/**
* @param {String} path - path to the document in format: "collection/documentId"
* @param {Array<FieldTransform>} fieldTransforms
*/
function transformDocument_(path, fieldTransforms) {
/** @type Firestore */
var app = getFirestoreApp_('v1beta1');
return app.transformDocument(path, fieldTransforms)
}
/**
* @param {String} [apiVersion] - v1
*/
function getFirestoreApp_(apiVersion) {
var email = options.email; // PUT YOUR SERVICE ACCOUNT EMAIL HERE
var projectId = options.projectId; // PUT YOUR PROJECT ID HERE
var key = getFirestoreKey_(); // PUT YOUR KEY HERE
var app = getFirestore(email, key, projectId, apiVersion);
return app;
}
As result, "credits" for my user were refuced by 5, it was 500, and not it is 495. Here's what I see in logs:
{"writeResults":
[{"updateTime":"2023-09-21T06:45:35.408298Z",
"transformResults":[{"integerValue":"495"}]}],
"commitTime":"2023-09-21T06:45:35.408298Z"}
With this code I'm sure if 2 operations will try to change user credits at the same time, no collision will happen with my data.
fieldTransform
object. Read docs here: https://firebase.google.com/docs/firestore/reference/rest/v1beta1/Write#FieldTransformfieldPath
option for map field is delimited with dots. If you have a nested map, like in my case: "credits" has "gpt5" field inside, the fieldPath is "credits.gpt5"increment
value is a bit tricky, see docs here: https://cloud.google.com/firestore/docs/reference/rpc/google.firestore.v1beta1#valuev1beta1
"writes": [write]
. I use one read-write operation at a time to transform my field. But as you see, the API lets you to perform multiple operations. This is very poverful, but I think the library should stay simple, and I did not add this option.I hope my solution will be helpful, and I hope one day a modified version of this code will be added to original library. Cheers to creator and maintaimers!
I didn't find this feature on the project Readme, but I think it would be great to have it in the library as well. For what I'm building I need this feature