Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with
or
.
Download ZIP
Browse files

flesh out more of the API.

  • Loading branch information...
commit 5f473885400c657d6159e7d93703a9f9b39dfca9 1 parent 4b216e3
@robey authored
View
37 src/fauna/fauna_client.coffee
@@ -93,6 +93,7 @@ class FaunaClient
@clientKeys = plumb _ClientKeys
@eventSets = plumb _EventSets
@classes = plumb _Classes
+ @instances = plumb _Instances
@users = plumb _Users
@_testing = plumb _Testing
@@ -180,6 +181,13 @@ class FaunaClient
login: requireClientOrPublisher asObject (email, password) ->
@rest("post", "tokens", email: email, password: password)
+ # convenience function: set the user token, and try to use it to fetch the
+ # user's own user object, to trick fauna into giving us an auth error if
+ # the token is invalid.
+ loginWithToken: (userId, token) ->
+ @setUserToken(token)
+ @users.getById(userId)
+
class _PublisherKeys
get: requireOwner asEventArray -> @rest("get", "keys/publisher")
create: requireOwner asObject -> @rest("post", "keys/publisher")
@@ -200,6 +208,15 @@ class FaunaClient
create: requireOwner asObject -> @rest("post", "keys/client")
getById: requireOwner asObject assertNamespace("keys/client") (id) -> @rest("get", "#{id}")
deleteById: requireOwner assertNamespace("keys/client") (id) -> @rest("delete", "#{id}")
+ deleteAll: ->
+ @clientKeys.get().then (keys) =>
+ Q.all(keys.map((key) => @clientKeys.deleteById(key._fauna.id)))
+ getOrCreate: ->
+ @clientKeys.get().then (keys) =>
+ if keys.length > 0 then keys[0] else @clientKeys.create()
+ .then (key) =>
+ @setClientKey(key.key)
+ key
class _EventSets
create: requirePublisher asObject (className, setName, data) -> @rest("put", "#{className}/sets/#{setName}/config", data)
@@ -209,6 +226,8 @@ class FaunaClient
if params.before? then list.push "before=#{params.before}"
if params.after? then list.push "after=#{params.after}"
@rest("get", "#{setName}?#{list.join("&")}")
+ addById: requireTokenOrPublisher asObject (setName, id) -> @rest("post", setName, resource: id)
+ deleteById: requireTokenOrPublisher asObject (setName, id) -> @rest("delete", setName, resource: id)
class _Classes
get: requirePublisher asEventArray -> @rest("get", "classes")
@@ -216,6 +235,21 @@ class FaunaClient
create: requirePublisher asObject assertNamespace("classes") (name, data) -> @rest("put", "#{name}/config", data)
deleteByName: requirePublisher assertNamespace("classes") (name) -> @rest("delete", "#{name}/config")
getStats: requirePublisher asObject (name) -> @rest("get", "#{name}/stats")
+ getInstances: requireTokenOrPublisher assertNamespace("classes") (name, params) -> @eventSets.get("#{name}", params)
+
+ class _Instances
+ create: requireTokenOrPublisher asObject (obj) ->
+ data = @schema.deflate(obj)
+ if obj.unique_id? then data.unique_id = obj.unique_id
+ @rest("post", @schema.classNameFor(obj), data)
+ getByUniqueId: requireTokenOrPublisher asObject assertNamespace("classes") (name, uniqueId) ->
+ @rest("get", "#{name}/unique_id/#{escape(uniqueId)}")
+ update: requireTokenOrPublisher asObject (obj) ->
+ data = @schema.deflate(obj)
+ if obj.unique_id? then data.unique_id = obj.unique_id
+ @rest("put", obj._fauna.id, data)
+ getById: requireTokenOrPublisher asObject assertNamespace("classes") (id) -> @rest("get", "#{id}")
+ deleteById: requireTokenOrPublisher assertNamespace("classes") (id) -> @rest("delete", "#{id}")
class _Users
create: requireClientOrPublisher asObject (user) ->
@@ -223,7 +257,7 @@ class FaunaClient
for key in [ "email", "unique_id", "password" ] then if user[key]? then data[key] = user[key]
@rest("post", "users", data)
getByEmail: requirePublisher asObject (email) -> @rest("get", "users/email/#{escape(email)}")
- getByUniqueId: requirePublisher asObject (uniqueId) -> @rest("get", "users/unique_id/#{uniqueId}")
+ getByUniqueId: requirePublisher asObject (uniqueId) -> @rest("get", "users/unique_id/#{escape(uniqueId)}")
update: requireTokenOrPublisher asObject (user) ->
data = @schema.deflate(user)
for key in [ "email", "unique_id", "password" ] then if user[key]? then data[key] = user[key]
@@ -308,4 +342,3 @@ topoSort = (graph) ->
visit(Object.keys(work)[0])
rv
-
View
36 src/fauna/schema.coffee
@@ -24,15 +24,25 @@ class Class
@eventSet = (name, data={}) ->
metadataForClass(@).eventSets[name] = data
- @getInstances = (fauna, params) ->
- fauna.listInstancesOfClass(metadataForClass(@).getClassName(), params)
-
@native = ->
metadataForClass(@).native = true
@data = (data) ->
metadataForClass(@).data = data
+ @getFaunaClass = -> metadataForClass(@).getClassName()
+
+ @getInstances = (fauna, params) ->
+ fauna.classes.getInstances(@getFaunaClass(), params)
+
+ # create or update this object.
+ persist: (client) ->
+ if @_fauna?
+ if not client? then client = @_fauna.fauna
+ client.instances.update(@)
+ else
+ if not client? then throw new Error("Fauna persist() must receive a FaunaClient to create an object")
+ client.instances.create(@)
exports.Class = Class
@@ -59,26 +69,32 @@ metadataForClass = (c) ->
c.__fauna__
-# some functions for dealing with event sets
-class EventSetWrapper
+# representation of an eventset on the server. (API sugar)
+class ProxyEventSet
# this constructor is only used for fauna built-in sets.
- constructor: (@instance, @setName) ->
+ constructor: (@instance, @setName, nested=false) ->
+ # make symlinks to the 'creates' and 'updates' event sets.
+ if not nested
+ @creates = new ProxyEventSet(@instance, "#{@setName}/creates", true)
+ @updates = new ProxyEventSet(@instance, "#{@setName}/updates", true)
name: -> @setName
+ fauna: -> @instance._fauna.fauna
+
add: (obj) ->
if typeof obj != "string" then obj = obj._fauna.id
- @instance._fauna.fauna.addToEventSet(@name(), obj)
+ @fauna().eventSets.addById(@name(), obj)
remove: (obj) ->
if typeof obj != "string" then obj = obj._fauna.id
- @instance._fauna.fauna.deleteFromEventSet(@name(), obj)
+ @fauna().eventSets.deleteById(@name(), obj)
page: (params) ->
- @instance._fauna.fauna.getEventSet(@name(), params)
+ @fauna().eventSets.get(@name(), params)
-class CustomEventSet extends EventSetWrapper
+class CustomEventSet extends ProxyEventSet
constructor: (@instance, @fieldName) ->
name: -> "#{@instance._fauna?.id}/sets/#{@fieldName}"
View
10 test/test_classes.coffee
@@ -71,6 +71,16 @@ describe "FaunaClient.classes", ->
resp.instances.should.eql(1)
resp["sets/config"].should.eql(0)
+ it "getInstances", futureTest ->
+ f = new fauna.FaunaClient()
+ f.setPublisherKey("qqq")
+ r = -> f.classes.getInstances("classes/magical_creatures")
+ withSuccessfulRequest(JSON.parse(data3), r).then ([ resp, requests ]) ->
+ requests.length.should.eql(1)
+ requests[0].method.should.eql("GET")
+ requests[0].url.should.match(/\/classes\/magical_creatures\?/)
+ resp.instances.should.eql(1)
+ resp["sets/config"].should.eql(0)
data1 = """
{
View
57 test/test_client_keys.coffee
@@ -51,6 +51,48 @@ describe "FaunaClient.clientKeys", ->
requests[0].method.should.eql("DELETE")
requests[0].url.should.match(/keys\/client\/30159234443247617/)
+ it "deleteAll", futureTest ->
+ f = new fauna.FaunaClient()
+ f.setOwnerAuth("u", "p")
+ r = -> f.clientKeys.deleteAll()
+ handler = (options) ->
+ if options.method == "GET"
+ [ null, 200, data1 ]
+ else
+ [ null, 204, null ]
+ fauna.Rest.withRequestHandler(handler, r).then ([ resp, requests ]) ->
+ requests.length.should.eql(3)
+ requests[0].method.should.eql("GET")
+ requests[1 ... 3].map (request) ->
+ request.method.should.eql("DELETE")
+ requests[1 ... 3].map((r) -> r.url.match(/keys\/client\/(\d+)/)[1]).sort().should.eql [
+ "30159234443247617"
+ "30159234499870721"
+ ]
+
+ it "getOrCreate", futureTest ->
+ f = new fauna.FaunaClient()
+ f.setOwnerAuth("u", "p")
+ r = -> f.clientKeys.getOrCreate()
+ handler = (options) ->
+ if options.method == "GET"
+ [ null, 200, data3 ]
+ else
+ [ null, 200, data2 ]
+ fauna.Rest.withRequestHandler(handler, r).then ([ resp, requests ]) ->
+ requests.length.should.eql(2)
+ requests[0].method.should.eql("GET")
+ requests[1].method.should.eql("POST")
+ resp.key.should.eql "AQEAayWp97AAAQBrJamcsAABR0HPBQQdosjqwCGI7q1cMg"
+ .then ->
+ handler = (options) -> [ null, 200, data1 ]
+ fauna.Rest.withRequestHandler(handler, r).then ([ resp, requests ]) ->
+ requests.length.should.eql(1)
+ requests[0].method.should.eql("GET")
+ resp.key.should.eql "AQEAayWp-xAAAQBrJamcsAABodTOVpMJU4EvMU6paEgrHA"
+ .then ->
+ f.clientKey.should.eql "AQEAayWp-xAAAQBrJamcsAABodTOVpMJU4EvMU6paEgrHA"
+
data1 = """
{
@@ -106,3 +148,18 @@ data2 = """
}
}
"""
+
+data3 = """
+{
+ "resource" : {
+ "ref" : "keys/client",
+ "class" : "sets",
+ "after" : 9223372036854775,
+ "creates" : 0,
+ "updates" : 0,
+ "deletes" : 0,
+ "events" : [ ]
+ },
+ "references": { }
+}
+"""
View
110 test/test_event_sets.coffee
@@ -35,6 +35,34 @@ describe "FaunaClient.eventSets", ->
users.length.should.eql(1)
users[0].unique_id.should.eql("taran186")
+ it "addById", futureTest ->
+ f = new fauna.FaunaClient()
+ f.setPublisherKey("qqq")
+ r = -> f.eventSets.addById("users/30159234491482113/sets/spellbook", "classes/magical_creatures/30159236086366209")
+ withSuccessfulRequest(JSON.parse(data3), r).then ([ resp, requests ]) ->
+ requests.length.should.eql(1)
+ requests[0].method.should.eql "POST"
+ requests[0].url.should.match(/\/users\/30159234491482113\/sets\/spellbook/)
+ requests[0].body.should.eql("{\"resource\":\"classes/magical_creatures/30159236086366209\"}")
+ resp._fauna.id.should.eql("users/30159234491482113/sets/spellbook")
+ resp.events.length.should.eql 1
+ resp.events[0].action.should.eql "create"
+ resp.events[0].resource._fauna.id.should.eql "classes/magical_creatures/30159236086366209"
+
+ it "deleteById", futureTest ->
+ f = new fauna.FaunaClient()
+ f.setPublisherKey("qqq")
+ r = -> f.eventSets.deleteById("users/30159234491482113/sets/spellbook", "classes/magical_creatures/30159236086366209")
+ withSuccessfulRequest(JSON.parse(data4), r).then ([ resp, requests ]) ->
+ requests.length.should.eql(1)
+ requests[0].method.should.eql "DELETE"
+ requests[0].url.should.match(/\/users\/30159234491482113\/sets\/spellbook/)
+ requests[0].body.should.eql("{\"resource\":\"classes/magical_creatures/30159236086366209\"}")
+ resp._fauna.id.should.eql("users/30159234491482113/sets/spellbook")
+ resp.events.length.should.eql 1
+ resp.events[0].action.should.eql "delete"
+ resp.events[0].resource._fauna.id.should.eql "classes/magical_creatures/30159236086366209"
+
data1 = """
{
@@ -79,3 +107,85 @@ data2 = """
}
}
"""
+
+data3 = """
+{
+ "resource" : {
+ "ref" : "users/30159234491482113/sets/spellbook",
+ "class" : "sets",
+ "after" : 1365020939504000,
+ "before" : 1365020939504000,
+ "creates" : 3,
+ "updates" : 2,
+ "deletes" : 0,
+ "events" : [
+ {
+ "ts" : 1365020939504000,
+ "action" : "create",
+ "resource" : "classes/magical_creatures/30159236086366209",
+ "set" : "users/30159234491482113/sets/spellbook"
+ }
+ ]
+ },
+ "references" : {
+ "users/30159234491482113" : {
+ "ref" : "users/30159234491482113",
+ "class" : "users",
+ "ts" : 1365020937346000,
+ "unique_id" : "taran186",
+ "references" : { },
+ "data" : { },
+ "deleted" : false
+ },
+ "classes/magical_creatures/30159236086366209" : {
+ "ref" : "classes/magical_creatures/30159236086366209",
+ "class" : "classes/magical_creatures",
+ "ts" : 1365020938864000,
+ "references" : { "keeper" : "users/30159234491482113" },
+ "data" : { "name" : "Henwen" },
+ "deleted" : false
+ }
+ }
+}
+"""
+
+data4 = """
+{
+ "resource" : {
+ "ref" : "users/30159234491482113/sets/spellbook",
+ "class" : "sets",
+ "after" : 1365020945421000,
+ "before" : 1365020945421000,
+ "creates" : 4,
+ "updates" : 4,
+ "deletes" : 0,
+ "events" : [
+ {
+ "ts" : 1365020945421000,
+ "action" : "delete",
+ "resource" : "classes/magical_creatures/30159236086366209",
+ "set" : "users/30159234491482113/sets/spellbook"
+ }
+ ]
+ },
+ "references" : {
+ "users/30159234491482113" : {
+ "ref" : "users/30159234491482113",
+ "class" : "users",
+ "ts" : 1365020940845000,
+ "unique_id" : "taran186",
+ "references" : { },
+ "data" : { "pockets" : 2 },
+ "deleted" : false
+ },
+ "classes/magical_creatures/30159236086366209" : {
+ "ref" : "classes/magical_creatures/30159236086366209",
+ "class" : "classes/magical_creatures",
+ "ts" : 1365020944044000,
+ "references" : { "keeper" : "users/30159234491482113" },
+ "data" : { "used" : true },
+ "deleted" : false
+ }
+ }
+}
+"""
View
83 test/test_fauna_client.coffee
@@ -178,6 +178,18 @@ describe "FaunaClient", ->
f._testing.requireTokenOrPublisher("test").then ->
f.auth.should.eql("winner:test%40example.com")
+ it "can make sense of a series of events", ->
+ f = new fauna.FaunaClient()
+ class Chat extends fauna.Class
+ @field "name"
+ f.addPrototypes Chat
+ es = f.unpack(JSON.parse(data_chats))
+ es.events.length.should.eql(9)
+ chats = es.toArray()
+ chats.length.should.eql(1)
+ chats[0].name.should.eql("fauna")
+
+
# test data from the fauna documentation for GET keys/publisher
data1 = """
{
@@ -319,3 +331,74 @@ data4 = """
}
}
"""
+
+data_chats = """
+{
+ "resource" : {
+ "ref" : "classes/chats",
+ "class" : "sets",
+ "after" : 1367136107017000,
+ "creates" : 5,
+ "updates" : 0,
+ "deletes" : 4,
+ "events" : [ {
+ "ts" : 1367134488059000,
+ "action" : "delete",
+ "resource" : "classes/chats/32375408531865600",
+ "set" : "classes/chats"
+ }, {
+ "ts" : 1367134488043000,
+ "action" : "delete",
+ "resource" : "classes/chats/32375422279745536",
+ "set" : "classes/chats"
+ }, {
+ "ts" : 1367134458712000,
+ "action" : "create",
+ "resource" : "classes/chats/32375422279745536",
+ "set" : "classes/chats"
+ }, {
+ "ts" : 1367134445601000,
+ "action" : "create",
+ "resource" : "classes/chats/32375408531865600",
+ "set" : "classes/chats"
+ }, {
+ "ts" : 1367134392516000,
+ "action" : "delete",
+ "resource" : "classes/chats/32364846075871232",
+ "set" : "classes/chats"
+ }, {
+ "ts" : 1367124372458000,
+ "action" : "create",
+ "resource" : "classes/chats/32364846075871232",
+ "set" : "classes/chats"
+ }, {
+ "ts" : 1367117241511000,
+ "action" : "delete",
+ "resource" : "classes/chats/30102379817861120",
+ "set" : "classes/chats"
+ }, {
+ "ts" : 1364966745697000,
+ "action" : "create",
+ "resource" : "classes/chats/30102410437328896",
+ "set" : "classes/chats"
+ }, {
+ "ts" : 1364966716496000,
+ "action" : "create",
+ "resource" : "classes/chats/30102379817861120",
+ "set" : "classes/chats"
+ } ]
+ },
+ "references" : {
+ "classes/chats/30102410437328896" : {
+ "ref" : "classes/chats/30102410437328896",
+ "class" : "classes/chats",
+ "ts" : 1364966745697000,
+ "references" : { },
+ "data" : {
+ "name" : "fauna"
+ },
+ "deleted" : false
+ }
+ }
+}
+"""
View
119 test/test_instances.coffee
@@ -0,0 +1,119 @@
+should = require 'should'
+Q = require 'q'
+util = require 'util'
+
+fauna = require("../lib/fauna")
+test_util = require("./test_util")
+withSuccessfulRequest = test_util.withSuccessfulRequest
+futureTest = test_util.futureTest
+
+describe "FaunaClient.instances", ->
+ it "create", futureTest ->
+ f = new fauna.FaunaClient()
+ class Cat extends fauna.Class
+ @field "name"
+ f.addPrototypes Cat
+ f.setPublisherKey("qqq")
+ r = -> f.instances.create(new Cat(name: "Lola"))
+ withSuccessfulRequest(JSON.parse(data1), r).then ([ resp, requests ]) ->
+ requests.length.should.eql(1)
+ requests[0].method.should.eql("POST")
+ requests[0].url.should.match(/\/classes\/cats/)
+ resp.name.should.eql "Lola"
+
+ it "getByUniqueId", futureTest ->
+ f = new fauna.FaunaClient()
+ f.setPublisherKey("qqq")
+ r = -> f.instances.getByUniqueId("classes/magical_creatures", "Henwen, the Pig")
+ withSuccessfulRequest(JSON.parse(data2), r).then ([ resp, requests ]) ->
+ requests.length.should.eql(1)
+ requests[0].method.should.eql("GET")
+ requests[0].url.should.match(/classes\/magical_creatures\/unique_id\/Henwen%2C%20the%20Pig/)
+ resp._fauna.id.should.eql("classes/magical_creatures/30159241380626433")
+ resp.unique_id.should.eql("Henwen, the Pig")
+
+ it "update", futureTest ->
+ f = new fauna.FaunaClient()
+ class Cat extends fauna.Class
+ @field "name"
+ f.addPrototypes Cat
+ f.setPublisherKey("qqq")
+ lola = new Cat("Lola")
+ f.schema.setFaunaData(lola, "cats", ref: "classes/cats/99", ts: 1000, deleted: false)
+ r = -> f.instances.update(lola)
+ withSuccessfulRequest(JSON.parse(data1), r).then ([ resp, requests ]) ->
+ requests.length.should.eql(1)
+ requests[0].method.should.eql("PUT")
+ requests[0].url.should.match(/\/classes\/cats\/99/)
+ resp.name.should.eql "Lola"
+
+ it "getById", futureTest ->
+ f = new fauna.FaunaClient()
+ f.setPublisherKey("qqq")
+ r = -> f.instances.getById("classes/magical_creatures/30159241380626433")
+ withSuccessfulRequest(JSON.parse(data2), r).then ([ resp, requests ]) ->
+ requests.length.should.eql(1)
+ requests[0].method.should.eql("GET")
+ requests[0].url.should.match(/classes\/magical_creatures\/30159241380626433/)
+ resp._fauna.id.should.eql("classes/magical_creatures/30159241380626433")
+ resp.unique_id.should.eql("Henwen, the Pig")
+
+ it "deleteById", futureTest ->
+ f = new fauna.FaunaClient()
+ f.setPublisherKey("qqq")
+ r = -> f.instances.deleteById("classes/cats/99")
+ handler = (options) -> [ null, 204, null ]
+ fauna.Rest.withRequestHandler(handler, r).then ([ resp, requests ]) ->
+ requests.length.should.eql(1)
+ requests[0].method.should.eql("DELETE")
+ requests[0].url.should.match(/\/classes\/cats\/99/)
+
+ it "can persist() itself", futureTest ->
+ f = new fauna.FaunaClient()
+ class Cat extends fauna.Class
+ @field "name"
+ f.addPrototypes Cat
+ f.setPublisherKey("qqq")
+ lola = new Cat(name: "Lola")
+ (-> lola.persist()).should.throw(/FaunaClient/)
+ r = -> lola.persist(f)
+ withSuccessfulRequest(JSON.parse(data1), r).then ([ resp, requests ]) ->
+ requests.length.should.eql(1)
+ requests[0].method.should.eql("POST")
+ requests[0].url.should.match(/\/classes\/cats/)
+ resp.name.should.eql "Lola"
+ r = -> resp.persist()
+ withSuccessfulRequest(JSON.parse(data1), r)
+ .then ([ resp, requests ]) ->
+ requests.length.should.eql(1)
+ requests[0].method.should.eql("PUT")
+ requests[0].url.should.match(/\/classes\/cats\/30159236086366209/)
+ resp.name.should.eql "Lola"
+
+data1 = """
+{
+ "resource" : {
+ "ref" : "classes/cats/30159236086366209",
+ "class" : "classes/cats",
+ "ts" : 1365020938864000,
+ "references" : { },
+ "data" : { "name" : "Lola" },
+ "deleted" : false
+ },
+ "references" : { }
+}
+"""
+
+data2 = """
+{
+ "resource" : {
+ "ref" : "classes/magical_creatures/30159241380626433",
+ "class" : "classes/magical_creatures",
+ "ts" : 1365020943908000,
+ "unique_id" : "Henwen, the Pig",
+ "references" : { },
+ "data" : { },
+ "deleted" : false
+ }
+}
+"""
View
24 test/test_schema.coffee
@@ -67,9 +67,10 @@ describe "Schema", ->
rv = null
result = { before: 900, after: 910, items: [ new Cat(name: "Oprah") ] }
fakeFauna =
- listInstancesOfClass: (name, params) ->
- rv = [ name, params ]
- result
+ classes:
+ getInstances: (name, params) ->
+ rv = [ name, params ]
+ result
Cat.getInstances(fakeFauna, count: 5).should.eql(result)
rv.should.eql([ "classes/cats", { count: 5 }])
@@ -127,8 +128,8 @@ describe "Schema.eventSet", ->
it "can add", ->
rv = null
fakeFauna =
- addToEventSet: (name, ref) ->
- rv = [ name, ref ]
+ eventSets:
+ addById: (name, ref) -> rv = [ name, ref ]
s = new fauna.Schema(fakeFauna)
s.addPrototypes Chat, Message
@@ -145,9 +146,10 @@ describe "Schema.eventSet", ->
rv = null
result = { before: 900, after: 910, items: [ new Message(text: "goodbye!") ] }
fakeFauna =
- getEventSet: (name, params) ->
- rv = [ name, params ]
- result
+ eventSets:
+ get: (name, params) ->
+ rv = [ name, params ]
+ result
s = new fauna.Schema(fakeFauna)
s.addPrototypes Chat, Message
@@ -161,8 +163,9 @@ describe "Schema.eventSet", ->
it "can remove", ->
rv = null
fakeFauna =
- deleteFromEventSet: (name, ref) ->
- rv = [ name, ref ]
+ eventSets:
+ deleteById: (name, ref) ->
+ rv = [ name, ref ]
s = new fauna.Schema(fakeFauna)
s.addPrototypes Chat, Message
@@ -174,3 +177,4 @@ describe "Schema.eventSet", ->
chat.messages.remove(m)
rv.should.eql([ "classes/chats/100/sets/messages", "classes/messages/999" ])
+
Please sign in to comment.
Something went wrong with that request. Please try again.