BaseClient

A BaseClient instance is the main endpoint you will use for interacting with a connected storage: listing, reading, creating, updating and deleting documents, as well as handling incoming changes.

Base clients are usually used in data modules, which are loaded with two BaseClient instances by default: one for private and one for public documents.

However, you can also instantiate a BaseClient outside of a data module using the remoteStorage.scope() function. Similarly, you can create a new scoped client within another client, using the BaseClient’s own scope().

Data read/write operations

A BaseClient deals with three types of data: folders, objects and files:

  • getListing() returns a mapping of all items within a folder.

  • getObject() and storeObject() operate on JSON objects. Each object has a type.

  • getFile() and storeFile() operates on files. Each file has a MIME type.

  • getAll() returns all objects or files for the given folder path.

  • remove() operates on either objects or files (but not folders; folders are created and removed implictly).

Caching logic for read operations

All functions requesting/reading data will immediately return data from the local store, as long as it is reasonably up-to-date. The default maximum age of requested data is two times the periodic sync interval (10 seconds by default).

However, you can adjust this behavior by using the maxAge argument with any of these functions, thereby changing the maximum age or removing the requirement entirely.

  • If the maxAge requirement is set, and the last sync request for the path is further in the past than the maximum age given, the folder will first be checked for changes on the remote, and then the promise will be fulfilled with the up-to-date document or listing.

  • If the maxAge requirement is set, and cannot be met because of network problems, the promise will be rejected.

  • If the maxAge requirement is set to false, or the library is in offline mode, or no remote storage is connected (a.k.a. “anonymous mode”), the promise will always be fulfilled with data from the local store.

Hint

If caching for the folder is turned off, none of this applies and data will always be requested from the remote store directly.

List of functions

getListing(path, maxAge)

Get a list of child nodes below a given path.

Arguments:
  • path (string) – The path to query. It MUST end with a forward slash.

  • maxAge (false|number) – (optional) Either false or the maximum age of cached listing in milliseconds. See Caching logic for read operations.

Returns:

Promise<unknown> – A promise for an object representing child nodes

Example usage:

client.getListing('')
      .then(listing => console.log(listing));

The folder listing is returned as a JSON object, with the root keys representing the pathnames of child nodes. Keys ending in a forward slash represent folder nodes (subdirectories), while all other keys represent data nodes (files/objects).

Data node information contains the item’s ETag, content type and -length.

Example of a listing object:

{
  "@context": "http://remotestorage.io/spec/folder-description",
  "items": {
    "thumbnails/": true,
    "screenshot-20170902-1913.png": {
      "ETag": "6749fcb9eef3f9e46bb537ed020aeece",
      "Content-Length": 53698,
      "Content-Type": "image/png;charset=binary"
    },
    "screenshot-20170823-0142.png": {
      "ETag": "92ab84792ef3f9e46bb537edac9bc3a1",
      "Content-Length": 412401,
      "Content-Type": "image/png;charset=binary"
    }
  }
}

Warning

At the moment, this function only returns detailed metadata, when caching is turned off (new RemoteStorage({cache: false})). With caching turned on, it will only contain the item names as properties with true as value. See issues #721 and #1108 — contributions welcome!

getObject(path, maxAge)

Get a JSON object from the given path.

Arguments:
  • path (string) – Relative path from the module root (without leading slash).

  • maxAge (false|number) – (optional) Either false or the maximum age of cached object in milliseconds. See Caching logic for read operations.

Returns:

Promise<unknown> – A promise, which resolves with the requested object (or null if non-existent)

Example:

client.getObject('/path/to/object')
      .then(obj => console.log(obj));
storeObject(typeAlias, path, object)

Store object at given path. Triggers synchronization.

See declareType() and data types for an explanation of types

For any given path, must not be called more frequently than once per second.

Arguments:
  • typeAlias (string) – Unique type of this object within this module.

  • path (string) – Path relative to the module root.

  • object (object) – A JavaScript object to be stored at the given path. Must be serializable as JSON.

Returns:

Promise<unknown> – Resolves with revision on success. Rejects with a ValidationError, if validations fail.

Example:

var bookmark = {
  url: 'http://unhosted.org',
  description: 'Unhosted Adventures',
  tags: ['unhosted', 'remotestorage', 'no-backend']
}
var path = MD5Hash(bookmark.url);

client.storeObject('bookmark', path, bookmark)
      .then(() => console.log('bookmark saved'))
      .catch((err) => console.log(err));
getAll(path, maxAge)

Get all objects directly below a given path.

Arguments:
  • path (string) – Path to the folder. Must end in a forward slash.

  • maxAge (false|number) – (optional) Either false or the maximum age of cached objects in milliseconds. See Caching logic for read operations.

Returns:

Promise<unknown> – A promise for an object

Example usage:

