@wireapp/cryptobox
Version:
High-level API with persistent storage for Proteus.
150 lines (128 loc) • 6.59 kB
TypeScript
/* dexie-syncable API - an independant syncronization API used by 'dexie-syncable'.
* Version: 1.1
* Date: December 2016
*
* Some assumptions are made upon how the database is structured though. We assume that:
* * Databases has 1..N tables. (For NOSQL databases without tables, this also works since it could be considered a db with a single table.)
* * Each table has a primary key.
* * The primary key is a UUID of some kind since auto-incremented primary keys are not suitable for syncronization
* (auto-incremented key would work but changes of conflicts would increase on create).
* * A database record is a JSON compatible object.
* * Always assume that the client may send the same set of changes twice. For example if client sent changes that server stored, but network went down before
* client got the ack from server, the client may try resending same set of changes again. This means that the same Object Create change may be sent twice etc.
* The implementation must not fail if trying to create an object with the same key twice, or delete an object with a key that does not exist.
* * Client and server must resolve conflicts in such way that the result on both sides are equal.
* * Since a server is the point of the most up-to-date database, conflicts should be resolved by prefering server changes over client changes.
* This makes it predestinable for client that the more often the client syncs, the more chance to prohibit conflicts.
*
* By separating module 'dexie-syncable' from 'dexie-syncable/api' we
* distinguish that:
*
* import {...} from 'dexie-syncable/api' is only for getting access to its
* interfaces and has no side-effects.
* Typescript-only import.
*
* import 'dexie-syncable' is only for side effects - to extend Dexie with
* functionality of dexie-syncable.
* Javascript / Typescript import.
*
*/
import {IDatabaseChange} from 'dexie-observable/api';
export type IDatabaseChange = IDatabaseChange;
export enum DatabaseChangeType {
Create = 1,
Update = 2,
Delete = 3
}
/* ISyncProtocol
Interface to implement for enabling syncronization with a remote database server. The remote database server may be SQL- or NOSQL based
as long as it is capable of storing JSON compliant objects into some kind of object stores and reference them by a primary key.
The server must also be revision- and changes aware. This is something that for many databases needs to be implemented by a REST- or
WebSocket gateway between the client and the backend database. The gateway can act as a controller and make sure any changes
are registered in the 'changes' table and that the API provides a sync() method to interchange changes between client and server.
Two examples of a ISyncProtocol instances are found in:
https://github.com/dfahlander/Dexie.js/tree/master/samples/remote-sync/ajax/AjaxSyncProtocol.js
https://github.com/dfahlander/Dexie.js/tree/master/samples/remote-sync/websocket/WebSocketSyncProtocol.js
*/
/**
* The interface to implement to provide sync towards a remote server.
*
* Documentation for this interface: https://github.com/dfahlander/Dexie.js/wiki/Dexie.Syncable.ISyncProtocol
*
*/
export interface ISyncProtocol {
partialsThreshold?: number;
sync (
context: IPersistedContext,
url: string,
options: any,
baseRevision: any,
syncedRevision: any,
changes: IDatabaseChange[],
partial: boolean,
applyRemoteChanges: ApplyRemoteChangesFunction,
onChangesAccepted: ()=>void,
onSuccess: (continuation: PollContinuation | ReactiveContinuation)=>void,
onError: (error: any, again?: number) => void) : void;
}
/**
* Documentation for this interface: https://github.com/dfahlander/Dexie.js/wiki/Dexie.Syncable.IPersistedContext
*/
export interface IPersistedContext {
save() : Promise<void>;
[customProp: string] : any;
}
/**
* Documentation for this function: https://github.com/dfahlander/Dexie.js/wiki/Dexie.Syncable.ISyncProtocol
*/
export type ApplyRemoteChangesFunction = (
changes: IDatabaseChange[],
lastRevision: any,
partial?: boolean,
clear?: boolean)
=> Promise<void>;
/**
* Provide a poll continuation if your backend is a reqest/response service, such as a REST API.
*/
export interface PollContinuation {
/** Implementation should return number of milliseconds until you want the framework to call sync() again. */
again: number
}
/**
* Provide a reactive continuation if your backend is connected to over WebSocket, socket-io, signalR or such,
* and may push changes back to the client as they occur.
*/
export interface ReactiveContinuation {
react (
/** List of local changes to send to server. */
changes: IDatabaseChange[],
/** Server revision that server needs to know in order to apply the changes correcly. */
baseRevision: any,
/** If true, it means that reach() will be called upon again with additional changes once you'version
* called onChangesAccepted(). An implementation may handle this transactionally, i.e. wait with applying
* these changes and instead buffer them in a temporary table and the apply everything once reac() is called
* with partial=false.
*/
partial: boolean,
/** Callback to call when the given changes has been acknowledged and persisted at the server side.
* This will mark the change-set as delivered and the framework wont try resending these changes anymore.
*/
onChangesAccepted: ()=>void): void;
/** Implementation should disconned the underlying transport and stop calling applyRemoteChanges(). */
disconnect(): void;
}
export enum SyncStatus {
/** An irrepairable error occurred and the sync provider is dead. */
ERROR = -1,
/** The sync provider hasnt yet become online, or it has been disconnected. */
OFFLINE = 0,
/** Trying to connect to server */
CONNECTING = 1,
/** Connected to server and currently in sync with server */
ONLINE = 2,
/** Syncing with server. For poll pattern, this is every poll call.
* For react pattern, this is when local changes are being sent to server. */
SYNCING = 3,
/** An error occured such as net down but the sync provider will retry to connect. */
ERROR_WILL_RETRY = 4
}