kinto
Version:
An Offline-First JavaScript client for Kinto.
654 lines (653 loc) • 23.2 kB
TypeScript
import BaseAdapter, { StorageProxy } from "./adapters/base";
import KintoBase from "./KintoBase";
import { Collection as KintoCollection } from "./http";
import { KintoRepresentation, IdSchema, RemoteTransformer, AvailableHook, Hooks, UndefiendKintoRepresentation, UpdateRepresentation, WithOptional, RecordStatus, SyncResult, KintoError, Conflict, Emitter, CollectionSyncOptions } from "./types";
/**
* Compare two records omitting local fields and synchronization
* attributes (like _status and last_modified)
* @param {Object} a A record to compare.
* @param {Object} b A record to compare.
* @param {Array} localFields Additional fields to ignore during the comparison
* @return {boolean}
*/
export declare function recordsEqual(a: {
[key: string]: unknown;
}, b: {
[key: string]: unknown;
}, localFields?: readonly string[]): boolean;
/**
* Synchronization result object.
*/
export declare class SyncResultObject {
/**
* Public constructor.
*/
lastModified?: number | null;
private _lists;
private _cached;
constructor();
/**
* Adds entries for a given result type.
*
* @param {String} type The result type.
* @param {Array} entries The result entries.
* @return {SyncResultObject}
*/
add<K extends keyof SyncResult>(type: K, entries: SyncResult[K] | SyncResult[K][0]): SyncResultObject;
get ok(): boolean;
get errors(): KintoError[];
get conflicts(): Conflict<any>[];
get skipped(): any[];
get resolved(): any[];
get created(): any[];
get updated(): import("./types").Update<any>[];
get deleted(): any[];
get published(): any[];
private _deduplicate;
/**
* Reinitializes result entries for a given result type.
*
* @param {String} type The result type.
* @return {SyncResultObject}
*/
reset(type: keyof SyncResult): SyncResultObject;
private toObject;
}
export declare class ServerWasFlushedError extends Error {
clientTimestamp: number;
serverTimestamp?: number;
message: string;
constructor(clientTimestamp: number, serverTimestamp: number | undefined, message: string);
}
/**
* IDSchema for when using kinto.js as a key-value store.
* Using this IDSchema requires you to set a property as the id.
* This will be the property used to retrieve this record.
*
* @example
* const exampleCollection = db.collection("example", { idSchema: createKeyValueStoreIdSchema() })
* await exampleCollection.create({ title: "How to tie a tie", favoriteColor: "blue", id: "user123" }, { useRecordId: true })
* await exampleCollection.getAny("user123")
*/
export declare function createKeyValueStoreIdSchema(): IdSchema;
/**
* Abstracts a collection of records stored in the local database, providing
* CRUD operations and synchronization helpers.
*/
export default class Collection<B extends {
id: string;
last_modified?: number;
_status?: RecordStatus;
} = any> {
/**
* Constructor.
*
* Options:
* - `{BaseAdapter} adapter` The DB adapter (default: `IDB`)
*
* @param {String} bucket The bucket identifier.
* @param {String} name The collection name.
* @param {KintoBase} kinto The Kinto instance.
* @param {Object} options The options object.
*/
private _bucket;
private _name;
private _lastModified;
db: BaseAdapter<B>;
kinto: KintoBase<B>;
events?: Emitter;
idSchema: IdSchema;
remoteTransformers: RemoteTransformer[];
hooks: Hooks<B>;
localFields: string[];
constructor(bucket: string, name: string, kinto: KintoBase<B>, options?: {
adapter?: (dbName: string, options?: {
dbName?: string;
migrateOldData?: boolean;
}) => BaseAdapter<B>;
adapterOptions?: {
dbName?: string;
migrateOldData?: boolean;
};
events?: Emitter;
idSchema?: IdSchema;
remoteTransformers?: RemoteTransformer[];
hooks?: Hooks<B>;
localFields?: string[];
});
/**
* The HTTP client.
* @type {KintoClient}
*/
get api(): import("./http").default;
/**
* The collection name.
* @type {String}
*/
get name(): string;
/**
* The bucket name.
* @type {String}
*/
get bucket(): string;
/**
* The last modified timestamp.
* @type {Number}
*/
get lastModified(): number | null;
/**
* Synchronization strategies. Available strategies are:
*
* - `MANUAL`: Conflicts will be reported in a dedicated array.
* - `SERVER_WINS`: Conflicts are resolved using remote data.
* - `CLIENT_WINS`: Conflicts are resolved using local data.
*
* @type {Object}
*/
static get strategy(): {
CLIENT_WINS: string;
SERVER_WINS: string;
PULL_ONLY: string;
MANUAL: string;
};
/**
* Validates an idSchema.
*
* @param {Object|undefined} idSchema
* @return {Object}
*/
private _validateIdSchema;
/**
* Validates a list of remote transformers.
*
* @param {Array|undefined} remoteTransformers
* @return {Array}
*/
private _validateRemoteTransformers;
/**
* Validate the passed hook is correct.
*
* @param {Array|undefined} hook.
* @return {Array}
**/
private _validateHook;
/**
* Validates a list of hooks.
*
* @param {Object|undefined} hooks
* @return {Object}
*/
private _validateHooks;
/**
* Deletes every records in the current collection and marks the collection as
* never synced.
*
* @return {Promise}
*/
clear(): Promise<KintoRepresentation<{}[]>>;
/**
* Encodes a record.
*
* @param {String} type Either "remote" or "local".
* @param {Object} record The record object to encode.
* @return {Promise}
*/
private _encodeRecord;
/**
* Decodes a record.
*
* @param {String} type Either "remote" or "local".
* @param {Object} record The record object to decode.
* @return {Promise}
*/
private _decodeRecord;
/**
* Adds a record to the local database, asserting that none
* already exist with this ID.
*
* Note: If either the `useRecordId` or `synced` options are true, then the
* record object must contain the id field to be validated. If none of these
* options are true, an id is generated using the current IdSchema; in this
* case, the record passed must not have an id.
*
* Options:
* - {Boolean} synced Sets record status to "synced" (default: `false`).
* - {Boolean} useRecordId Forces the `id` field from the record to be used,
* instead of one that is generated automatically
* (default: `false`).
*
* @param {Object} record
* @param {Object} options
* @return {Promise}
*/
create(record: WithOptional<B, "id">, options?: {
useRecordId?: boolean;
synced?: boolean;
}): Promise<KintoRepresentation<B>>;
/**
* Like {@link CollectionTransaction#update}, but wrapped in its own transaction.
*
* Options:
* - {Boolean} synced: Sets record status to "synced" (default: false)
* - {Boolean} patch: Extends the existing record instead of overwriting it
* (default: false)
*
* @param {Object} record
* @param {Object} options
* @return {Promise}
*/
update(record: B & {
id: string;
}, options?: {
synced?: boolean;
patch?: boolean;
}): Promise<UpdateRepresentation<B>>;
/**
* Like {@link CollectionTransaction#upsert}, but wrapped in its own transaction.
*
* @param {Object} record
* @return {Promise}
*/
upsert(record: B & {
id: string;
}): Promise<WithOptional<UpdateRepresentation<B>, "oldRecord">>;
/**
* Like {@link CollectionTransaction#get}, but wrapped in its own transaction.
*
* Options:
* - {Boolean} includeDeleted: Include virtually deleted records.
*
* @param {String} id
* @param {Object} options
* @return {Promise}
*/
get(id: string, options?: {
includeDeleted: boolean;
}): Promise<KintoRepresentation<B>>;
/**
* Like {@link CollectionTransaction#getAny}, but wrapped in its own transaction.
*
* @param {String} id
* @return {Promise}
*/
getAny(id: string): Promise<UndefiendKintoRepresentation<B>>;
/**
* Same as {@link Collection#delete}, but wrapped in its own transaction.
*
* Options:
* - {Boolean} virtual: When set to `true`, doesn't actually delete the record,
* update its `_status` attribute to `deleted` instead (default: true)
*
* @param {String} id The record's Id.
* @param {Object} options The options object.
* @return {Promise}
*/
delete(id: string, options?: {
virtual: boolean;
}): Promise<KintoRepresentation<B>>;
/**
* Same as {@link Collection#deleteAll}, but wrapped in its own transaction, execulding the parameter.
*
* @return {Promise}
*/
deleteAll(): Promise<KintoRepresentation<B[]>>;
/**
* The same as {@link CollectionTransaction#deleteAny}, but wrapped
* in its own transaction.
*
* @param {String} id The record's Id.
* @return {Promise}
*/
deleteAny(id: string): Promise<KintoRepresentation<B> & {
deleted: boolean;
}>;
/**
* Lists records from the local database.
*
* Params:
* - {Object} filters Filter the results (default: `{}`).
* - {String} order The order to apply (default: `-last_modified`).
*
* Options:
* - {Boolean} includeDeleted: Include virtually deleted records.
*
* @param {Object} params The filters and order to apply to the results.
* @param {Object} options The options object.
* @return {Promise}
*/
list(params?: {
filters?: {
[key: string]: any;
};
order?: string;
}, options?: {
includeDeleted: boolean;
}): Promise<KintoRepresentation<B[]>>;
/**
* Imports remote changes into the local database.
* This method is in charge of detecting the conflicts, and resolve them
* according to the specified strategy.
* @param {SyncResultObject} syncResultObject The sync result object.
* @param {Array} decodedChanges The list of changes to import in the local database.
* @param {String} strategy The {@link Collection.strategy} (default: MANUAL)
* @return {Promise}
*/
importChanges(syncResultObject: SyncResultObject, decodedChanges: any[], strategy?: string): Promise<SyncResultObject>;
/**
* Imports the responses of pushed changes into the local database.
* Basically it stores the timestamp assigned by the server into the local
* database.
* @param {SyncResultObject} syncResultObject The sync result object.
* @param {Array} toApplyLocally The list of changes to import in the local database.
* @param {Array} conflicts The list of conflicts that have to be resolved.
* @param {String} strategy The {@link Collection.strategy}.
* @return {Promise}
*/
private _applyPushedResults;
/**
* Handles synchronization conflicts according to specified strategy.
*
* @param {SyncResultObject} result The sync result object.
* @param {String} strategy The {@link Collection.strategy}.
* @return {Promise<Array<Object>>} The resolved conflicts, as an
* array of {accepted, rejected} objects
*/
private _handleConflicts;
/**
* Execute a bunch of operations in a transaction.
*
* This transaction should be atomic -- either all of its operations
* will succeed, or none will.
*
* The argument to this function is itself a function which will be
* called with a {@link CollectionTransaction}. Collection methods
* are available on this transaction, but instead of returning
* promises, they are synchronous. execute() returns a Promise whose
* value will be the return value of the provided function.
*
* Most operations will require access to the record itself, which
* must be preloaded by passing its ID in the preloadIds option.
*
* Options:
* - {Array} preloadIds: list of IDs to fetch at the beginning of
* the transaction
*
* @return {Promise} Resolves with the result of the given function
* when the transaction commits.
*/
execute<R>(doOperations: (proxy: CollectionTransaction<B>) => R, { preloadIds }?: {
preloadIds?: string[];
}): Promise<R>;
/**
* Resets the local records as if they were never synced; existing records are
* marked as newly created, deleted records are dropped.
*
* A next call to {@link Collection.sync} will thus republish the whole
* content of the local collection to the server.
*
* @return {Promise} Resolves with the number of processed records.
*/
resetSyncStatus(): Promise<number>;
/**
* Returns an object containing two lists:
*
* - `toDelete`: unsynced deleted records we can safely delete;
* - `toSync`: local updates to send to the server.
*
* @return {Promise}
*/
gatherLocalChanges(): Promise<unknown[]>;
/**
* Fetch remote changes, import them to the local database, and handle
* conflicts according to `options.strategy`. Then, updates the passed
* {@link SyncResultObject} with import results.
*
* Options:
* - {String} strategy: The selected sync strategy.
* - {String} expectedTimestamp: A timestamp to use as a "cache busting" query parameter.
* - {Array<String>} exclude: A list of record ids to exclude from pull.
* - {Object} headers: The HTTP headers to use in the request.
* - {int} retry: The number of retries to do if the HTTP request fails.
* - {int} lastModified: The timestamp to use in `?_since` query.
*
* @param {KintoClient.Collection} client Kinto client Collection instance.
* @param {SyncResultObject} syncResultObject The sync result object.
* @param {Object} options The options object.
* @return {Promise}
*/
pullChanges(client: KintoCollection, syncResultObject: SyncResultObject, options?: {
exclude?: any[];
strategy?: string;
lastModified?: number | null;
headers?: Record<string, string>;
expectedTimestamp?: string | null;
retry?: number;
}): Promise<SyncResultObject>;
applyHook(hookName: AvailableHook, payload: any): Promise<{
changes: B[];
}>;
/**
* Publish local changes to the remote server and updates the passed
* {@link SyncResultObject} with publication results.
*
* Options:
* - {String} strategy: The selected sync strategy.
* - {Object} headers: The HTTP headers to use in the request.
* - {int} retry: The number of retries to do if the HTTP request fails.
*
* @param {KintoClient.Collection} client Kinto client Collection instance.
* @param {SyncResultObject} syncResultObject The sync result object.
* @param {Object} changes The change object.
* @param {Array} changes.toDelete The list of records to delete.
* @param {Array} changes.toSync The list of records to create/update.
* @param {Object} options The options object.
* @return {Promise}
*/
pushChanges(client: KintoCollection, changes: any, syncResultObject: SyncResultObject, options?: {
strategy?: string;
headers?: Record<string, string>;
retry?: number;
}): Promise<SyncResultObject>;
/**
* Return a copy of the specified record without the local fields.
*
* @param {Object} record A record with potential local fields.
* @return {Object}
*/
cleanLocalFields(record: B): Omit<B, string>;
/**
* Resolves a conflict, updating local record according to proposed
* resolution — keeping remote record `last_modified` value as a reference for
* further batch sending.
*
* @param {Object} conflict The conflict object.
* @param {Object} resolution The proposed record.
* @return {Promise}
*/
resolve(conflict: Conflict<B>, resolution: B): Promise<KintoRepresentation<B>>;
/**
* @private
*/
private _resolveRaw;
/**
* Synchronize remote and local data. The promise will resolve with a
* {@link SyncResultObject}, though will reject:
*
* - if the server is currently backed off;
* - if the server has been detected flushed.
*
* Options:
* - {Object} headers: HTTP headers to attach to outgoing requests.
* - {String} expectedTimestamp: A timestamp to use as a "cache busting" query parameter.
* - {Number} retry: Number of retries when server fails to process the request (default: 1).
* - {Collection.strategy} strategy: See {@link Collection.strategy}.
* - {Boolean} ignoreBackoff: Force synchronization even if server is currently
* backed off.
* - {String} bucket: The remove bucket id to use (default: null)
* - {String} collection: The remove collection id to use (default: null)
* - {String} remote The remote Kinto server endpoint to use (default: null).
*
* @param {Object} options Options.
* @return {Promise}
* @throws {Error} If an invalid remote option is passed.
*/
sync(options?: CollectionSyncOptions): Promise<SyncResultObject>;
/**
* Load a list of records already synced with the remote server.
*
* The local records which are unsynced or whose timestamp is either missing
* or superior to those being loaded will be ignored.
*
* @deprecated Use {@link importBulk} instead.
* @param {Array} records The previously exported list of records to load.
* @return {Promise} with the effectively imported records.
*/
loadDump(records: (B & {
last_modified: number;
})[]): Promise<B[]>;
/**
* Load a list of records already synced with the remote server.
*
* The local records which are unsynced or whose timestamp is either missing
* or superior to those being loaded will be ignored.
*
* @param {Array} records The previously exported list of records to load.
* @return {Promise} with the effectively imported records.
*/
importBulk(records: (B & {
last_modified: number;
})[]): Promise<B[]>;
pullMetadata(client: KintoCollection, options?: {
expectedTimestamp?: string | null;
headers?: Record<string, string>;
}): Promise<{
[key: string]: unknown;
} | null>;
metadata(): Promise<unknown>;
}
/**
* A Collection-oriented wrapper for an adapter's transaction.
*
* This defines the high-level functions available on a collection.
* The collection itself offers functions of the same name. These will
* perform just one operation in its own transaction.
*/
export declare class CollectionTransaction<B extends {
id: string;
last_modified?: number;
_status?: RecordStatus;
}> {
collection: Collection<B>;
adapterTransaction: StorageProxy<B>;
private _events;
constructor(collection: Collection<B>, adapterTransaction: StorageProxy<B>);
private _queueEvent;
/**
* Emit queued events, to be called once every transaction operations have
* been executed successfully.
*/
emitEvents(): void;
/**
* Retrieve a record by its id from the local database, or
* undefined if none exists.
*
* This will also return virtually deleted records.
*
* @param {String} id
* @return {Object}
*/
getAny(id: string): UndefiendKintoRepresentation<B>;
/**
* Retrieve a record by its id from the local database.
*
* Options:
* - {Boolean} includeDeleted: Include virtually deleted records.
*
* @param {String} id
* @param {Object} options
* @return {Object}
*/
get(id: string, options?: {
includeDeleted: boolean;
}): KintoRepresentation<B>;
/**
* Deletes a record from the local database.
*
* Options:
* - {Boolean} virtual: When set to `true`, doesn't actually delete the record,
* update its `_status` attribute to `deleted` instead (default: true)
*
* @param {String} id The record's Id.
* @param {Object} options The options object.
* @return {Object}
*/
delete(id: string, options?: {
virtual: boolean;
}): KintoRepresentation<B>;
/**
* Soft delete all records from the local database.
*
* @param {Array} ids Array of non-deleted Record Ids.
* @return {Object}
*/
deleteAll(ids: string[]): KintoRepresentation<B[]>;
/**
* Deletes a record from the local database, if any exists.
* Otherwise, do nothing.
*
* @param {String} id The record's Id.
* @return {Object}
*/
deleteAny(id: string): KintoRepresentation<B> & {
deleted: boolean;
};
/**
* Adds a record to the local database, asserting that none
* already exist with this ID.
*
* @param {Object} record, which must contain an ID
* @return {Object}
*/
create(record: B): KintoRepresentation<B>;
/**
* Updates a record from the local database.
*
* Options:
* - {Boolean} synced: Sets record status to "synced" (default: false)
* - {Boolean} patch: Extends the existing record instead of overwriting it
* (default: false)
*
* @param {Object} record
* @param {Object} options
* @return {Object}
*/
update(record: B & {
id: string;
}, options?: {
synced: boolean;
patch: boolean;
}): UpdateRepresentation<B>;
/**
* Lower-level primitive for updating a record while respecting
* _status and last_modified.
*
* @param {Object} oldRecord: the record retrieved from the DB
* @param {Object} newRecord: the record to replace it with
* @return {Object}
*/
private _updateRaw;
/**
* Upsert a record into the local database.
*
* This record must have an ID.
*
* If a record with this ID already exists, it will be replaced.
* Otherwise, this record will be inserted.
*
* @param {Object} record
* @return {Object}
*/
upsert(record: B & {
id: string;
}): WithOptional<UpdateRepresentation<B>, "oldRecord">;
}