client.getAll('example-subdirectory/').then(objects => {
  for (var path in objects) {
    console.log(path, objects[path]);
  }
});

Example response object:

{
  "27b8dc16483734625fff9de653a14e03": {
    "@context": "http://remotestorage.io/spec/modules/bookmarks/archive-bookmark",
    "id": "27b8dc16483734625fff9de653a14e03",
    "url": "https://unhosted.org/",
    "title": "Unhosted Web Apps",
    "description": "Freedom from web 2.0's monopoly platforms",
    "tags": [
      "unhosted",
      "remotestorage"
    ],
    "createdAt": "2017-11-02T15:22:25.289Z",
    "updatedAt": "2019-11-07T17:52:22.643Z"
  },
  "900a5ca174bf57c56b79af0653053bdc": {
    "@context": "http://remotestorage.io/spec/modules/bookmarks/archive-bookmark",
    "id": "900a5ca174bf57c56b79af0653053bdc",
    "url": "https://remotestorage.io/",
    "title": "remoteStorage",
    "description": "An open protocol for per-user storage on the Web",
    "tags": [
      "unhosted",
      "remotestorage"
    ],
    "createdAt": "2019-11-07T17:59:34.883Z"
  }
}

Hint

For items that are not JSON-stringified objects (for example stored using storeFile() instead of storeObject()), the object’s value is filled in with true.

getFile(path, maxAge)

Get the file at the given path. A file is raw data, as opposed to a JSON object (use getObject() for that).

Arguments:
  • path (string) – Relative path from the module root (without leading slash).

  • maxAge (false|number) – (optional) Either false or the maximum age of the cached file in milliseconds. See Caching logic for read operations.

Returns:

Promise<unknown> – A promise for an object

The response object contains two properties:

mimeType

String representing the MIME Type of the document.

data

Raw data of the document (either a string or an ArrayBuffer)

Example usage (displaying an image):

client.getFile('path/to/some/image').then(file => {
  var blob = new Blob([file.data], { type: file.mimeType });
  var targetElement = document.findElementById('my-image-element');
  targetElement.src = window.URL.createObjectURL(blob);
});
storeFile(mimeType, path, body)

Store raw data at a given path.

Arguments:
  • mimeType (string) – MIME media type of the data being stored

  • path (string) – Path relative to the module root

  • body (string|ArrayBuffer|ArrayBufferView) – Raw data to store

Returns:

Promise<string> – A promise for the created/updated revision (ETag)

Example (UTF-8 data):

client.storeFile('text/html', 'index.html', '<h1>Hello World!</h1>')
      .then(() => { console.log("Upload done") });

Example (Binary data):

var input = document.querySelector('form#upload input[type=file]');
var file = input.files[0];
var fileReader = new FileReader();

fileReader.onload = function () {
  client.storeFile(file.type, file.name, fileReader.result)
        .then(() => { console.log("Upload done") });
};

fileReader.readAsArrayBuffer(file);
remove(path)

Remove node at given path from storage. Triggers synchronization.

Arguments:
  • path (string) – Path relative to the module root.

Returns:

Promise<unknown>

Example:

client.remove('path/to/object')
      .then(() => console.log('item successfully deleted'));

Change events

BaseClient offers a single event, named change, which you can add a handler for using the .on() function (same as in RemoteStorage):

client.on('change', function (evt) {
  console.log('data was added, updated, or removed:', evt)
});

Using this event, you can stay informed about data changes, both remote (from other devices or browsers), as well as locally (e.g. other browser tabs).

In order to determine where a change originated from, look at the origin property of the incoming event. Possible values are window, local, remote, and conflict, explained in detail below.

Example:

{
  // Absolute path of the changed node, from the storage root
  path: path,
  // Path of the changed node, relative to this baseclient's scope root
  relativePath: relativePath,
  // See origin descriptions below
  origin: 'window|local|remote|conflict',
  // Old body of the changed node (local version in conflicts; undefined if creation)
  oldValue: oldBody,
  // New body of the changed node (remote version in conflicts; undefined if deletion)
  newValue: newBody,
  // Body when local and remote last agreed; only present in conflict events
  lastCommonValue: lastCommonBody,
  // Old contentType of the changed node (local version for conflicts; undefined if creation)
  oldContentType: oldContentType,
  // New contentType of the changed node (remote version for conflicts; undefined if deletion)
  newContentType: newContentType,
  // ContentType when local and remote last agreed; only present in conflict events
  lastCommonContentType: lastCommonContentType
}

In general, the dataType of the document can be extracted thus:

const context = evt.newValue?.['@context'] || evt.oldValue?.['@context'] || evt.lastCommonValue?.['@context'];

local

Events with origin local are fired conveniently during the page load, so that you can fill your views when the page loads.

Example:

