-
-
Notifications
You must be signed in to change notification settings - Fork 1.2k
API (v0.3.x)
Please post documentation comments, questions, and suggestions to issue #70.
Table of Contents
- Gun constructor
- gun.put
- gun.key
- gun.get
- gun.path
- gun.set
- gun.back
- gun.on
- gun.map
- gun.not
- gun.init
- gun.val
- gun.opt
Used to creates a new gun database instance.
var gun = Gun(options)
note:
Gun
works with or without thenew
operator.
-
no parameters
undefined
creates a local datastore using the default persistence layer, either localStorage or a JSON file. -
passing a URL
string
creates the above local datastore that also tries to sync with the URL.- or you can pass in an
array
of URLs to sync with multiple peers.
- or you can pass in an
-
the previous options are actually aggregated into an
object
, which you can pass in yourself.-
options.peers
is an object, where the URLs are properties, and the value is an empty object. -
options.uuid
allows you to override the default 24 random alphanumeric soul generator with your own function. -
options['module name']
allows you to pass options to a 3rd party module. Their project README will likely list the exposed options. Here is a list of such modules...
-
Sync with one peer
Gun('http://yourdomain.com/gun')
Sync with many peers
Gun(['http://server1.com/gun', 'http://server2.com/gun'])
Working with modules
Gun({
// Amazon S3 (comes bundled)
s3: {
key: '',
secret: '',
bucket: ''
},
// simple JSON persistence (bundled)
// meant for ease of getting started
// NOT meant for production
file: 'file/path.json',
// set your own UUID function
uuid: function () {...}
})
Save data into gun, syncing it with your connected peers.
It has three parameters, and only the first is required:
- the
data
to save - an optional
callback
, invoked on each acknowledgment - an
options
object, though no options are available yet
gun.put(data, callback)
Note: when using
.put
, if any part of the chain is undefined, it is implicitly set as an empty object.
gun.get('undefined key').path('undefined.properties').put(true)
// `.put` creates an empty object on each undefined path/key,
// and places the value at the last point.
.put
restricts the input to a specific subset:
Gun will refuse undefined
, NaN
, Infinity
, and arrays
.
Traditional arrays are dangerous in real-time apps
The callback
is fired each time a peer responds with an error or successful persistence message, including the local cache. Acknowledgement can be slow, but the write propagates across networks as fast as the pipes connecting them.
If the error argument is undefined, then the operation succeeded, although the exact values are left up to the module developer.
Saving objects
gun.path('propertyName').put({
property: 'value',
object: {
nested: true
}
})
Saving primitives
// strings
gun.path('name.first').put('Dave')
// numbers
gun.path('temperature').put(58.6)
// booleans
gun.path('enabled').put(true)
Using the callback
gun.path('survey.submission').put(submission, function (err) {
if (err) {
return ui.show.error(err)
}
ui.show.success(true)
})
gun.put
does not change the gun context.
gun.path('key').put(value) /* same context as */ gun.path('key')
While nodes can be placed directly into gun by its reference:
var msg = Gun().put({ text: 'Hello world!' })
Gun().get('messages').set(msg)
Nodes with nested nodes can NOT successfully be placed directly into gun by its reference:
var sender = Gun().put({ name: 'Tom' })
var msg = Gun().put({
text: 'Hello world!',
sender: sender // this will fail
})
// however
msg.path('sender').put(sender) // this will succeed
Index your data, so you can find it later, faster.
We can think of keys as variables pointing to an object, and a node can have more than one name.
The .key
method takes 3 arguments, and only the first is required:
- the
name
of the category to join under - the
callback
to invoke on each acknowledgment -
options
for configuration
A string representing the name of a group to attach a node to. Common practice is to namespace your keys into routes: .key('users/cities/Provo')
note: keys are case sensitive
The same behavior as the callback in gun.put.
Ignore the chain context, tagging a node directly by it's soul.
// find node by soul "Zwl6..." and tag it under "examples"
gun.key('examples', null, 'Zwl6PaS4oo7Ivt21X5bU0nds')
Aggregating fragmented data into a named object
// each member of the chatroom
gun.get('trace').path([userID, 'profile']).key(userID + '/profile')
gun.get('battlefleet').path([userID, 'profile']).key(userID + '/profile')
gun.get(userID + '/profile')
// contains the merged sum of all profile information
This is especially useful since the intermediate objects might be large, and by putting a key on the data you're looking for, you won't need to perform the costly lookup in the future.
Tagging data on insertion
gun.get(group).path([memberID, 'contact']).put({
twitter: '@databaseGun',
gitter: 'gitter.im/amark/gun'
}).key(group + '/' + memberID + '/contact')
By putting a name on the data, the operation will be faster in the future since it won't need to request the potentially large group
object, or even the member object. We can skip straight to the data we need. This functionality pairs nicely with .not
.
gun.key
does not change the context.
gun.path('node').key('nodes') /* same context as */ gun.path('node')
Load all data under a key into the context.
It takes three parameters:
keyName
callback
options
You'll almost never need to use the callback
or options
parameters, unless you're building bare-metal extensions.
No options are currently available for this method.
The keyName
string is the name of the data you want to retrieve.
Note that if you use
.put
at any depth after retrieving a key that doesn't exist, the key is implicitly initialized as an empty object.
gun.get(existingData).put({ property: 'value' })
/*
{
data: ...,
property: 'value',
data: ...
}
*/
gun.get(nonExistentKey).put({ property: 'value' })
// { property: 'value' }
The callback is a listener for errors and raw node data. It may be called multiple times for a single request, since gun uses a streaming architecture. If you need to listen for incoming data, use .not
or .val
.
The callback is exposed for bare-metal extensions. Most developers will never need it.
gun.get(key, function (error, node) {
// called many times
})
Retrieving a key
// retrieve all available seats
gun.get('seats/available').map(ui.show.seats)
Using the callback
gun.get(key, function (error, node) {
if (error) {
server.log(error)
}
})
After calling gun.get
, the chain context refers to the data you requested, regardless of what happened before calling gun.get
.
gun.get('materials').get('users') /* same context as */ gun.get('users')
Navigate through a node's properties
.path
accepts three parameters, but only the first is required.
- the
property
name - an optional
callback
function - an
options
object, although none are supported yet
The property
is the name of the field to move to.
// move to the "themes" field on the settings object
gun.get('settings').path('themes')
Once you've changed the context, you can read, write, and path
forward from that field. Although you can just chain one path
after another, that can be unnecessarily verbose, so there are two shorthand styles:
- dot format
- array format
Here's dot notation in action:
// verbose
gun.get('settings').path('themes').path('active')
// shorthand
gun.get('settings').path('themes.active')
And the array format, which really becomes useful when using variables instead of literal strings:
gun.get('settings').path(['themes', themeName])
The dot notation can do some strange things if you're not expecting it. Under the hood, everything is changed into a string, including floating point numbers. If you use a decimal in your path, it will split into two paths...
gun.path(30.5)
// interprets to
gun.path(30).path(5)
This can be especially confusing if you've disabled implicit initialization, as the chain might never resolve to a value.
We may change this in the near future.
The callback
is used by bare-metal extensions to provide handling for errors and raw data streams. Like .get
, if you want to listen for new data, use the .on
or .val
methods. With that said, the callback takes three arguments:
error
- the
data
partial - the
property
name
Navigating to a property
/*
where `user` is {
name: 'Bob'
}
*/
gun.get('user').path('name')
Once you've focused on the name
property, you can chain other methods like .put
or .on
to interact with it.
Moving through multiple properties
/*
where `user` is {
name: { first: 'bob' }
}
*/
gun.get('user').path('name').path('first')
// or the shorthand...
gun.get('user').path('name.first')
gun.path
creates a new context each time it's called, and is always a result of the previous context.
gun.get('API').path('path').path('chain')
/* is different from */
gun.get('API').path('path')
/* and is different from */
gun.get('API')
Add a unique item to an unordered list.
gun.set
works in the sense of a mathematical set, where each item in the list is unique. If the same object is added twice, it's simply merged. The main distinction is that sets can only contain objects, not primitives.
Note: we may allow primitives in the future.
gun.set
takes a node to add to the set in the form of a gun instance. For example:
var data = gun.get('data')
var parent = gun.get('parent')
parent.set(data)
The data must always evaluate to a node, since the soul is needed to create a unique field name. You can pass a reference to any node in gun.
The callback is invoked exactly the same as .put
, with (error, okay)
parameters. The okay
parameter may be undefined if underlying layers chose not to provide more information.
v0.2.x
had a method called .set
that works somewhat similar to the new method. The main distinctions:
The old version...
- Wasn't a true set It was basically "pushing" to a list
- Was becoming an API catchall The method was becoming bloated with features
In v0.3.x
we decided to clean up the API and move the .not/.put
behavior into a dedicated method called .init
. To prevent too much confusion, we waited until v0.3.4
before publishing the new .set
method. Since some of the functionality was moved into the .init
method, we wanted an obvious distinction between this version and last version's .set
method, so we chose not to allow primitive types (although they will be supported in the future).
var gun = Gun()
var bob = gun.get('bob')
var dave = gun.get('dave')
dave.path('friends').set(bob)
bob.path('friends').set(dave)
The "friends" example is perfect, since the set guarantees that you won't have duplicates in your list.
var gun = Gun()
var book1 = gun.get('book1')
var book2 = gun.get('book2')
var book3 = gun.put({ title: title })
var books = gun.get('books')
books.set(book1)
books.set(book2)
books.set(book3)
gun.set
changes the chain context.
gun.path('friends') /* is not the same as */ gun.path('friends').set(friend)
Move up to the parent context on the chain.
Every time a new chain is created, a reference to the old context is kept in the back
property. It's not a function, but a reference to the this
value you had before it was changed. For example, when calling .path
, a new context is assigned to the this
value, allowing you to chain directly off your context in jQuery style. If you want to return to your old context, just use gun.back
.
Moving to a parent context
gun.get('peers')
/* change the context to my peer */
.path(myself)
.put(me)
/* go back to the "get" statement */
.back.map(...)
Another example
gun.get('players').get('game/history').back
// is the same as...
gun.get('players')
The context will always be different, since .back
's only purpose is to change it.
gun.path('property') /* is not the same as */ gun.path('property').back
Subscribe to updates changes on a node or property real-time.
When the property or node you're focused on is changed, the callback is immediately fired and given the value as it is at that point in time.
Since gun uses data streams, the callback will probably be called multiple times as new chunk comes in.
Currently, the only option is to filter old data, and be given the changes, but nothing more. If you're listening to a node with 100 fields, and just one changes, you'll be passed a node with a single property representing that change. Since it's often useful, there's a shorthand for it...
Longhand syntax
gun.on(callback, {
change: true
})
Shorthand syntax
gun.on(callback, true)
Listening for updates on a key
gun.path('users/' + username).on(function (user) {
// update in real-time
if (user.online) {
view.show.active(user.name)
} else {
view.show.offline(user.name)
}
})
Listening to updates on a field
gun.get('lights').path('living room').on(function (state, room) {
// update the UI when the living room lights change state
view.lights[room].show(state)
})
gun.on
does not change the chain context.
gun.get(keyName).on(handler) /* is the same as */ gun.get(keyName)
On any update —even if a property is only updated with the same value— gun.on(cb,true)
fires. Even if the value does not change, the HAM state will still be updated.
Loop over each property in a node, and subscribe to future changes. It's essentially performing a .path
on each field.
If you don't know the property names to .path
into, or need to "forEach"
over a group of data, the .map
function is usually your best choice. It accepts two arguments:
- a
Callback
function - an
Options
object
If your data is cyclic and has a lot of self-references, you may receive multiple callbacks with the same result. For example: "Alice is both the president of the company, the wife of Bob, and the friend to the cat." This would return 3 times:
key: president value: alice,
key: wife value: alice,
key: friend value: alice
.map does not exclude or unify nodes, because they exist in different contexts, it is up to you on either UI side or in structuring your data to take this into account.
The callback is invoked as new data becomes available, and is subscribed to subsequent changes like .on
. Since there may be multiple peers and several layers of data, the callback may be invoked with the same data multiple times, but it will always be the latest at that point in time. It's given both the value and the field it was placed under.
Currently, there is just one option. By passing an object with change
set to true, it will be have like .on
's true
. It has its own shorthand...
Longhand syntax
gun.map(callback, {
change: true
})
Shorthand syntax
gun.map(callback, true)
Iterate over an object
/*
where `visitor/stats` are {
'new customers': 35,
'returning': 65
}
*/
gun.get('visitor/stats').map(function (percent, category) {
pie.chart(category, percent)
})
The context for .map
is a bit tricky. All dependent methods will treat a .map
context as though it were split into every possible .path
from that object. Stay with me.
If you run a .put(null)
directly after a .map
, every field will be changed to null. Effectively, the .map
statement applied the .put
operation to every property on the object.
/*
where `object` is {
field1: 'one',
field2: 'two'
}
*/
gun.get('object').map().put(null)
/*
now `object` is {
field1: null,
field2: null
}
*/
Hopefully this demonstrates some of .map
s expressive power. But to summarize and stick with the Chain Context
tradition, here's the gist:
gun.get(keyName).map() /* is not the same as */ gun.get(keyName)
Handle cases where data can't be found.
If you need to know whether a property or key exists, you can check with .not
. It will consult the connected peers and invoke the callback if there's reasonable certainty that none of them have the data available.
No options are currently available for this method.
Warning:
.not
is dangerous, since there is no guarantee that the data doesn't exist elsewhere. Always consider that you may be overwriting other data.
If there's reason to believe the data doesn't exist, the callback will be invoked. It's purpose is to allow you to handle those cases intelligently. For example, you could check to see if a property doesn't exist, setting it to a default value. As mentioned previously, that can be dangerous, and should only be done if your app seriously depends on it.
The callback takes two arguments, key
and kick
.
Note: returning inside a callback is the same as calling
kick
.
The name of the property or key that could not be found.
kick
is a function that accepts a chain context. It can be a bit tricky to wrap your head around. Essentially, gun uses your argument as it's new chain context, so no matter what context you were at previously, you can overwrite it using kick
, sending it out to methods chained after. This is also discouraged, because it makes your code difficult to follow, and also may cause chain divergence. Let's go for something more concrete:
gun.get(original).not(function (key, kick) {
kick(gun.get(otherKeyName))
}).map(backup)
In that example, .not
might have been called if the connected peers could find the data, meaning that .get(backup)
was used instead. The problem arises when new data is received on original
, meaning that the original chain finally has data to work from. .map
is now running on two keys, original
and backup
.
Providing defaults if they aren't found
// if not found
gun.get('players/3').not(function (key) {
// put in an object and key it
gun.put({
active: false
}).key(key)
}).on(handler)
// listen for changes on that key
Setting a property if it isn't found
gun.path('chat.enabled').not(function (path) {
gun.path(path).put(false)
})
Those examples demonstrate the power and drama from .not
. If a peer with the data you're looking for (client or server) goes offline, you change the value of something, then they eventually rejoin, that information will converge, overwriting their data with yours. .not
is a powerful tool, but should be used wisely.
.not
may or may not change the context, depending on how it's used. It has developer-facing methods for manually changing the context, depending on whether or not data was found.
gun.get(keyName).not(handler) /* _might_ be the same as */ gun.get(keyName)
If a property or key hasn't been defined yet, set it as an object.
Note: this is automatically run recursively on each
.put
unless explicitly disabled. You shouldn't need to use this method unless you're doing low-level operations or working with schematized data.
This method helps prevent an unnecessarily verbose API. If it didn't exist, you would need to use .not
for every .get
or .path
to handle cases where it hasn't been defined. For example:
gun.get('data').not(function (key) {
this.put({}).key(key)
}).path('property').not(function (key) {
this.put({})
}).path('next').put(data)
.init
handles things more intelligently than a raw .not
would, but the above example gives a good comparison. Here's an example using .init
instead:
gun.get('data').init().path('property').init().path('next').put(data)
However, with v0.3.x
, the init()
happens implicitly for all methods preceding a .put
, meaning the above example is exactly equivalent to this:
gun.get('data').path('property.next').put(data)
Warning: this method hasn't reached stability and may change in the future.
Read a full object without subscribing updates.
.val
will send a request for the node, and read out the value from the first peer that finishes their response. Since .val
has to wait for a termination, it cannot stream data, slowing down the transmission of large objects considerably. If you simply want to read out an object without subscribing to changes, .val
is your best option. Note that .val
will subscribe for every new item of an object, but not for changes on those items.
This method is excellent for learning and debugging, but should be avoided in production applications. Instead, use
.on
.
.val
takes two arguments:
- a
callback
function - an
options
object
Tip: If no callback is given, it will automatically log the result to the console.
No options are currently available for this method.
The value is the fully-loaded object/primitive, and the property is the field it belongs under.
Reading a full object
// load in the full profile object
gun.get('peers/' + userID).path('profile').val(function (profile) {
// render it, but only once. No updates.
view.show.user(profile)
})
Reading a property
gun.path('temperature').val(function (number) {
view.show.temp(number)
})
Printing a value to the console
gun.path('property').val()
// `console.log` is automatically called
gun.val
does not change the chain context.
gun.get(keyName).val() /* is the same as */ gun.get(keyName)
Change configuration of the gun database instance.
The options
argument is the same object you pass to the constructor. The options
's properties replace those in the instance's configuration but options.peers
are added to peers known to the gun instance.
Create the gun instance.
gun = Gun('http://yourdomain.com/gun')
Change UUID generator:
gun.opt({
uuid: function () {
return Math.floor(Math.random() * 4294967296);
}
});
Add more peers:
gun.opt({peers: ['http://anotherdomain.com/gun']})
/* Now gun syncs with ['http://yourdomain.com/gun', 'http://anotherdomain.com/gun']. */