01 Tutorials

Languages:

Introduction

In this tutorial, you’ll combine what you’ve learned in the Basics and Social Graph tutorials, to explore FaunaDB’s temporal features.

Temporal queries show you exactly how data has changed over time. So you can ask for instances that have been updated in the last few days, and even scope whole queries to a point in the past, so you can query a snapshot of the database before and after particular transactions were processed.

Getting Started

The combined data from the other tutorials should already be in the my_app database you’ve been using for tutorials. We’ll make a few small changes to it so we are set up to run temporal queries. You can continue using a server key you created in either of the earlier tutorials.

If you haven’t completed the Basics and Social Graph tutorials, you should run them now to create the data you need for this tutorial.

Change Some Instances

The blog posts you created earlier when you ran the Basics tutorial all have the same or very similar timestamps, so we need to introduce some variety to give us some changes to query.

They all have similar timestamps because they were written at the same time. If we add a new one now it will have a different timestamp. This is easy.


curl https://db.fauna.com/ \
    -u kqnPAi3Kj3ZgAAC0Hu51Ng2dtn0JcgP7Fb-Q_uzLGZE: \
    -d '{
          "create": { "class": "posts" },
          "params": {
            "object": {
              "data": {
                "object": { "title": "Theory of time", "tags": [ "philosophy" ] }
              }
            }
          }
        }'

client.query(
  Create(
    Class(Value("posts")),
    Obj(
      "data", Obj(
        "title", Value("Theory of time"),
        "tags", Arr(Value("philosophy"))
      )
    )));

client.query(
  q.Create(
    q.Class("posts"),
    { data: { title: "Theory of time", tags: ["philosophy"] } }));

client.query(
  Create(
    Class("posts"),
    Obj(
      "data" -> Obj("title" -> "Theory of time", "tags" -> Arr("philosophy"))
    )))

$client.query do
  create class_('posts'),
         data: { title: 'Theory of time', tags: ['philosophy'] }
end

client.Query(
  Create(
    Class("posts"),
    Obj(
      "data", Obj("title", "Theory of time", "tags", Arr("philosophy"))
    )));

client.query(
  q.create(
    q.class_expr("posts"),
    {"data": {"title": "Theory of time", "tags": ["philosophy"]}}
  ))

client.Query(
    f.Create(
        f.Class("posts"),
        f.Obj{
            "data": f.Obj{
                "title": "Theory of time",
                "tags": f.Arr{"philosophy"},
            },
        },
    ),
)

client.query(
    Create(
        at: Class("posts"),
        Obj(
            "data" => Obj(
                "title" => "Theory of time",
                "tags" => Arr("philosophy")
            )
        )
    )
)

HTTP/1.1 201 Created
{
  "resource": {
    "ref": { "@ref": "classes/posts/104979509692858368" },
    "class": { "@ref": "classes/posts" },
    "ts": 1436375112199704,
    "data": { "title": "Theory of time", "tags": [ "philosophy" ] }
  }
}

=> {
  "ref": { "@ref": "classes/posts/104979509692858368" },
  "class": { "@ref": "classes/posts" },
  "ts": 1436375112199704,
  "data": { "title": "Theory of time", "tags": [ "philosophy" ] }
}

=> {
  "ref": { "@ref": "classes/posts/104979509692858368" },
  "class": { "@ref": "classes/posts" },
  "ts": 1436375112199704,
  "data": { "title": "Theory of time", "tags": [ "philosophy" ] }
}

=> {
  "ref": { "@ref": "classes/posts/104979509692858368" },
  "class": { "@ref": "classes/posts" },
  "ts": 1436375112199704,
  "data": { "title": "Theory of time", "tags": [ "philosophy" ] }
}

=> {
  "ref": { "@ref": "classes/posts/104979509692858368" },
  "class": { "@ref": "classes/posts" },
  "ts": 1436375112199704,
  "data": { "title": "Theory of time", "tags": [ "philosophy" ] }
}

=> {
  "ref": { "@ref": "classes/posts/104979509692858368" },
  "class": { "@ref": "classes/posts" },
  "ts": 1436375112199704,
  "data": { "title": "Theory of time", "tags": [ "philosophy" ] }
}

=> {
  "ref": { "@ref": "classes/posts/104979509692858368" },
  "class": { "@ref": "classes/posts" },
  "ts": 1436375112199704,
  "data": { "title": "Theory of time", "tags": [ "philosophy" ] }
}

