share / sharedb

Realtime database backend based on Operational Transformation (OT)
Other
6.25k stars 453 forks source link

How to access mtime and ctime? #164

Open curran opened 7 years ago

curran commented 7 years ago

ShareDB adds the creation and last modified date to documents under the m (meta) field, as m.ctime and m.mtime (in SubmitRequest.prototype._addSnapshotMeta). However, these fields do not appear on doc objects after fetching or subscribing. Does anyone know of a way to access these values from within a ShareDB client?

curran commented 7 years ago

FWIW, I'm using sharedb-mongo, and I see that those values are stored in the database. A whole document looks something like this:

{
  "_id" : "1569dee41c42424cb0c5800e621020b6",
  "title" : "A new thing",
  "description" : "Testing",
  "_type" : "http://sharejs.org/types/JSONv0",
  "_v" : 3,
  "_m" : {
    "ctime" : 1499677902674,
    "mtime" : 1499678012715
  },
  "_o" : ObjectId("5963453c78cc1e784f6cb1ad")
}
curran commented 6 years ago

Proposal for ShareDB API change: expose mtime and ctime to client-side documents.

User story:

As a developer using ShareDB,
I would like to be able to access the modified and updated times for a document,
so that I can present that information to end users.
domonless commented 6 years ago

@curran Hi, curran, could you tell me how do you add "title" and "description" when create the doc? thx.

curran commented 6 years ago

@domonless I'm using the ShareDB API to create the document and add those fields, like this:

import connection from '../connection'
import { DB_DOCUMENTS_COLLECTION } from '../../constants'
import generateId from '../generateId'

// Creates a new document in the ShareDB backend,
export const createDocument = options => {

  const {
    title,
    description
  } = options

  const id = generateId()
  const doc = connection.get(DB_DOCUMENTS_COLLECTION, id)

  doc.create({

    // Human readable title, String.
    title,

    // Human readable description, String.
    description
  })

  return doc
}

I'm still interested in accessing ctime and mtime. Would a PR be welcome that exposes these?

domonless commented 6 years ago

@curran Thank you, I will try this and I think accessing ctime and mtime will help a lot. I am currently use some side function log then send these meta info, it is not a pleasant experience.

naserzandi commented 6 years ago

Any update on this issue? I'm also trying to access the m.mtime in the client. In my case, I'm creating a SubscribeQuery to a projection to show the list of documents. I'd like to show the modification time in the list. I tried passing options.metadata=true when I created the SubscribeQuery, but it seems even though the mongo driver receives that information from the database, it's being filtered out somewhere and the client does no receive this information.

alecgibson commented 6 years ago

For what it's worth, we use a slightly hacky way of figuring out the creation time from the Mongo ObjectId: https://docs.mongodb.com/manual/reference/method/ObjectId.getTimestamp/

Doesn't help with mtime, though unfortunately.

naserzandi commented 6 years ago

Thanks @alecgibson. Clever hack. Though it's not what I'm looking for, as you mentioned. Since this information is stored in the database by sharedb, and this thread is for 2017, I was hoping this issue is solved by now. So there no way to get the information through the sharedb client itself?

alecgibson commented 6 years ago

Doing some digging around it looks like you may be able to do something hacky by using a projection with $submit: true: https://github.com/share/sharedb/blob/master/test/db.js#L274-L284

So maybe if you did something like this:

backend.addProjection('myCollectionWithMeta', 'myCollection', { $submit: true });

const doc = connection.get('myCollectionWithMeta', 'myDocId');
doc.fetch(() => {
  // doc.m should be populated...?
});

Disclaimer: I have absolutely no idea what $submit: true is actually meant to be used for, so proceed with caution, and maybe do some digging of your own (also I haven't tested that code, so it might even not be right).

alecgibson commented 6 years ago

Aha, yes, further to my above comment, using $submit: true should return metadata, because that's what it's intended to do.

That being said, it's been prefixed with a $, which is usually convention-speak for "this is an internal property and may change in a breaking way at any time," so I'd still proceed with caution if you choose to go this route.

Of course, you could always submit a pull request to add this sort of functionality in a way less hacky way.

alecgibson commented 6 years ago

Although actually on second thought, it looks like Doc completely ignores the m metadata field when ingesting a snapshot: https://github.com/share/sharedb/blob/master/lib/client/doc.js#L158-L204

So actually this probably won't work.

naserzandi commented 6 years ago

Thanks @alecgibson. Your comments were very helpful. Following your comments, I found out that even though that the $submit:true / metadata:true makes the metadata to be fetched from the database, it will be ignored in the getSnapshotData method in agent.js: https://github.com/share/sharedb/blob/10902b72ae0362471fdc5409f323430a65f4d2b0/lib/agent.js#L416-L425 So the client never receives the meta information. And then as you mentioned, the client implementation itself also ignores this information, even if it was sent.

naserzandi commented 6 years ago

So, here is what I changed to send the metadata to the client.

---
 lib/agent.js      | 3 ++-
 lib/client/doc.js | 1 +
 2 files changed, 3 insertions(+), 1 deletion(-)

diff --git a/lib/agent.js b/lib/agent.js
index 3ef5583..46d6662 100644
--- a/lib/agent.js
+++ b/lib/agent.js
@@ -416,7 +416,8 @@ function getMapData(snapshotMap) {
 function getSnapshotData(snapshot) {
   var data = {
     v: snapshot.v,
-    data: snapshot.data
+    data: snapshot.data,
+    m: snapshot.m
   };
   if (types.defaultType !== types.map[snapshot.type]) {
     data.type = snapshot.type;
diff --git a/lib/client/doc.js b/lib/client/doc.js
index 71f4e20..3bc1f80 100644
--- a/lib/client/doc.js
+++ b/lib/client/doc.js
@@ -199,6 +199,7 @@ Doc.prototype.ingestSnapshot = function(snapshot, callback) {
   this.data = (this.type && this.type.deserialize) ?
     this.type.deserialize(snapshot.data) :
     snapshot.data;
+  this.meta = snapshot.m;
   this.emit('load');
   callback && callback();
 };
-- 
2.18.0

Any concerns?

alecgibson commented 6 years ago

It all looks generally fine to me. I suspect people will suggest hiding this functionality behind some sort of option/flag, although what exactly that would look like I'm not entirely sure of myself (there are many ways you could do it - set a flag on a Doc; sending on option on Doc.fetch; send an option when opening a Connection, setting an option across all of ShareDB; etc.).

I'd suggest raising a PR and discussing there.

naserzandi commented 6 years ago

Well, what's missing now is that the update operations received by the client don't have the meta information.

naserzandi commented 6 years ago

It all looks generally fine to me. I suspect people will suggest hiding this functionality behind some sort of option/flag, although what exactly that would look like I'm not entirely sure of myself (there are many ways you could do it - set a flag on a Doc; sending on option on Doc.fetch; send an option when opening a Connection, setting an option across all of ShareDB; etc.).

I'd suggest raising a PR and discussing there.

Yes that make sense. The meta information is only being sent if the $submit:true / metadata:true is being passed, though. otherwise it won't be returned to the client.