TinyBase logoTinyBase β

An Intro To Relationships

This guide describes how the relationships module gives you the ability to create and track relationships between Row objects based on the data in a Store.

The main entry point to using the relationships module is the createRelationships function, which returns a new Relationships object. That object in turn has methods that let you create new Relationship definitions, access them directly, and register listeners for when they change.

The Basics

Here's a simple example to show a Relationships object in action. The pets Table has three Row objects, each with a string species Cell, which act as a key into the Ids of the species Table. We create a Relationship definition called petSpecies which connects the two:

import {createRelationships, createStore} from 'tinybase';

const store = createStore()
  .setTable('pets', {
    fido: {species: 'dog'},
    felix: {species: 'cat'},
    cujo: {species: 'dog'},
  })
  .setTable('species', {
    dog: {price: 5},
    cat: {price: 4},
  });

const relationships = createRelationships(store);
relationships.setRelationshipDefinition(
  'petSpecies', // relationshipId
  'pets', //       localTableId to link from
  'species', //    remoteTableId to link to
  'species', //    cellId containing remote key
);

console.log(relationships.getRemoteRowId('petSpecies', 'fido'));
// -> 'dog'
console.log(relationships.getLocalRowIds('petSpecies', 'dog'));
// -> ['fido', 'cujo']

The getRemoteRowId method allows you to traverse in the many-to-one direction: in other words for every Row in the local Table you can find out the one remote Row referenced. The getLocalRowIds method is the reverse: for a remote Row, it will return an array of the Row Ids in the local Table that reference it.

There is a special case when the local Table is the same as the remote Table. This creates a 'linked list' of Row Ids. Specify from which you would like to start the list, and the getLinkedRowIds method will return the list:

store.setTable('pets', {
  fido: {species: 'dog', next: 'felix'},
  felix: {species: 'cat', next: 'cujo'},
  cujo: {species: 'dog'},
});

relationships.setRelationshipDefinition('petSequence', 'pets', 'pets', 'next');

console.log(relationships.getLinkedRowIds('petSequence', 'fido'));
// -> ['fido', 'felix', 'cujo']
console.log(relationships.getLinkedRowIds('petSequence', 'felix'));
// -> ['felix', 'cujo']

Relationship Reactivity

As with Metrics and Indexes, Relationships objects take care of tracking changes that will affect the Relationships. The familiar paradigm is used to let you add a listener to the Relationships object. The listener fires when there's a change to a Relationship:

const listenerId = relationships.addRemoteRowIdListener(
  'petSpecies',
  'cujo',
  () => {
    console.log(relationships.getRemoteRowId('petSpecies', 'cujo'));
  },
);
store.setCell('pets', 'cujo', 'species', 'wolf');
// -> 'wolf'

As expected, reactivity will also work for local and linked Row relationships (with the addLocalRowIdsListener method and addLinkedRowIdsListener method respectively).

You can set multiple Relationship definitions on each Relationships object. However, a given Store can only have one Relationships object associated with it. If you call this function twice on the same Store, your second call will return a reference to the Relationships object created by the first.

Let's find out how to include relationships in a user interface in the Building A UI With Relationships guide.