=> {
  "ref": { "@ref": "classes/posts/104979509692858368" },
  "class": { "@ref": "classes/posts" },
  "ts": 1436375112199704,
  "data": { "title": "Theory of time", "tags": [ "philosophy" ] }
}

=> {
  "ref": { "@ref": "classes/posts/104979509692858368" },
  "class": { "@ref": "classes/posts" },
  "ts": 1436375112199704,
  "data": { "title": "Theory of time", "tags": [ "philosophy" ] }
}

We’ll also modify an existing blog post by looking it up in our existing index, posts_by_title, so that you can see instance updates are also visible in the temporal query.


curl https://db.fauna.com/ \
    -u kqnPAi3Kj3ZgAAC0Hu51Ng2dtn0JcgP7Fb-Q_uzLGZE: \
    -d '{
          "update": {
            "select": "ref",
            "from": {
              "get": {
                "match": { "index": "posts_by_title" },
                "terms": "Pondering during a commute"
              }
            }
          },
          "params": {
            "object": {
              "data": { "object": { "title": "Considering my ride" } }
            }
          }
        }'

client.query(
  Update(
    Select(
      Value("ref"),
      Get(
        Match(
          Index(Value("posts_by_title")),
          Value("Pondering during a commute")))),
    Obj("data", Obj("title", Value("Considering my ride")))));

client.query(
  q.Update(
    q.Select(
      "ref",
      q.Get(
        q.Match(
          q.Index("posts_by_title"),
          "Pondering during a commute"))),
    { data: { title: "Considering my ride" } }));

client.query(
  Update(
    Select(
      "ref",
      Get(
        Match(
          Index("posts_by_title"),
          "Pondering during a commute"))),
    Obj("data" -> Obj("title" -> "Considering my ride"))))

$client.query do
  update select('ref',
                get(match(index('posts_by_title'),
                          'Pondering during a commute'))),
         data: { title: 'Considering my ride' }
end

client.Query(
  Update(
    Select(
      "ref",
      Get(
        Match(
          Index("posts_by_title"),
          "Pondering during a commute"))),
    Obj("data", Obj("title", "Considering my ride"))));

client.query(
  q.update(
    q.select(
      "ref",
      q.get(
        q.match(
          q.index("posts_by_title"),
          "Pondering during a commute"
        )
      )
    ),
    {"data": {"title": "Considering my ride"}}
  ))

client.Query(
    f.Update(
        f.Select(
            "ref",
            f.Get(
                f.MatchTerm(
                    f.Index("posts_by_title"),
                    "Pondering during a commute",
                ),
            ),
        ),
        f.Obj{"data": f.Obj{"title": "Considering my ride"}},
    ),
)

client.query(
    Update(
        ref: Select(
            path: "ref",
            from: Get(
                Match(
                    index: Index("posts_by_title"),
                    terms: "Pondering during a commute"
                )
            )
        ),
        to: Obj("data" => Obj("title" => "Considering my ride"))
    )
)

HTTP/1.1 200 OK
{
  "resource": {
    "ref": { "@ref": "classes/posts/104979509693618791" },
    "class": { "@ref": "classes/posts" },
    "ts": 1436375112257866,
    "data": {
      "title": "Considering my ride",
      "tags": [ "philosophy", "travel" ]
    }
  }
}

=> {
  "ref": { "@ref": "classes/posts/104979509693618791" },
  "class": { "@ref": "classes/posts" },
  "ts": 1436375112257866,
  "data": {
    "title": "Considering my ride",
    "tags": [ "philosophy", "travel" ]
  }
}

=> {
  "ref": { "@ref": "classes/posts/104979509693618791" },
  "class": { "@ref": "classes/posts" },
  "ts": 1436375112257866,
  "data": {
    "title": "Considering my ride",
    "tags": [ "philosophy", "travel" ]
  }
}

=> {
  "ref": { "@ref": "classes/posts/104979509693618791" },
  "class": { "@ref": "classes/posts" },
  "ts": 1436375112257866,
  "data": {
    "title": "Considering my ride",
    "tags": [ "philosophy", "travel" ]
  }
}

