samuelgozi/firebase-firestore-lite

Setting server timestamp in document

vivekweb2013 opened this issue ยท 14 comments

It seems firestore does not support ordering on meta information such as updateTime, createTime etc.

So I was thinking of adding a timestamp field in the document. But the timestamp value should be as per the server, since Date.now() will return the timestamp according to the clients clock setting (which could be incorrect)

There seems some support for setting the field value to server timestamp with firestore apis, Having this feature into this library would be a great enhancement.

Thanks for the feature request.
If it is indeed possible I will add this feature, possibly today.

Small update: As you said, it is indeed possible to instruct the backend to "fill" a specified field with the date in the database.
I'm working on it, and I'm pretty sure I'll finish the implementation today along with other similar features, but the tests might take a little bit longer.

Small update: As you said, it is indeed possible to instruct the backend to "fill" a specified field with the date in the database.
I'm working on it, and I'm pretty sure I'll finish the implementation today along with other similar features, but the tests might take a little bit longer.

Thanks @samuelgozi

Another update: It seems like this indeed is possible, however, it's not very "eloquent".
It cannot be done within a regular set request. It needs to be made with a different API.

I can push a working solution, but I don't want to rush it. I don't want to end up with an ugly API.
@vivekweb2013 Can you attach a link to documentation about how to use it with the official SDK? I can't seem to find a working solution.

In the meantime, what I'm doing in my production apps is adding a timestamp from within the firebase rules. That way it can't be manipulated from the front end, and I believe it is a better solution than what this feature will look like when it is done.

Right, its not possible with document created with set. Seems like there are some write APIs that offer fieldtransform for transforming the field values.

FieldTransform

ServerValue

In the meantime, what I'm doing in my production apps is adding a timestamp from within the firebase rules.

Rules? you mean cloud functions right?

Right, its not possible with document created with set. Seems like there are some write APIs that offer fieldtransform for transforming the field values.

FieldTransform

ServerValue

In the meantime, what I'm doing in my production apps is adding a timestamp from within the firebase rules.

Thanks, I'm familiar with those. They are allowed within transactions.
But I want to make them indistinguishable from regular writes.
I think I have an idea, I'll see how it goes and update.

Rules? you mean cloud functions right?

No. Firebase security rules. No need for functions.
https://firebase.google.com/docs/firestore/security/get-started

No. Firebase security rules. No need for functions.

Could you please specify the example, I'm not sure if the firebase rules could be used for updating data, I thought they are only for validation purpose

I'm sorry, you are correct. I don't know why I remembered that it was possible to manipulate data within the security rules. I might have confused it with AWS.

Another update:
The feature is ready with tests, I'm just making some last checks before pushing.
However, there is an "issue". I couldn't find a way of adding "server Timestamps" with the official SDK. there is just no way of doing it. All of the Timestamps there are actually generated in the front end, therefore they are not really server timestamps, nor they claim to be.

But as I stated before, it is possible to do so with the REST API, therefore it can be implemented, which I did. But the issue I mentioned is that you can only use it when "updating" a document, or when creating one by specifying a name(which behind the scenes it technically is an update), so that means that this is not possible:

db.reference('users').set({
    createdAt: new Transform('serverTimestamp');
});

It is not possible because Transforms can only be used as part of a transaction Write.
And firestores transactions do not allow creating a document with server-generated names.

Ok, so what does work?
When calling the set or update methods on a reference to a document, the name is already known, which means that technically it is an update operation, which means that it can be made in a transaction.

These are examples that are allowed, and will work:

db.reference('users/username').set({
    createdAt: new Transform('serverTimestamp');
});

// or

db.reference('users/username').set({
    createdAt: new Transform('serverTimestamp');
});

Behind the scenes, this will be converted into a transaction.
That is the only way that it could work, and unfortunately, it only works when with documents with a known name.

I submitted a feature request to the firebase team that if implemented, will allow us to simplify how this works behind the scenes, and will also allow us to use serverTimestamps with "new" documents.

I'll issue a final update once this is implemented. If you have any suggestions/questions, feel free to ask.

EDIT: A small correction, it is possible to use a serverTimestamp with the official SDK, but they use a "hack" and generate the document name on the client... mmm... So now I have a dilemma, generating document names in the front end is a mess, and will add a lot of code just for one feature... I need to think about this.

Just pushed a commit that adds this feature. this is still a beta version, things/syntax might change.
There is one breaking change in this release, but it's not related to this issue directly, you can read about it in the releases.

There is still no documentation for this, so ill explain here how to use this feature.
In order to set a serverTimestamp or any of the other Transform(this is the official name, not my choice) you need to import the Transform class. The way this is done might change, but currently, this is how it's done:

import Transform from 'firebase-firestore-lite/src/Transform.js';

The new Transform represents 6 types that are calculated by the Firebase Servers:

  • serverTimestamp which was renamed from setToServerValue: "REQUEST_TIME"
  • increment
  • max renamed from maximum
  • min renamed from minimum
  • appendToArray renamed from appendMissingElements
  • removeFromArray renamed from removeAllFromArray

This is how you use them:

db.reference('users/id').set({
    lastUpdate: new Transform('serverTimestamp')
})

When using a transform that requires an argument:

db.reference('videos/id').set({
    viewCount: new Transform('increment', 1)
})

That's it for now.
For further documentation of the available transforms look here:
https://firebase.google.com/docs/firestore/reference/rest/v1beta1/Write#ServerValue

Please let me know if there are any suggestions/feedback regarding the implementation/syntax/naming or anything.

Thanks @samuelgozi for implementing the feature.
Setting the server timestamp on the new documents should have been supported by firestore core APIs, Its really an overhead to first create the document and make the update just to have the server date assigned to it. So I'm counting on the firebase team to provide such feature with their ReST apis which is anyways an essential core functionality.

Could you please mention your feature request link here.

Otherwise your overall changes looks good to me, thanks for supporting the different transform types in the feature.

I can't send the link because feature requests are not exposed, but if you'd like to request a feature you can do it here: https://firebase.google.com/support/troubleshooter/report/features
It is not possible to any transform at all on document creation, not only in the REST API. In the official, they use a workaround for that which I think is too hacky so I don't do it, but I might do it in the future.

@samuelgozi Thanks for the simplified version! Excited to test the results!
I see serverTimestamp transform expect value too which is not required.

new Transform('serverTimestamp'); -> Throwing " An argument for 'value' was not provided."

This is because Transform constructor expecting two definite params.

    constructor(name: TransformName, value: number | any[]);

@manwithsteelnerves Thanks, I pushed a quick fix. Let me know if there are any more issues.
make sure to update to 1.0.0-RC1.2.