{
  path: '/public/design/color.txt',
  relativePath: 'color.txt',
  origin: 'local',
  oldValue: undefined,
  newValue: 'white',
  oldContentType: undefined,
  newContentType: 'text/plain'
}

Hint

You may also use for example getAll() instead, and choose to deactivate these.

remote

Events with origin remote are fired when remote changes are discovered during sync.

Note

Automatically receiving remote changes depends on the caching settings for your module/paths.

window

Events with origin window are fired whenever you change a value by calling a method on the BaseClient; these are disabled by default.

Hint

You can enable them by configuring changeEvents for your RemoteStorage instance.

conflict

Events with origin conflict are fired when a conflict occurs while pushing out your local changes to the remote store.

Say you changed ‘color.txt’ from ‘white’ to ‘blue’; if you have set config.changeEvents.window to true (by passing it to the RemoteStorage constructor, see the Constructor section of the RemoteStorage API doc), then you will receive:

{
   path: '/public/design/color.txt',
   relativePath: 'color.txt',
   origin: 'window',
   oldValue: 'white',
   newValue: 'blue',
   oldContentType: 'text/plain',
   newContentType: 'text/plain'
 }

But when this change is pushed out by asynchronous synchronization, this change may be rejected by the server, if the remote version has in the meantime changed from ‘white’ to for instance ‘red’; this will then lead to a change event with origin ‘conflict’ (usually a few seconds after the event with origin ‘window’, if you have those activated). Note that since you already changed it from ‘white’ to ‘blue’ in the local version a few seconds ago, oldValue is now your local value of ‘blue’:

{
   path: '/public/design/color.txt',
   relativePath: 'color.txt',
   origin: 'conflict',
   oldValue: 'blue',
   newValue: 'red',
   oldContentType: 'text/plain',
   newContentType: 'text/plain',
   // Most recent known common ancestor body of local and remote
   lastCommonValue: 'white',
   // Most recent known common ancestor contentType of local and remote
   lastCommonContentType: 'text/plain'
 }

Conflict Resolution

Conflicts are resolved by calling storeObject() or storeFile() on the device where the conflict surfaced. Other devices are not aware of the conflict.

If there is an algorithm to merge the differences between local and remote versions of the data, conflicts may be automatically resolved. storeObject() or storeFile() must not be called synchronously from the change event handler, nor by chaining Promises. storeObject() or storeFile() must not be called until the next iteration of the JavaScript Task Queue, using for example setTimeout().

If no algorithm exists, conflict resolution typically involves displaying local and remote versions to the user, and having the user merge them, or choose which version to keep.

Data types

declareType(alias, uri, schema)

Declare a remoteStorage object type using a JSON schema.

See Defining data types for more info.

Arguments:
  • alias (string) – A type alias/shortname

  • uriOrSchema (string|JsonSchema) –

  • schema (JsonSchema) – A JSON Schema object describing the object type

Example:

client.declareType('todo-item', {
  "type": "object",
  "properties": {
    "id": {
      "type": "string"
    },
    "title": {
      "type": "string"
    },
    "finished": {
      "type": "boolean"
      "default": false
    },
    "createdAt": {
      "type": "date"
    }
  },
  "required": ["id", "title"]
})

Visit http://json-schema.org for details on how to use JSON Schema.

validate(object)

Validate an object against the associated schema.

Arguments:
  • object (<TODO: reflection>) – JS object to validate. Must have a @context property.

Returns:

<TODO: reflection> – An object containing information about validation errors

Example:

var result = client.validate(document);

// result:
// {
//   error: null,
//   missing: [],
//   valid: true
// }

Caching

cache(path, strategy)

Set caching strategy for a given path and its children.

See Caching strategies for a detailed description of the available strategies.

Arguments:
  • path (string) – Path to cache

  • strategy ("ALL"|"SEEN"|"FLUSH") – Caching strategy. One of ‘ALL’, ‘SEEN’, or ‘FLUSH’. Defaults to ‘ALL’.

Returns:

BaseClient – The same instance this is called on to allow for method chaining

Example:

client.cache('documents/', 'ALL');
flush(path)

TODO: document

Arguments:
  • path (string) –

Returns:

unknown

Example:

client.flush('documents/');

Other functions

getItemURL(path)

Retrieve full URL of a document. Useful for example for sharing the public URL of an item in the /public folder. TODO: refactor this into the Remote interface

Arguments:
  • path (string) – Path relative to the module root.

Returns:

string – The full URL of the item, including the storage origin

Warning

This method currently only works for remoteStorage backends. The issues for implementing it for Dropbox and Google Drive can be found at #1052 and #1054.

scope(path)

Instantiate a new client, scoped to a subpath of the current client’s path.

Arguments:
  • path (string) – The path to scope the new client to

Returns:

BaseClient – A new client operating on a subpath of the current base path