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()
andstoreObject()
operate on JSON objects. Each object has a type.getFile()
andstoreFile()
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 tofalse
, 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" } } }
- 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 typesFor 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 ofstoreObject()
), the object’s value is filled in withtrue
.
- 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
- 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