The Data Store API allows an application to create and maintain a data store that can be shared with other applications. To make multiple applications keep their local storages up-to-date with the data store, this API supports a mechanism to allow an application to concurrently synchronize data from the data store into the application-local storage which can be indexed, grouped or sorted in whatever format the application needs in order to support its UI.
The Contacts Manager API and the Messaging API were originally designed to support richer querying, like filtering, sorting and grouping data. However, they both have the severe shortcoming that the consumers are forced to live with the limitations of what querying capabilities those APIs can have.
That's why we are constantly having to revise these APIs because it turns out that the querying capabilities aren't matching what our applications need, which is not a workable long-term solution. Also, it's not even a workable short-term solution for third-party applications since we cannot revise the APIs to support the capabilities that every third-party application developer needs.
Therefore, we've been figuring out such a generic Data Store API to allow applications to synchronize and save data into their own application-local storages which can be managed in various formats than we could think of and bake into APIs, thus supporting richer querying capabilities that the application really needs.
Note that the Data Store API is still in charge of maintaining a central storage to keep data which can be added, retrieved or deleted by applications through a bunch of methods provided by the Data Store API. This implies the application doesn't need to duplicate the whole data in its own local storage. Instead, it can simply synchronize the data that is actively required to construct the needed indexes for its own querying purpose.
Similar to the IndexedDB API, the Data Store API provides some database-like methods to add, retrieve and delete data records but it doesn't expose methods for building indexes or searching data. Instead, it provides a synchronizing mechanism for applications to keep their local storages up-to-date and accordingly update the local indexes needed for filtering data.
The other difference from the IndexedDB API is the Data Store API provides a central data storage which can be concurrently accessed and modified by multiple applications. Also, it provides a permission model to allow different applications to have different types of priviledges to change the data store or listen to the change of the data store.
If an application has the privilege to modify the content of the data store, it can use a bunch of basic methods defined in the DataStore to manage the data records in the data store, which is shown as the following example.
// Retrieve a list of data stores named as 'fb-contacts'. navigator.getDataStores('fb-contacts').then(function(stores) { if (!stores.length) { return; } // Check if the application is allowed to modify the data store. if (stores[0].readOnly) { return; } // Retrieve an object. stores[0].get(42).then(function(obj) { // Update the object obj.name = 'foo'; stores[0].update(42, obj).then(function(id) { // The object has been updated. }, function(error) { // The object fails to be updated. }); }); // Delete an object. stores[0].remove(23).then(function(success) { if (success) { // The object has been deleted. } else { // The object fails to be deleted. } }); // Add a new object. stores[0].insert({ name: "bar" }).then(function(id) { // The object has been added. }, function(error) { // The object fails to be added. }); });
An application can call the sync()
method defined in the
DataStore to keep its local storage synchronized with the data
store, which creates a DataStoreCursor to retrieve the change
history starting from a certain revision kept in the application to the
current revision of the data store, which is shown as the following
example.
var appLocalRevisionId = "revision_id_kept_by_app"; // Retrieve a list of data stores named as 'fb-contacts'. navagiator.getDataStores('fb-contacts').then(functions(stores) { if (!stores.length) { return; } // Check if the application's local storage is out-of-date. if (appLocalRevisionId == stores[0].revisionId) { dump("The app's local storage is already in sync.\n"); return; } var cursor = stores[0].sync(appLocalRevisionId); function cursorResolve(task) { switch (task.operation) { case 'done': // All the data are in sync. Update the local revision ID. dump("The current revision ID: " + task.revisionId + "\n"); appLocalRevisionId = task.revisionId; return; case 'clear': // All the data have to be deleted in the local storage. break; case 'add': // A new object has to be added in the local storage. dump("Add ID: " + task.id + " data: " + task.data + "\n"); break; case 'update': // An object has to be updated in the local storage. dump("Update ID: " + task.id + " data: " + task.data + "\n"); break; case 'remove': // An object has to be deleted in the local storage. dump("Remove ID: " + task.id + " data: " + task.data + "\n"); break; } cursor.next().then(cursorResolve); } // Start to sync. cursor.next().then(cursorResolve); });
This specification defines conformance criteria that apply to a single product: the user agent that implements the interfaces that it contains.
Implementations that use ECMAScript to implement the APIs defined in this specification MUST implement them in a manner consistent with the ECMAScript Bindings defined in the Web IDL specification [[!WEBIDL]], as this specification uses that specification and terminology.
The EventHandler interface represents a callback used for event handlers as defined in [[!HTML5]].
The concepts queue a task and fire an event are defined in [[!HTML5]].
The terms event handler and event handler event types are defined in [[!HTML5]].
The Promise interface, the concepts of a resolver, a resolver's fulfill algorithm and a resolver's reject algorithm are defined in [[DOM4]].
For the application that provides the data store, its manifest MUST be
claimed to own the data store by datastores-owned
, where
the JSON object can contain multiple properties representing different
names of data stores respectively. Each data store can use
readonly
to specify whether the data store can be read by
other applications and description
to describe the
purpose.
As shown as the following example, if a Facebook applicaton wants to
provide a read-only fb-contacts data store, its manifest MUST
be claimed to own the data store by setting the attribute
readonly
to true.
{ datastores-owned: { "fb-contacts": { "readonly": true, "description": "own the Facebook contacts data store" } } }
For the application that wants to access the data store, its manifest
MUST be claimed to access the data store by
datastores-access
, where the JSON object can contain
multiple properties representing different names of data stores
respectively. Each data store can use access
to specify
the application's accessibility and description
to
describe the purpose.
As shown as the following example, if the application wants to read or
modify (e.g., add, update, remove, clear... etc) the content of the
fb-contacts data store, its manifest MUST be claimed to access
the fb-contacts data store by setting the attribute
access
to readwrite.
{ datastores-access: { "fb-contacts": { "access": "readwrite", "description": "access (read and write) the Facebook contacts data stores" } } }
As shown as the following example, if the application simply wants to
read the content of the fb-contacts data store without the
need of modifying it, its manifest MUST be claimed to access the
fb-contacts data store by setting the attribute
access
to readonly.
{ datastores-access: { "fb-contacts": { "access": "readonly", "description": "access (read only) the Facebook contacts data store" } } }
The Data Store API can be exposed to trusted or untrusted contents.
name
parameter. It returns a new
Promise
that will be used to notify the caller about
the result of the operation, which is an array of DataStore
elements to access the data stores which have the same name equal
to the name
.
The getDataStores
method when invoked MUST run
the following steps:
Promise
object and
resolver be its associated resolver
.
name
parameter passed in the
request, where the caller application has claimed the data store
access by datastores-access
in its manifest.
value
argument.
value
argument.
The DataStore interface represents a bunch of properties of the data store and a set of operations that can be used to manage and synchronize the content of the data store.
id
parameter. It returns a new Promise
that will be used to notify the caller about the result of the
operation, which is an arbitrary object to represent the data
record if id
is a single value, or a set of data
records if id
is an array of values.
id
and the data
parameters. It returns
a new Promise
that will be used to notify the caller
about the result of the operation.
data
parameter. It returns a new Promise
that will be used to notify the caller about the result of the
operation, which is an identifier to access the data record that is
added.
id
parameter. It returns a new Promise
that will be used to notify the caller about the result of the
operation, which is a boolean value to indicate whether the data
record(s) is successfully deleted or not.
Promise
that will be used
to notify the caller about the result of the operation.
Promise
that will be used to notify the caller about
the result of the operation, which is a numeric value to indicate
the total number of data records saved in the data store.
revisionId
parameter. It returns a new
DataStoreCursor that will be used to iteratively access a set
of DataStoreTask elements.
change
event of type
DataStoreChangeEvent, fired when a data record is added,
updated or deleted in the data store. Note that if some data change
in the data store when the DataStoreCursor is still
synchronizing data and the cursor's close
method
has not yet been called, all the change events will not be
dispatched to the application's onchange
event handler.
Instead, all the changes will be managed by the cursor's
next
method as additional operations.
The get
method when invoked MUST run the
following steps:
Promise
object and
resolver be its associated resolver
.
id
parameter passed in the
request.
value
argument.
value
argument.
The update
method when invoked MUST run the
following steps:
Promise
object and
resolver be its associated resolver
.
readOnly
attribute of the current data store is
true to the caller, invoke resolver's reject algorithm
without assigning a value to the value
argument.
id
parameter passed in the
request.
value
argument.
data
parameter
passed in the request.
value
argument.
value
argument.
The insert
method when invoked MUST run the
following steps:
Promise
object and
resolver be its associated resolver
.
readOnly
attribute of the current data store is
true to the caller, invoke resolver's reject algorithm
without assigning a value to the value
argument.
data
parameter passed in the request.
value
argument.
value
argument.
The remove
method when invoked MUST run the
following steps:
Promise
object and
resolver be its associated resolver
.
readOnly
attribute of the current data store is
true to the caller, invoke resolver's reject algorithm
with false as the value
argument.
id
parameter passed in the
request.
value
argument.
id
parameter passed in
the request.
value
argument.
value
argument.
The clear
method when invoked MUST run the
following steps:
Promise
object and
resolver be its associated resolver
.
readOnly
attribute of the current data store is
true to the caller, invoke resolver's reject algorithm
without assigning a value to the value
argument.
value
argument.
value
argument.
The getLength
method when invoked MUST run the
following steps:
Promise
object and
resolver be its associated resolver
.
value
argument.
value
argument.
The sync
method when invoked MUST run the
following steps:
store
of dataStoreCursor to the
current data store.
The following are the event handlers (and their corresponding event types) that MUST be supported as attributes by the DataStore object.
Event handler | Event name | Event type | Short description |
---|---|---|---|
onchange |
change |
DataStoreChangeEvent |
Handles the information of the data record changed in the data store. |
The DataStoreCursor interface allows the application to iterate through a list of DataStoreTask elements that represents the change history of the data store.
Promise
that will be used to notify the caller
about the result of the operation, which is a DataStoreTask
to represent the information of the change operation.
next
method as additional operations,
which means when the cursor completes its tasks, the application
will be in synchronization with the current revision of the data
store.
The next
method when invoked MUST run the
following steps:
Promise
object and
resolver be its associated resolver
.
value
argument.
revisionId
of dataStoreTask to the
revision of the data store, which changes a data record.
id
of dataStoreTask to the
identifier of the changed data record in the data store.
operation
of dataStoreTask to the
type of operation that changes the data record in the data store.
data
of dataStoreTask to the
changed data record in the data store.
value
argument.
The close
method when invoked MUST run the
following steps:
The DataStoreTask dictionary contains the information related to a data record changed in the data store.
operation
is clear or done.
operation
is clear or done.
The DataStoreChangeEvent interface represents the event related to a date record changed in the data store.
operation
is
clear or done.
owner
here
is not the owner application of the data store. Instead, it's the
application making the change in the data store.
The attribute operation
in a DataStoreTask or
a DataStoreChangeEvent can have the following values:
The editors would like to express their gratitude to the Mozilla Firefox OS Team and specially to Jonas Sicking, Mounir Lamouri, Ehsan Akhgari, Thinker Lee and Hsin-Yi Tsai for their technical guidance, as well as to Andrea Marchesini for his implementation work and support. Also, huge thanks to Zoltan Kis (Intel) and Christophe Dumez (Samsung) for their suggestions and contributions to this specification.