Search code examples
gun

Only a node can be linked! Not "undefined"!


I want to put nodes in Gun set.

const Gun = require('gun');
const _ = require('lodash');
require( "gun/lib/path" );

const gun = new Gun({peers:['http://localhost:8080/gun']});

const watchers = [ 
  {
    _id: '123',
    _type: 'skeleton',
    _source: {
      trigger: {
        schedule: {
          later: 'every 1 sec'
        }   
      }   
    }   
  },  
  {
    _id: '456',
    _type: 'snowman',
    _source: {
      trigger: {
        schedule: {
          later: 'every 1 sec'
        }   
      }   
    }   
  }
]; 

const tasks = gun.get('tasks'); 

_.forEach(watchers, function (watcher) {
  let task = gun.get(`watcher/${watcher._id}`).put(watcher);
  tasks.set(task);
});

In the end, I receive only the following message. And script stuck in the terminal.

Only a node can be linked! Not "undefined"!

There is nothing on the listener side:

const tasks = gun.get('tasks');
tasks.map().val(function (task) {
  console.log('task', task);
});

What is wrong?


The result is received on the listener side only if I change the watchers objects to simpler ones, for example:

_.forEach(watchers, function (watcher) {
  let task = gun.get(`watcher/${watcher._id}`).put({id: '123'});
  tasks.set(task);
});

Results:

task { _: { '#': 'watcher/123', '>': { id: 1506953120419 } },
  id: '123' }
task { _: { '#': 'watcher/456', '>': { id: 1506953120437 } },
  id: '123' }

Solution

  • @trex you correctly reported this as a bug, and we got this fixed here: https://github.com/amark/gun/issues/427 .

    When a node is referenced, it should not act as if it is undefined. This was a bug.

    However, in the future, some people may attempt to link non-node references. As such, I would like to answer the title of your subject (but note, your actual issue has been fixed, and your code should now work correctly in v0.8.8+).

    Why do I get "Only a node can be linked!" error?

    Say you have a reference to a thing in gun:

    var thing = gun.get('alice').get('age');
    

    You may want to add it to a set (otherwise called a table, or list, or collection) like so:

    gun.get('list').set(thing);
    

    You will get a "Only a node can be linked!" error. This is annoying! But here is why:

    Because age (or any other example data) is a primitive value, adding it to a table would cause it to lose its context. Instead, we can achieve the exact same end result using the following approach:

    var person = gun.get('alice').get('age');
    gun.get('list').set(thing);
    gun.get('list').map().get('age').on(callback);
    

    Now we get back a list of ages, but those ages will always reflect their latest/current realtime context. Had we just added the age to the table, it would no longer have a realtime context.

    This is why only nodes can be linked, because any of the data on that node that you were trying to link can just be linked to by traversing via the node. In this case, it was by doing .get('age') after the list. There are a couple really cool things about this:

    1. Bandwidth is saved. GUN will only load the age property from each item in the list, it will not load the rest of the item. It syncs the data you ask about.
    2. Everything is traversable. No matter where your data is in a graph, whether it is a document, a key/value pair, a table, relational data, or anything else, it will always be traversable from its node in the graph. This is possible because it is always the node that is linked, not the primitive data.

    Note: What can be frustrating is that you may not know in advance whether a certain gun reference is a node or a primitive, as you could always allow your users to dynamically change the data on that reference. This would require you to handle the error gracefully and do whatever you can best guess the user intended. You can avoid this problem by enforcing a schema on the data in your app. If your app is deployed, we strongly recommend using a schema.

    But what if I want the raw data linked, not a realtime context?

    Then all you have to do is pass the actual value of the data, not the reference to it. Like so:

    gun.get('list').set(thing);
    

    As always, the chatroom is super friendly so come say hi. Please use StackOverflow for asking questions, but notify us in the chatroom. The chatroom is for quick help, and SO is for long standing questions that others would benefit from.

    Thanks for asking this question! I hope this answered it, give us a shout if you have any further questions or concerns.