=> {
  "ref": { "@ref": "classes/posts/104979509693618791" },
  "class": { "@ref": "classes/posts" },
  "ts": 1436375112257866,
  "data": {
    "title": "Considering my ride",
    "tags": [ "philosophy", "travel" ]
  }
}

=> {
  "ref": { "@ref": "classes/posts/104979509693618791" },
  "class": { "@ref": "classes/posts" },
  "ts": 1436375112257866,
  "data": {
    "title": "Considering my ride",
    "tags": [ "philosophy", "travel" ]
  }
}

=> {
  "ref": { "@ref": "classes/posts/104979509693618791" },
  "class": { "@ref": "classes/posts" },
  "ts": 1436375112257866,
  "data": {
    "title": "Considering my ride",
    "tags": [ "philosophy", "travel" ]
  }
}

=> {
  "ref": { "@ref": "classes/posts/104979509693618791" },
  "class": { "@ref": "classes/posts" },
  "ts": 1436375112257866,
  "data": {
    "title": "Considering my ride",
    "tags": [ "philosophy", "travel" ]
  }
}

=> {
  "ref": { "@ref": "classes/posts/104979509693618791" },
  "class": { "@ref": "classes/posts" },
  "ts": 1436375112257866,
  "data": {
    "title": "Considering my ride",
    "tags": [ "philosophy", "travel" ]
  }
}

Now that you have created some sample data with operations spread over time, you are all set to query it temporally.

Running Temporal Queries

There are two main kinds of temporal queries, snapshot and event queries.

Running a Snapshot Query

In the previous section you changed the title of a post. Now you’ll run a query over the posts_by_tags_with_title index. By default, this returns the current version of everything tagged “philosophy”. If instead you specify a snapshot timestamp with your query, you’ll see the index as it existed at that time.

Find the timestamp returned as part of the first operation in this tutorial (the Create statement in the “Change Some Instances” section), now subtract one from it. Now we can run a query to see what things looked like before we started this tutorial.


curl https://db.fauna.com/ \
    -u kqnPAi3Kj3ZgAAC0Hu51Ng2dtn0JcgP7Fb-Q_uzLGZE: \
    -d '{
          "paginate": {
            "match": { "index": "posts_by_tags_with_title" },
            "terms": "philosophy"
          },
          "ts": 1436375112141542
        }'

client.query(
  Paginate(
      Match(
        Index(Value("posts_by_tags_with_title")),
        Value("philosophy")))
    .ts(Value(1436375112141542)));

client.query(
  q.Paginate(
    q.Match(q.Index("posts_by_tags_with_title"), "philosophy"),
    { ts: 1436375112141542 }));

client.query(
  Paginate(
    Match(Index("posts_by_tags_with_title"), "philosophy"),
    ts = 1436375112141542))

$client.query do
  paginate match(index('posts_by_tags_with_title'), 'philosophy'),
           ts: 1436375112141542
end

client.Query(
  Paginate(
    Match(Index("posts_by_tags_with_title"), "philosophy"),
    ts: 1436375112141542));

client.query(
  q.paginate(
    q.match(q.index("posts_by_tags_with_title"), "philosophy"),
    ts=1436375112141542
  ))

client.Query(
    f.Paginate(
        f.MatchTerm(
            f.Index("posts_by_tags_with_title"),
            "philosophy",
        ),
        f.TS(1436375112141542),
    ),
)

client.query(
    Paginate(
        Match(
            index: Index("posts_by_tags_with_title"),
            terms: "philosophy"
        ),
        ts: 1436375112141542
    )
)

HTTP/1.1 200 OK
{ "resource": { "data": [ "Pondering during a commute" ] } }

=> { "data": [ "Pondering during a commute" ] }

=> { "data": [ "Pondering during a commute" ] }

=> { "data": [ "Pondering during a commute" ] }

=> { "data": [ "Pondering during a commute" ] }

=> { "data": [ "Pondering during a commute" ] }

=> { "data": [ "Pondering during a commute" ] }

=> { "data": [ "Pondering during a commute" ] }

=> { "data": [ "Pondering during a commute" ] }

This leaves out the “Theory of time” blog post you created, and returns the old title, “Pondering during a commute” instead of the updated title “Considering my ride”.

If you remove the timestamp option from the above query, you’ll get a current snapshot, including all the updates you’ve made.


