Last active
August 29, 2015 14:15
-
-
Save notduncansmith/7858d27a2b875ff10b84 to your computer and use it in GitHub Desktop.
In-memory graph datastore with friendship and notification support in <100 lines
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
# Bring in static test data | |
data = require './data' | |
# Spin up graph store | |
Rel = require('./rel')() | |
# Set up the friendships | |
Rel.put [ | |
data.events.aliceBefriendsBob | |
data.events.carolBefriendsBob | |
] | |
# Create some interactions | |
setTimeout (-> Rel.put(data.events.aliceOwnsPost)), 1000 | |
setTimeout (-> Rel.put(data.events.bobLikesPost)), 2000 | |
### | |
Output: | |
Alice isFriendsWith Bob | |
Carol isFriendsWith Bob | |
Alice owns {Alice: "I am happy"} | |
Bob isNotifiedBy <Rel: Alice owns {Alice: "I am happy"}> | |
Bob likes {Alice: "I am happy"} | |
Alice isNotifiedBy <Rel: Bob likes {Alice: "I am happy"}> | |
Carol isNotifiedBy <Rel: Bob likes {Alice: "I am happy"}> | |
### |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
data = | |
post: | |
_id: 0 | |
_type: 'post' | |
text: 'I am happy' | |
Alice: | |
_id: 1 | |
_type: 'user' | |
name: 'Alice' | |
Bob: | |
_id: 2 | |
_type: 'user' | |
name: 'Bob' | |
Carol: | |
_id: 3 | |
_type: 'user' | |
name: 'Carol' | |
events: {} | |
data.events.aliceBefriendsBob = | |
subject: data.Alice | |
predicate: 'isFriendsWith' | |
object: data.Bob | |
data.events.carolBefriendsBob = | |
subject: data.Carol | |
predicate: 'isFriendsWith' | |
object: data.Bob | |
data.events.aliceOwnsPost = | |
subject: data.Alice | |
predicate: 'owns' | |
object: data.post | |
data.events.bobLikesPost = | |
subject: data.Bob | |
predicate: 'likes' | |
object: data.post | |
module.exports = data |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
_ = require 'lodash' | |
$ = require 'highland' | |
uuid = require('node-uuid').v4 | |
EventEmitter = require('events').EventEmitter | |
module.exports = -> | |
Rel = | |
ee: new EventEmitter() | |
data: {} | |
put: (rel) -> | |
if _.isArray(rel) | |
rel.map @put.bind(@) | |
else | |
rel._id = uuid() | |
rel._type = 'rel' | |
@data[rel._id] = rel | |
@ee.emit 'put', rel | |
get: (opts) -> | |
if typeof opts is 'string' | |
_.findWhere _.values(@data), {_id: id} | |
else | |
@find opts | |
find: (opts) -> | |
{subject, predicate, object} = _.extend {}, {subject:'*', predicate:'*', object:'*'}, opts | |
_.values(@data).filter (rel) -> | |
matchesSubject = (subject is '*') or (subject._id is rel.subject._id) or (subject is rel.subject._id) | |
matchesPredicate = (predicate is '*') or (predicate is rel.predicate) # always a string | |
matchesObject = (object is '*') or (object._id is rel.object._id) or (object is rel.object._id) | |
matchesSubject and matchesPredicate and matchesObject | |
on: (eventName) -> | |
$(eventName, @ee) | |
owner: (object) -> | |
result = @find {predicate: 'owns', object: object} | |
result[0].subject | |
friends: (user) -> | |
if typeof user isnt 'string' | |
user = user._id | |
befriended = _.pluck @find({subject: user, predicate: 'isFriendsWith'}), 'object' | |
befriendedBy = _.pluck @find({predicate: 'isFriendsWith', object: user}), 'subject' | |
allFriends = befriended.concat(befriendedBy) | |
_.uniq allFriends, '_id' | |
notify: (whom, ofWhat) -> | |
if _.isArray(whom) | |
return whom.map (w) => @notify w, ofWhat | |
what = @stringify ofWhat | |
# Would probably just merge this on the client-side and periodically compact DB | |
# But we'll de-dupe notifications here to show how easy it is | |
alreadyNotified = @find {subject: whom, object: what} | |
unless alreadyNotified.length > 0 | |
@put {subject: whom, predicate: 'isNotifiedBy', object: what} | |
stringify: (rel) -> | |
if typeof rel is 'string' | |
return "<Rel: #{rel}>" | |
switch rel._type | |
when 'rel' then "#{@stringify(rel.subject)} #{rel.predicate} #{@stringify(rel.object)}" | |
when 'user' then "#{rel.name}" | |
when 'post' then "{#{@owner(rel).name}: \"#{rel.text}\"}" | |
else rel | |
# Logging | |
Rel.on('put').each (rel) -> console.log Rel.stringify(rel) | |
# Notify users when their friends create posts | |
Rel.on('put') | |
.filter (rel) -> rel.predicate is 'owns' | |
.each (rel) -> | |
friends = Rel.friends rel.subject | |
Rel.notify friends, rel | |
# Notify users when their posts are liked | |
Rel.on('put') | |
.filter (rel) -> rel.predicate is 'likes' | |
.each (rel) -> | |
owner = Rel.owner rel.object | |
Rel.notify owner, rel | |
# Notify users when their friends like posts | |
Rel.on('put') | |
.filter (rel) -> rel.predicate is 'likes' | |
.each (rel) -> | |
friends = Rel.friends rel.subject | |
Rel.notify friends, rel | |
return Rel |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment