# Feathers Offline-first API
This api describes three wrappers first the server wrapper, realtimeWrapper,
and then the two client wrappers owndataWrapper,
and ownnetWrapper.
# @feathers-offline/server
(opens new window) (opens new window) (opens new window) (opens new window) (opens new window)
@feathersjs-offline/server (opens new window)is a database service adapter that extends any Feathers CRUD database adapter and helps to enable both own-data and own-net (as implemented in @feathersjs-offline/client (opens new window) for the service chosen.
$ npm install --save @feathersjs-offline/server
Important: @feathersjs-offline
extends any Feathers Common database adapter API and querying syntax.
# API
# service(options)
Returns a new service instance initialized with the given options.
const ls-service = require('feathers-localstorage');
const { realtimeWrapper } = require('@feathersjs-offline/server');
app.use('/messages', ls-service({
storage: window.localStorage || AsyncStorage
}));
realtimeWrapper(app, '/messages', { storage, id, startId, name, store, paginate });
Options:
As the wrapper extends the wrapped service, the available options are those of the wrapped service and these:
useShortUuid
(optional, defaulttrue
) - Generate shortuuid
's. If false longuuid
's are generated. This option should match whatever you choose on the client.adapterTest
(optional, defaultfalse
) - This is usually only used for running adapter tests as it suppresses returning the control attributesupdatedAt,
onServerAt,
deletedAt,
anduuid
(or what ever you chose to call them) in results.myUuid
(optional, default'uuid'
) - Rename control attributeuuid
to suit your model.myUpdatedAt
(optional, default'updatedAt'
) - Rename control attributeupdatedAt
to suit your model.myOnServerAt
(optional, default'onServerAt'
) - Rename control attributeonServerAt
to suit your model.myDeletedAt
(optional, default'deletedAt'
) - Rename control attributedeletedAt
to suit your model.
Please note, when renaming control attributes you must do it on both the client and the server side.
# Internal service handle
The internal service handle is useful for instance if you want to insert or delete rows directly into the wrapped service adapter. You can use it with any of the CRUD operations get
, find
, create
, update
, patch
, or remove
.
The available service handle is:
Handle | Comment |
---|---|
service.wrapped | The server database (i.e. equivalent to app.service('/message') for non-wrapped services) |
The service handle is read-only.
# Methods
The services under realtimeWrapper
control all implement the standard service methods as described in Services.
# Example
Here is an example of a Feathers server with a messages in-memory service that supports pagination:
$ npm install @feathersjs/feathers @feathersjs/express @feathersjs/socketio @feathersjs/errors feathers-memory @feathersjs-offline/server
In app.js
:
const feathers = require('@feathersjs/feathers');
const express = require('@feathersjs/express');
const socketio = require('@feathersjs/socketio');
const memory = require('feathers-memory');
const realtimeWrapper = require('@feathersjs-offline/server);
// Create an Express compatible Feathers application instance.
const app = express(feathers());
// Turn on JSON parser for REST services
app.use(express.json());
// Turn on URL-encoded parser for REST services
app.use(express.urlencoded({ extended: true }));
// Enable REST services
app.configure(express.rest());
// Enable REST services
app.configure(socketio());
// Create an in-memory Feathers service with a default page size of 2 items
// and a maximum size of 4
app.use('/messages', memory({
paginate: {
default: 2,
max: 4
}
}));
realtimeWrapper(app, '/messages');
// Set up default error handler
app.use(express.errorHandler());
// Create a dummy Message
app.service('messages').create({
text: 'Message created on server'
}).then(message => console.log('Created message', message));
// Start the server.
const port = 3030;
app.listen(port, () => {
console.log(`Feathers server listening on port ${port}`)
});
Run the example with node app
and go to localhost:3030/messages (opens new window).
# @feathersjs-offline/client, own-data
(opens new window) (opens new window) (opens new window) (opens new window) (opens new window)
The owndataWrapper
(and Owndata
) of @feathersjs-offline/client (opens new window) is a database service adapter that wraps and extends any Feathers CRUD database adapter and behind the scenes locally stores data in localStorage (opens new window) in the browser or AsyncStorage (opens new window) in React Native.
Options:
All options available for the wrapped adapter can be used in addition to:
id
(optional, default:'id'
) - The name of the id field property. Preferably, forown-data
it ought to beuuid.
storage
(optional, defaultwindow.localStorage
) - The local storage engine. You can pass in the browserswindow.localStorage,
React Native'sAsyncStorage,
or a NodeJSlocalstorage
module.store
(optional, defaultnull
) - An object with id to item assignments to pre-initialize the data store.paginate
(optional, defaultfalse
) - A pagination object containing a default and max page size.multi
(optional) - Allow create with arrays and update and remove with id null to change multiple items. Can be true for all methods or an array of allowed methods (e.g.[ 'remove', 'create' ]
).timedSync
(optional, default:86400000
ms) - Determines the frequency of forced synchronization with the server. Default is 24 hours. 0 means never. You can always callservice.sync()
to perform a synchronization. Any ad hoc synchronizations does not affect any timed synchronizations.fixedName
(optional, default:null
) - Force the prefix name of the local DB and queue DB. This is useful when several apps on the same device use@feathersjs-offline
and the same service name (e.g.'/messages'
).useShortUuid
(optional, defaulttrue
) - Generate shortuuid
's. If false longuuid
's are generated. This option should match whatever you choose on the server.dates
(optional, defaultfalse
) - Generate shortuuid
's. If false longuuid
's are generated. This option should match whatever you choose on the server.adapterTest
(optional, defaultfalse
) - This is usually only used for running adapter tests as it suppresses returning the control attributes -updatedAt,
onServerAt,
deletedAt,
anduuid
(or what ever you chose to call them) in results.myUuid
(optional, default'uuid'
) - Rename control attributeuuid
to suit your model.myUpdatedAt
(optional, default'updatedAt'
) - Rename control attributeupdatedAt
to suit your model.myOnServerAt
(optional, default'onServerAt'
) - Rename control attributeonServerAt
to suit your model.myDeletedAt
(optional, default'deletedAt'
) - Rename control attributedeletedAt
to suit your model.
Please note, when renaming control attributes you must do it on both the client and the server side.
# Methods
The services under owndataWrapper
control all implement the standard service methods as described in Services. In addition to this there are a couple of other methods to be used at your discretion.
# .getEntries(params)
service.getEntries(params) -> Promise
- Retrieves a list of all resources from the service. params.query can be used to filter and limit the returned data.
async app.service('/messages').getEntries(params)
.then(result => {
console.log(`entries = ${JSON.stringify(result)}`);
});
Note: getEntries
retrieves all resources (fulfilling the criteria of params.query
) and always return an array.
# .sync(bAll)
service.sync(bAll) -> Promise
- Synchronize local data with server. if bAll
is true
synchronization is done beginning from BOT (Beginning Of Time). If bAll
is false
synchronization will be done from the newest possible timestamp (determined from resources in local DB). Synchronization will only be performed if no active operations involving the server are running.
async app.service('/messages').getEntries(params)
.then(result => {
console.log(`entries = ${JSON.stringify(result)}`);
});
# Internal service handles
Internal service handles are useful for receiving messages whenever a CRUD operation have been successfully performed. To receive a message you first have to register a message handler for the relevant service handle. You can register message handlers for the messages created,
updated,
patched,
and removed
. The available service handles are:
Handle | Comment |
---|---|
service.local | The local database holding the relevant service resources |
service.queue | The queue database holding all unfinished requests/operations |
service.remote | The server database (i.e. equivalent to app.service('/message') for non-wrapped services) |
Service handles are read-only.
See an example using service handles and showing the inner workings of offline-first wrappers here (opens new window).
In addition, the service emits a synced
message every time a synchronization has been attempted. It returns a value of true
if the synchronization was successful and false
otherwise.
# Example
See the clients (opens new window) chapter for more information about using Feathers in the browser and React Native.
# Browser
<html lang="en">
<head>
<meta http-equiv="content-type" content="text/html; charset=utf-8">
<meta name="viewport"
content="width=device-width, initial-scale=1.0, maximum-scale=1, user-scalable=0" />
<title>FeathersJS chat</title>
<link rel="shortcut icon" href="favicon.ico">
<link rel="stylesheet" href="//unpkg.com/feathers-chat@4.0.0/public/base.css">
<link rel="stylesheet" href="//unpkg.com/feathers-chat@4.0.0/public/chat.css">
</head>
<body>
<div id="app" class="flex flex-column"></div>
<script src="//unpkg.com/@feathersjs/client@^4.3.0/dist/feathers.js"></script>
<script src="/socket.io/socket.io.js"></script>
<script src="//unpkg.com/@feathersjs-offline/client@^2.0/dist/feathersjs-offline-client.min.js"></script>
<script type="text/javascript">
var service = feathers.localstorage({
storage: window.localStorage
});
var app = feathers().use('/messages', service);
feathersOfflineClient.owndataWrapper(app, '/messages', {});
var messages = app.service('messages');
messages.on('created', function(message) {
console.log('Someone created a message', message);
});
messages.create({
text: 'Message created in browser'
});
</script>
</body>
</html>
</script>
# React Native
$ npm install @feathersjs/feathers feathers-localstorage @feathersjs-offline/owndata --save
import React from 'react-native';
import feathers from '@feathersjs/feathers';
import localstorage from 'feathers-localstorage';
import { owndataWrapper } from '@feathersjs-offline/client';
const { AsyncStorage } = React;
const app = feathers()
.use('/messages', localstorage({ storage: AsyncStorage }));
owndataWrapper(app, '/messages', {});
const messages = app.service('messages');
messages.on('created', function(message) {
console.log('Someone created a message', message);
});
messages.create({
text: 'Message from React Native'
});
# @feathersjs-offline/client, own-net
(opens new window) (opens new window) (opens new window) (opens new window) (opens new window)
The ownnetWrapper
(and Ownnet
) of @feathersjs-offline/client (opens new window) is a database service adapter that wraps and extends any Feathers CRUD database adapter and behind the scenes locally stores data in localStorage (opens new window) in the browser or AsyncStorage (opens new window) in React Native.
Options:
All options available for the wrapped adapter can be used in addition to:
id
(optional, default:'id'
) - The name of the id field property. Preferably, forown-data
it ought to beuuid.
storage
(optional, defaultwindow.localStorage
) - The local storage engine. You can pass in the browserswindow.localStorage,
React Native'sAsyncStorage,
or a NodeJSlocalstorage
module.store
(optional, defaultnull
) - An object with id to item assignments to pre-initialize the data store.paginate
(optional, defaultfalse
) - A pagination object containing a default and max page size.multi
(optional) - Allow create with arrays and update and remove with id null to change multiple items. Can be true for all methods or an array of allowed methods (e.g.[ 'remove', 'create' ]
).throttle
(optional, default:200
ms) - Determines the minimum time before an operation on the local DB is actually written to permanent storage.timedSync
(optional, default:86400000
ms) - Determines the frequency of forced synchronization with the server. Default is 24 hours. 0 means never. You can always callservice.sync()
to perform a synchronization. Any ad hoc synchronizations does not affect any timed synchronizations.fixedName
(optional, default:null
) - Force the prefix name of the local DB and queue DB. This is useful when several apps on the same device use@feathersjs-offline
and the same service name (e.g. '/messages').useShortUuid
(optional, defaulttrue
) - Generate shortuuid
's. If false longuuid
's are generated. This option should match whatever you choose on the server.adapterTest
(optional, defaultfalse
) - This is usually only used for running adapter tests as it suppresses returning the control attributes -updatedAt,
onServerAt,
deletedAt,
anduuid
(or what ever you chose to call them) in results.myUuid
(optional, default'uuid'
) - Rename control attributeuuid
to suit your model.myUpdatedAt
(optional, default'updatedAt'
) - Rename control attributeupdatedAt
to suit your model.myOnServerAt
(optional, default'onServerAt'
) - Rename control attributeonServerAt
to suit your model.myDeletedAt
(optional, default'deletedAt'
) - Rename control attributedeletedAt
to suit your model.
Please note, when renaming control attributes you must do it on both the client and the server side.
# Methods
The services under ownnetWrapper
control all implement the standard service methods as described in Services. In addition to this there are a couple of other methods to be used at your discretion.
# .getEntries(params)
service.getEntries(params) -> Promise
- Retrieves a list of all resources from the service. params.query
can be used to filter and limit the returned data.
async app.service('/messages').getEntries(params)
.then(result => {
console.log(`entries = ${JSON.stringify(result)}`);
});
Note: getEntries
retrieves all resources (fulfilling the criteria of params.query
) and always return an array.
# .sync(bAll)
service.sync(bAll) -> Promise
- Synchronize local data with server. if bAll
is true
synchronization is done beginning from BOT (Beginning Of Time). If bAll
is false
synchronization will be done from the newest possible timestamp (determined from resources in local DB). Synchronization will only be performed if no active operations involving the server are running.
async app.service('/messages').getEntries(params)
.then(result => {
console.log(`entries = ${JSON.stringify(result)}`);
});
# Internal service handles
Internal service handles are useful for receiving messages whenever a CRUD operation have been successfully performed. To receive a message tou first have to register a message handler for the relevant service handle. You can register message handlers for the messages created,
updated,
patched,
and removed.
The available service handles are
Handle | Comment |
---|---|
service.local | The local database holding the relevant service resources |
service.queue | The queue database holding all unfinished requests/operations |
service.remote | The server database (i.e. equivalent to app.service('/messages') for non-wrapped services) |
Service handles are read-only.
See an example using service handles and showing the inner workings of offline-first wrappers here (opens new window).
In addition, the service emits a synced
message every time a synchronization has been attempted. It returns a value of true
if the synchronization was successful and false
otherwise.
# Example
See the clients (opens new window) chapter for more information about using Feathers in the browser and React Native.
# Browser
<html lang="en">
<head>
<meta http-equiv="content-type" content="text/html; charset=utf-8">
<meta name="viewport"
content="width=device-width, initial-scale=1.0, maximum-scale=1, user-scalable=0" />
<title>FeathersJS chat</title>
<link rel="shortcut icon" href="favicon.ico">
<link rel="stylesheet" href="//unpkg.com/feathers-chat@4.0.0/public/base.css">
<link rel="stylesheet" href="//unpkg.com/feathers-chat@4.0.0/public/chat.css">
</head>
<body>
<div id="app" class="flex flex-column"></div>
<script src="//unpkg.com/@feathersjs/client@^4.3.0/dist/feathers.js"></script>
<script src="/socket.io/socket.io.js"></script>
<script src="//unpkg.com/@feathersjs-offline/client@^2.0/dist/feathersjs-offline-client.min.js"></script>
<script type="text/javascript">
var service = feathers.localstorage({
storage: window.localStorage
});
var app = feathers().use('/messages', service);
feathersOfflineClient.ownnetWrapper(app, '/messages', {});
var messages = app.service('messages');
messages.on('created', function(message) {
console.log('Someone created a message', message);
});
messages.create({
text: 'Message created in browser'
});
</script>
</body>
</html>
</script>
# React Native
$ npm install @feathersjs/feathers feathers-localstorage @feathersjs-offline/ownnet --save
import React from 'react-native';
import feathers from '@feathersjs/feathers';
import localstorage from 'feathers-localstorage';
import { ownnetWrapper } from '@feathersjs-offline/ownnet';
const { AsyncStorage } = React;
const app = feathers()
.use('/messages', localstorage({ storage: AsyncStorage }));
ownnetWrapper(app, '/messages', {});
const messages = app.service('messages');
messages.on('created', function(message) {
console.log('Someone created a message', message);
});
messages.create({
text: 'Message from React Native'
});
# License
Copyright (c) 2020 -
Licensed under the MIT license.