curl https://db.fauna.com/ \
    -u kqnPAi3Kj3ZgAAC0Hu51Ng2dtn0JcgP7Fb-Q_uzLGZE: \
    -d '{
          "paginate": {
            "match": { "index": "posts_by_tags_with_title" },
            "terms": "philosophy"
          }
        }'

client.query(
  Paginate(
      Match(
        Index(Value("posts_by_tags_with_title")),
        Value("philosophy"))));

client.query(
  q.Paginate(
    q.Match(q.Index("posts_by_tags_with_title"), "philosophy")));

client.query(
  Paginate(
    Match(Index("posts_by_tags_with_title"), "philosophy")))

$client.query do
  paginate match(index('posts_by_tags_with_title'), 'philosophy')
end

client.Query(
  Paginate(
    Match(Index("posts_by_tags_with_title"), "philosophy")));

client.query(
  q.paginate(
    q.match(q.index("posts_by_tags_with_title"), "philosophy")
  ))

client.Query(
    f.Paginate(
        f.MatchTerm(
            f.Index("posts_by_tags_with_title"),
            "philosophy",
        ),
    ),
)

client.query(
    Paginate(
        Match(
            index: Index("posts_by_tags_with_title"),
            terms: "philosophy"
        )
    )
)

HTTP/1.1 200 OK
{
  "resource": { "data": [ "Considering my ride", "Theory of time" ] }
}

=> { "data": [ "Considering my ride", "Theory of time" ] }

=> { "data": [ "Considering my ride", "Theory of time" ] }

=> { "data": [ "Considering my ride", "Theory of time" ] }

=> { "data": [ "Considering my ride", "Theory of time" ] }

=> { "data": [ "Considering my ride", "Theory of time" ] }

=> { "data": [ "Considering my ride", "Theory of time" ] }

=> { "data": [ "Considering my ride", "Theory of time" ] }

=> { "data": [ "Considering my ride", "Theory of time" ] }

Running an Events Query

If instead of seeing the state of the database at a recent snapshot, you want a summary of everything that has happened since that snapshot, you can use the events option. This is also great for supporting partially-connected clients like mobile phones, and keeping analytics systems up-to-date. You’ll use it later in this tutorial to create an activity feed, by querying across posts from multiple users. For now you’ll run a query for events on posts tagged “philosophy”.

With the events option you see the changes you made during the course of this tutorial. Note that currently the update from “Pondering during a commute” to “Considering my ride” shows up as two events, but in a future update of the event API it will be consolidated to a single update event.


curl https://db.fauna.com/ \
    -u kqnPAi3Kj3ZgAAC0Hu51Ng2dtn0JcgP7Fb-Q_uzLGZE: \
    -d '{
          "paginate": {
            "match": { "index": "posts_by_tags_with_title" },
            "terms": "philosophy"
          },
          "after": 1436375112141542,
          "events": true
        }'

client.query(
  Paginate(
      Match(
        Index(Value("posts_by_tags_with_title")),
        Value("philosophy")))
    .after(Value(1436375112141542))
    .events(Value(true)));

client.query(
  q.Paginate(
    q.Match(q.Index("posts_by_tags_with_title"), "philosophy"),
    { after: 1436375112141542, events: true }));

client.query(
  Paginate(
    Match(Index("posts_by_tags_with_title"), "philosophy"),
    cursor = After(1436375112141542),
    events = true))

$client.query do
  paginate match(index('posts_by_tags_with_title'), 'philosophy'),
           after: 1436375112141542,
           events: true
end

client.Query(
  Paginate(
    Match(Index("posts_by_tags_with_title"), "philosophy"),
    after: 1436375112141542,
    events: true));

client.query(
  q.paginate(
    q.match(q.index("posts_by_tags_with_title"), "philosophy"),
    after=1436375112141542,
    events=True
  ))

client.Query(
    f.Paginate(
        f.MatchTerm(
            f.Index("posts_by_tags_with_title"),
            "philosophy",
        ),
        f.After(1436375112141542),
        f.Events(true),
    ),
)

client.query(
    Paginate(
        Match(
            index: Index("posts_by_tags_with_title"),
            terms: "philosophy"
        ),
        after: 1436375112141542,
        events: true
    )
)

HTTP/1.1 200 OK
{
  "resource": {
    "before": { "ts": 1436375112141542, "action": "create" },
    "data": [
      {
        "ts": 1436375112199704,
        "action": "create",
        "resource": { "@ref": "classes/posts/104979509692858368" },
        "values": [ "Theory of time" ]
      },
      {
        "ts": 1436375112257866,
        "action": "delete",
        "resource": { "@ref": "classes/posts/104979509693618791" },
        "values": [ "Pondering during a commute" ]
      },
      {
        "ts": 1436375112257866,
        "action": "create",
        "resource": { "@ref": "classes/posts/104979509693618791" },
        "values": [ "Considering my ride" ]
      }
    ]
  }
}

=> {
  "before": { "ts": 1436375112141542, "action": "create" },
  "data": [
    {
      "ts": 1436375112199704,
      "action": "create",
      "resource": { "@ref": "classes/posts/104979509692858368" },
      "values": [ "Theory of time" ]
    },
    {
      "ts": 1436375112257866,
      "action": "delete",
      "resource": { "@ref": "classes/posts/104979509693618791" },
      "values": [ "Pondering during a commute" ]
    },
    {
      "ts": 1436375112257866,
      "action": "create",
      "resource": { "@ref": "classes/posts/104979509693618791" },
      "values": [ "Considering my ride" ]
    }
  ]
}

=> {
  "before": { "ts": 1436375112141542, "action": "create" },
  "data": [
    {
      "ts": 1436375112199704,
      "action": "create",
      "resource": { "@ref": "classes/posts/104979509692858368" },
      "values": [ "Theory of time" ]
    },
    {
      "ts": 1436375112257866,
      "action": "delete",
      "resource": { "@ref": "classes/posts/104979509693618791" },
      "values": [ "Pondering during a commute" ]
    },
    {
      "ts": 1436375112257866,
      "action": "create",
      "resource": { "@ref": "classes/posts/104979509693618791" },
      "values": [ "Considering my ride" ]
    }
  ]
}

=> {
  "before": { "ts": 1436375112141542, "action": "create" },
  "data": [
    {
      "ts": 1436375112199704,
      "action": "create",
      "resource": { "@ref": "classes/posts/104979509692858368" },
      "values": [ "Theory of time" ]
    },
    {
      "ts": 1436375112257866,
      "action": "delete",
      "resource": { "@ref": "classes/posts/104979509693618791" },
      "values": [ "Pondering during a commute" ]
    },
    {
      "ts": 1436375112257866,
      "action": "create",
      "resource": { "@ref": "classes/posts/104979509693618791" },
      "values": [ "Considering my ride" ]
    }
  ]
}

=> {
  "before": { "ts": 1436375112141542, "action": "create" },
  "data": [
    {
      "ts": 1436375112199704,
      "action": "create",
      "resource": { "@ref": "classes/posts/104979509692858368" },
      "values": [ "Theory of time" ]
    },
    {
      "ts": 1436375112257866,
      "action": "delete",
      "resource": { "@ref": "classes/posts/104979509693618791" },
      "values": [ "Pondering during a commute" ]
    },
    {
      "ts": 1436375112257866,
      "action": "create",
      "resource": { "@ref": "classes/posts/104979509693618791" },
      "values": [ "Considering my ride" ]
    }
  ]
}

=> {
  "before": { "ts": 1436375112141542, "action": "create" },
  "data": [
    {
      "ts": 1436375112199704,
      "action": "create",
      "resource": { "@ref": "classes/posts/104979509692858368" },
      "values": [ "Theory of time" ]
    },
    {
      "ts": 1436375112257866,
      "action": "delete",
      "resource": { "@ref": "classes/posts/104979509693618791" },
      "values": [ "Pondering during a commute" ]
    },
    {
      "ts": 1436375112257866,
      "action": "create",
      "resource": { "@ref": "classes/posts/104979509693618791" },
      "values": [ "Considering my ride" ]
    }
  ]
}

=> {
  "before": { "ts": 1436375112141542, "action": "create" },
  "data": [
    {
      "ts": 1436375112199704,
      "action": "create",
      "resource": { "@ref": "classes/posts/104979509692858368" },
      "values": [ "Theory of time" ]
    },
    {
      "ts": 1436375112257866,
      "action": "delete",
      "resource": { "@ref": "classes/posts/104979509693618791" },
      "values": [ "Pondering during a commute" ]
    },
    {
      "ts": 1436375112257866,
      "action": "create",
      "resource": { "@ref": "classes/posts/104979509693618791" },
      "values": [ "Considering my ride" ]
    }
  ]
}

=> {
  "before": { "ts": 1436375112141542, "action": "create" },
  "data": [
    {
      "ts": 1436375112199704,
      "action": "create",
      "resource": { "@ref": "classes/posts/104979509692858368" },
      "values": [ "Theory of time" ]
    },
    {
      "ts": 1436375112257866,
      "action": "delete",
      "resource": { "@ref": "classes/posts/104979509693618791" },
      "values": [ "Pondering during a commute" ]
    },
    {
      "ts": 1436375112257866,
      "action": "create",
      "resource": { "@ref": "classes/posts/104979509693618791" },
      "values": [ "Considering my ride" ]
    }
  ]
}

=> {
  "before": { "ts": 1436375112141542, "action": "create" },
  "data": [
    {
      "ts": 1436375112199704,
      "action": "create",
      "resource": { "@ref": "classes/posts/104979509692858368" },
      "values": [ "Theory of time" ]
    },
    {
      "ts": 1436375112257866,
      "action": "delete",
      "resource": { "@ref": "classes/posts/104979509693618791" },
      "values": [ "Pondering during a commute" ]
    },
    {
      "ts": 1436375112257866,
      "action": "create",
      "resource": { "@ref": "classes/posts/104979509693618791" },
      "values": [ "Considering my ride" ]
    }
  ]
}

The raw events don’t include the user data, so if you’d like to see the updated changes to instances, you can add a Map and a Get to your query to load the indexed records.


curl https://db.fauna.com/ \
    -u kqnPAi3Kj3ZgAAC0Hu51Ng2dtn0JcgP7Fb-Q_uzLGZE: \
    -d '{
          "map": {
            "lambda": "event",
            "expr": {
              "get": { "select": "resource", "from": { "var": "event" } },
              "ts": { "select": "ts", "from": { "var": "event" } }
            }
          },
          "collection": {
            "paginate": {
              "match": { "index": "posts_by_tags_with_title" },
              "terms": "philosophy"
            },
            "after": 1436375112141542,
            "events": true
          }
        }'

client.query(
  Map(
    Paginate(
        Match(
          Index(Value("posts_by_tags_with_title")),
          Value("philosophy")))
      .after(Value(1436375112141542))
      .events(Value(true)),
    Lambda(
      Value("event"),
      Get(
        Select(Value("resource"), Var("event")),
        Select(Value("ts"), Var("event"))))));

client.query(
  q.Map(
    q.Paginate(
      q.Match(q.Index("posts_by_tags_with_title"), "philosophy"),
      { after: 1436375112141542, events: true }),
    function(event) {
      return q.Get(q.Select("resource", event), q.Select("ts", event));
    }));

client.query(
  Map(
    Paginate(
      Match(Index("posts_by_tags_with_title"), "philosophy"),
      cursor = After(1436375112141542),
      events = true),
    Lambda { event =>
      Get(Select("resource", event), Select("ts", event))
    }))

$client.query do
  map paginate(match(index('posts_by_tags_with_title'), 'philosophy'),
           after: 1436375112141542,
           events: true) do |event|
    get(select('resource', event), select('ts', event))
  end
end

client.Query(
  Map(
    Paginate(
      Match(Index("posts_by_tags_with_title"), "philosophy"),
      after: 1436375112141542,
      events: true),
    event => Get(Select("resource", event), Select("ts", event))));

client.query(
  q.map_expr(
    lambda event: q.get(q.select("resource", event), q.select("ts", event)),
    q.paginate(
      q.match(q.index("posts_by_tags_with_title"), "philosophy"),
      after=1436375112141542,
      events=True
    )
  ))

client.Query(
    f.Map(
        f.Paginate(
            f.MatchTerm(
                f.Index("posts_by_tags_with_title"),
                "philosophy",
            ),
            f.After(1436375112141542),
            f.Events(true),
        ),
        f.Lambda(
            "event",
            f.Get(
                f.Select("resource", f.Var("event")),
                f.Select("ts", f.Var("event")),
            ),
        ),
    ),
)

client.query(
    Map(
        collection: Paginate(
            Match(
                index: Index("posts_by_tags_with_title"),
                terms: "philosophy"
            ),
            after: 1436375112141542,
            events: true
        ),
        to: { event in
            Get(
                Select(path: "resource", from: event),
                ts: Select(path: "ts", from: event)
            )
        }
    )
)

HTTP/1.1 200 OK
{
  "resource": {
    "before": { "ts": 1436375112141542, "action": "create" },
    "data": [
      {
        "ref": { "@ref": "classes/posts/104979509692858368" },
        "class": { "@ref": "classes/posts" },
        "ts": 1436375112199704,
        "data": { "title": "Theory of time", "tags": [ "philosophy" ] }
      },
      {
        "ref": { "@ref": "classes/posts/104979509693618791" },
        "class": { "@ref": "classes/posts" },
        "ts": 1436375112257866,
        "data": {
          "title": "Considering my ride",
          "tags": [ "philosophy", "travel" ]
        }
      },
      {
        "ref": { "@ref": "classes/posts/104979509693618791" },
        "class": { "@ref": "classes/posts" },
        "ts": 1436375112257866,
        "data": {
          "title": "Considering my ride",
          "tags": [ "philosophy", "travel" ]
        }
      }
    ]
  }
}

=> {
  "before": { "ts": 1436375112141542, "action": "create" },
  "data": [
    {
      "ref": { "@ref": "classes/posts/104979509692858368" },
      "class": { "@ref": "classes/posts" },
      "ts": 1436375112199704,
      "data": { "title": "Theory of time", "tags": [ "philosophy" ] }
    },
    {
      "ref": { "@ref": "classes/posts/104979509693618791" },
      "class": { "@ref": "classes/posts" },
      "ts": 1436375112257866,
      "data": {
        "title": "Considering my ride",
        "tags": [ "philosophy", "travel" ]
      }
    },
    {
      "ref": { "@ref": "classes/posts/104979509693618791" },
      "class": { "@ref": "classes/posts" },
      "ts": 1436375112257866,
      "data": {
        "title": "Considering my ride",
        "tags": [ "philosophy", "travel" ]
      }
    }
  ]
}

=> {
  "before": { "ts": 1436375112141542, "action": "create" },
  "data": [
    {
      "ref": { "@ref": "classes/posts/104979509692858368" },
      "class": { "@ref": "classes/posts" },
      "ts": 1436375112199704,
      "data": { "title": "Theory of time", "tags": [ "philosophy" ] }
    },
    {
      "ref": { "@ref": "classes/posts/104979509693618791" },
      "class": { "@ref": "classes/posts" },
      "ts": 1436375112257866,
      "data": {
        "title": "Considering my ride",
        "tags": [ "philosophy", "travel" ]
      }
    },
    {
      "ref": { "@ref": "classes/posts/104979509693618791" },
      "class": { "@ref": "classes/posts" },
      "ts": 1436375112257866,
      "data": {
        "title": "Considering my ride",
        "tags": [ "philosophy", "travel" ]
      }
    }
  ]
}

=> {
  "before": { "ts": 1436375112141542, "action": "create" },
  "data": [
    {
      "ref": { "@ref": "classes/posts/104979509692858368" },
      "class": { "@ref": "classes/posts" },
      "ts": 1436375112199704,
      "data": { "title": "Theory of time", "tags": [ "philosophy" ] }
    },
    {
      "ref": { "@ref": "classes/posts/104979509693618791" },
      "class": { "@ref": "classes/posts" },
      "ts": 1436375112257866,
      "data": {
        "title": "Considering my ride",
        "tags": [ "philosophy", "travel" ]
      }
    },
    {
      "ref": { "@ref": "classes/posts/104979509693618791" },
      "class": { "@ref": "classes/posts" },
      "ts": 1436375112257866,
      "data": {
        "title": "Considering my ride",
        "tags": [ "philosophy", "travel" ]
      }
    }
  ]
}

=> {
  "before": { "ts": 1436375112141542, "action": "create" },
  "data": [
    {
      "ref": { "@ref": "classes/posts/104979509692858368" },
      "class": { "@ref": "classes/posts" },
      "ts": 1436375112199704,
      "data": { "title": "Theory of time", "tags": [ "philosophy" ] }
    },
    {
      "ref": { "@ref": "classes/posts/104979509693618791" },
      "class": { "@ref": "classes/posts" },
      "ts": 1436375112257866,
      "data": {
        "title": "Considering my ride",
        "tags": [ "philosophy", "travel" ]
      }
    },
    {
      "ref": { "@ref": "classes/posts/104979509693618791" },
      "class": { "@ref": "classes/posts" },
      "ts": 1436375112257866,
      "data": {
        "title": "Considering my ride",
        "tags": [ "philosophy", "travel" ]
      }
    }
  ]
}

=> {
  "before": { "ts": 1436375112141542, "action": "create" },
  "data": [
    {
      "ref": { "@ref": "classes/posts/104979509692858368" },
      "class": { "@ref": "classes/posts" },
      "ts": 1436375112199704,
      "data": { "title": "Theory of time", "tags": [ "philosophy" ] }
    },
    {
      "ref": { "@ref": "classes/posts/104979509693618791" },
      "class": { "@ref": "classes/posts" },
      "ts": 1436375112257866,
      "data": {
        "title": "Considering my ride",
        "tags": [ "philosophy", "travel" ]
      }
    },
    {
      "ref": { "@ref": "classes/posts/104979509693618791" },
      "class": { "@ref": "classes/posts" },
      "ts": 1436375112257866,
      "data": {
        "title": "Considering my ride",
        "tags": [ "philosophy", "travel" ]
      }
    }
  ]
}

=> {
  "before": { "ts": 1436375112141542, "action": "create" },
  "data": [
    {
      "ref": { "@ref": "classes/posts/104979509692858368" },
      "class": { "@ref": "classes/posts" },
      "ts": 1436375112199704,
      "data": { "title": "Theory of time", "tags": [ "philosophy" ] }
    },
    {
      "ref": { "@ref": "classes/posts/104979509693618791" },
      "class": { "@ref": "classes/posts" },
      "ts": 1436375112257866,
      "data": {
        "title": "Considering my ride",
        "tags": [ "philosophy", "travel" ]
      }
    },
    {
      "ref": { "@ref": "classes/posts/104979509693618791" },
      "class": { "@ref": "classes/posts" },
      "ts": 1436375112257866,
      "data": {
        "title": "Considering my ride",
        "tags": [ "philosophy", "travel" ]
      }
    }
  ]
}

=> {
  "before": { "ts": 1436375112141542, "action": "create" },
  "data": [
    {
      "ref": { "@ref": "classes/posts/104979509692858368" },
      "class": { "@ref": "classes/posts" },
      "ts": 1436375112199704,
      "data": { "title": "Theory of time", "tags": [ "philosophy" ] }
    },
    {
      "ref": { "@ref": "classes/posts/104979509693618791" },
      "class": { "@ref": "classes/posts" },
      "ts": 1436375112257866,
      "data": {
        "title": "Considering my ride",
        "tags": [ "philosophy", "travel" ]
      }
    },
    {
      "ref": { "@ref": "classes/posts/104979509693618791" },
      "class": { "@ref": "classes/posts" },
      "ts": 1436375112257866,
      "data": {
        "title": "Considering my ride",
        "tags": [ "philosophy", "travel" ]
      }
    }
  ]
}

=> {
  "before": { "ts": 1436375112141542, "action": "create" },
  "data": [
    {
      "ref": { "@ref": "classes/posts/104979509692858368" },
      "class": { "@ref": "classes/posts" },
      "ts": 1436375112199704,
      "data": { "title": "Theory of time", "tags": [ "philosophy" ] }
    },
    {
      "ref": { "@ref": "classes/posts/104979509693618791" },
      "class": { "@ref": "classes/posts" },
      "ts": 1436375112257866,
      "data": {
        "title": "Considering my ride",
        "tags": [ "philosophy", "travel" ]
      }
    },
    {
      "ref": { "@ref": "classes/posts/104979509693618791" },
      "class": { "@ref": "classes/posts" },
      "ts": 1436375112257866,
      "data": {
        "title": "Considering my ride",
        "tags": [ "philosophy", "travel" ]
      }
    }
  ]
}

This returns the versions of created and updated instance corresponding to the timestamps they were written. You can use this query to update the user’s UI, or 3rd party systems, and rest assured you won’t miss changes, even if you are disconnected for a while.

Further Reading

This tutorial builds on previous tutorials. If you are going to follow along at the code level, you should start by working through the tutorials below:

The Activity Feed tutorial follows on by showing how to mix social graph joins with temporal queries, to answer the question, “what’s new for this user?”

For more details about history features, see the Timeline tutorial