﻿// Type definitions for lokijs v1.2.5
// Project: https://github.com/techfort/LokiJS
// Definitions by: TeamworkGuy2 <https://github.com/TeamworkGuy2>
// Definitions: https://github.com/DefinitelyTyped/DefinitelyTyped

// NOTE: definition last updated (2016-3-13) based on latest code as of https://github.com/techfort/LokiJS/commit/3d2cf9546cd22556444deeabc4df314f227ecf5c

/** LokiJS
 * A lightweight document oriented javascript database
 * @author Joe Minichino <joe.minichino@gmail.com>
 */


/** Loki: The main database class
 * @constructor
 * @param {string} filename - name of the file to be saved to
 * @param {object} options - config object
 */
interface Loki extends LokiEventEmitter {
    // autosave support (disabled by default)
    autosave: boolean;
    autosaveInterval: number; // milliseconds between auto-saves
    autosaveHandle: number; // ID from setInterval(...)
    collections: LokiCollection<any>[];
    databaseVersion: number;
    engineVersion: number;
    ENV: string;/*NODEJS, CORDOVA, BROWSER*/
    events: { [id: string]: ((...args: any[]) => void)[] }; /*{
        'init': ((...args) => void)[];
        'loaded': ((...args) => void)[];
        'flushChanges': ((...args) => void)[];
        'close': ((...args) => void)[];
        'changes': ((...args) => void)[];
        'warning': ((...args) => void)[];
    };*/
    filename: string;
    options: LokiConfigureOptions;
    persistenceAdapter: LokiPersistenceInterface;
    // persistenceMethod could be 'fs', 'localStorage', or 'adapter'
    // this is optional option param, otherwise environment detection will be used
    // if user passes their own adapter we will force this method to 'adapter' later, so no need to pass method option.
    persistenceMethod: string; /*'fs', 'localStorage', 'adapter'*/
    verbose: boolean;

    new (filename: string, options: LokiConfigureOptions): Loki;

    // experimental support for browserify's abstract syntax scan to pick up dependency of indexed adapter.
    // Hopefully, once this hits npm a browserify require of lokijs should scan the main file and detect this indexed adapter reference.
    getIndexedAdapter(): LokiPersistenceInterface; // require("./loki-indexed-adapter.js")


    /** configureOptions - allows reconfiguring database options
     *
     * @param {object} options - configuration options to apply to loki db object
     * @param {boolean} initialConfig - (optional) if this is a reconfig, don't pass this
     */
    configureOptions(options: LokiConfigureOptions, initialConfig?: boolean): void;

    /** anonym() - shorthand method for quickly creating and populating an anonymous collection.
     *    This collection is not referenced internally so upon losing scope it will be garbage collected.
     *
     *    Example : var results = new loki().anonym(myDocArray).find({'age': {'$gt': 30} });
     *
     * @param {Array} docs - document array to initialize the anonymous collection with
     * @param {Array} indexesArray - (Optional) array of property names to index
     * @returns {Collection} New collection which you can query or chain
     */
    anonym<T>(docs: T | T[], indexesArray?: LokiCollectionOptions): LokiCollection<T>;

    addCollection<T>(name: string, options?: LokiCollectionOptions): LokiCollection<T>;

    loadCollection(collection: LokiCollection<any>): void;

    getCollection<T>(collectionName: string): LokiCollection<T>;

    listCollections(): { name: string; type: string; count: number }[];

    removeCollection(collectionName: string): void;

    getName(): string;

    /** serializeReplacer - used to prevent certain properties from being serialized
     */
    serializeReplacer<T>(key: "autosaveHandle", value: T): T;
    serializeReplacer<T>(key: "persistenceAdapter", value: T): T;
    serializeReplacer<T>(key: "constraints", value: T): T;
    serializeReplacer<T>(key: string, value: T): T;

    // toJson
    serialize(): string;

    // alias of serialize
    toJson(): string;

    /** loadJSON - inflates a loki database from a serialized JSON string
     *
     * @param {string} serializedDb - a serialized loki database string
     * @param {object} options - apply or override collection level settings
     */
    loadJSON(serializedDb: string, options?: { [collectionName: string]: { inflate?: (src: any, dst: any) => void; proto: any; } }): void;

    /** loadJSONObject - inflates a loki database from a JS object
     *
     * @param {object} dbObject - a serialized loki database string
     * @param {object} options - apply or override collection level settings
     */
    loadJSONObject(dbObject: Loki, options?: { [collectionName: string]: { inflate?: (src: any, dst: any) => void; proto: any; } }): void;

    /** close(callback) - emits the close event with an optional callback. Does not actually destroy the db
     * but useful from an API perspective
     */
    close(callback?: (...args: any[]) => void): void;

    /**-------------------------+
    | Changes API               |
    +--------------------------*/

    /** The Changes API enables the tracking the changes occurred in the collections since the beginning of the session,
     * so it's possible to create a differential dataset for synchronization purposes (possibly to a remote db)
     */

    /** generateChangesNotification() - takes all the changes stored in each
     * collection and creates a single array for the entire database. If an array of names
     * of collections is passed then only the included collections will be tracked.
     *
     * @param {array} optional array of collection names. No arg means all collections are processed.
     * @returns {array} array of changes
     * @see private method createChange() in Collection
     */
    generateChangesNotification(arrayOfCollectionNames?: string[]): LokiCollectionChange[];

    /** serializeChanges() - stringify changes for network transmission
     * @returns {string} string representation of the changes
     */
    serializeChanges(collectionNamesArray?: string[]): string;

    /** clearChanges() - clears all the changes in all collections.
     */
    clearChanges(): void;

    /** loadDatabase - Handles loading from file system, local storage, or adapter (indexeddb)
     *    This method utilizes loki configuration options (if provided) to determine which
     *    persistence method to use, or environment detection (if configuration was not provided).
     *
     * @param {object} options - not currently used (remove or allow overrides?)
     * @param {function} callback - (Optional) user supplied async callback / error handler
     */
    loadDatabase(options: { [collectionName: string]: { inflate?: (src: any, dst: any) => void; proto: any; } }, callback?: (err: any, data: any) => void): void;

    /** saveDatabase - Handles saving to file system, local storage, or adapter (indexeddb)
     *    This method utilizes loki configuration options (if provided) to determine which
     *    persistence method to use, or environment detection (if configuration was not provided).
     *
     * @param {object} options - not currently used (remove or allow overrides?)
     * @param {function} callback - (Optional) user supplied async callback / error handler
     */
    saveDatabase(callback?: (err: any) => void): void;

    // alias for saveDatabase
    save(callback ?: (err: any) => void): void;

    /** deleteDatabase - Handles deleting a database from file system, local
     *    storage, or adapter (indexeddb)
     *    This method utilizes loki configuration options (if provided) to determine which
     *    persistence method to use, or environment detection (if configuration was not provided).
     *
     * @param {object} options - not currently used (remove or allow overrides?)
     * @param {function} callback - user supplied async callback / error handler
     */
    deleteDatabase(options: any, callback: (err: any, data: any) => void): void;

    /** autosaveDirty - check whether any collections are 'dirty' meaning we need to save (entire) database
     * @returns {boolean} - true if database has changed since last autosave, false if not.
     */
    autosaveDirty(): boolean;

    /** autosaveClearFlags - resets dirty flags on all collections.
     *    Called from saveDatabase() after db is saved.
     */
    autosaveClearFlags(): void;

    /** autosaveEnable - begin a javascript interval to periodically save the database.
     *
     * @param {object} options - not currently used (remove or allow overrides?)
     * @param {function} callback - (Optional) user supplied async callback
     */
    autosaveEnable(options?: LokiConfigureOptions, callback?: (err: any) => void): void;

    /** autosaveDisable - stop the autosave interval timer.
     */
    autosaveDisable(): void;
}




/**
 * LokiEventEmitter is a minimalist version of EventEmitter. It enables any
 * constructor that inherits EventEmitter to emit events and trigger
 * listeners that have been added to the event through the on(event, callback) method
 */
interface LokiEventEmitter {
    /**
     * @prop Events property is a hashmap, with each property being an array of callbacks
     */
    events: { [eventName: string]: ((...args: any[]) => void)[] };

    new (): LokiEventEmitter;

    /**
     * @prop asyncListeners - boolean determines whether or not the callbacks associated with each event
     * should happen in an async fashion or not
     * Default is false, which means events are synchronous
     */
    asyncListeners: boolean;

    /**
     * @prop on(eventName, listener) - adds a listener to the queue of callbacks associated to an event
     * @returns {int} the index of the callback in the array of listeners for a particular event
     */
    on<U extends (...args: any[]) => void>(eventName: string, listener: U): U;

    /**
     * @propt emit(eventName, data) - emits a particular event
     * with the option of passing optional parameters which are going to be processed by the callback
     * provided signatures match (i.e. if passing emit(event, arg0, arg1) the listener should take two parameters)
     * @param {string} eventName - the name of the event
     * @param {object} data - optional object passed with the event
     */
    emit(eventName: string, data?: any): void;

    /**
     * @prop remove() - removes the listener at position 'index' from the event 'eventName'
     */
    removeListener(eventName: string, listener: (...args: any[]) => void): void;
}




/*------------------+
| PERSISTENCE       |
-------------------*/

/** there are two build in persistence adapters for internal use
 * fs             for use in Nodejs type environments
 * localStorage   for use in browser environment
 * defined as helper classes here so its easy and clean to use
 */

interface LokiPersistenceInterface {
    loadDatabase(dbname: string, callback: (dataOrErr: string | Error) => void): void;
    saveDatabase(dbname: string, dbstring: string, callback: (resOrErr: void | Error) => void): void;
    deleteDatabase(dbname: string, callback?: (resOrErr: void | Error) => void): void;
    // optional
    mode?: string; // 'reference'
    // filename may seem redundant but loadDatabase will need to expect this same filename
    exportDatabase?(filename: string, param: any, callback?: (err: any) => void): void;
}


/** constructor for fs
 */
interface LokiFsAdapter extends LokiPersistenceInterface {
    fs: any; //require('fs');

    /** loadDatabase() - Load data from file, will throw an error if the file does not exist
     * @param {string} dbname - the filename of the database to load
     * @param {function} callback - the callback to handle the result
     */
    loadDatabase(dbname: string, callback: (err: Error, data: string) => void): void;

    /** saveDatabase() - save data to file, will throw an error if the file can't be saved
     * might want to expand this to avoid dataloss on partial save
     * @param {string} dbname - the filename of the database to load
     * @param {function} callback - the callback to handle the result
     */
    saveDatabase(dbname: string, dbstring: string, callback: (err: any) => void): void;

    /** deleteDatabase() - delete the database file, will throw an error if the
     * file can't be deleted
     * @param {string} dbname - the filename of the database to delete
     * @param {function} callback - the callback to handle the result
     */
    deleteDatabase(dbname: string, callback: (resOrErr: void | Error) => void): void;
}


/** constructor for local storage
 */
interface LokiLocalStorageAdapter extends LokiPersistenceInterface {

    /** loadDatabase() - Load data from localstorage
     * @param {string} dbname - the name of the database to load
     * @param {function} callback - the callback to handle the result
     */
    loadDatabase(dbname: string, callback: (dataOrErr: string | Error) => void): void;

    /** saveDatabase() - save data to localstorage, will throw an error if the file can't be saved
     * might want to expand this to avoid dataloss on partial save
     * @param {string} dbname - the filename of the database to load
     * @param {function} callback - the callback to handle the result
     */
    saveDatabase(dbname: string, dbstring: string, callback: (resOrErr: void | Error) => void): void;

    /** deleteDatabase() - delete the database from localstorage, will throw an error if it
     * can't be deleted
     * @param {string} dbname - the filename of the database to delete
     * @param {function} callback - the callback to handle the result
     */
    deleteDatabase(dbname: string, callback: (resOrErr: void | Error) => void): void;
}




/** Resultset class allowing chainable queries.  Intended to be instanced internally.
 *    Collection.find(), Collection.where(), and Collection.chain() instantiate this.
 *
 *    Example:
 *    mycollection.chain()
 *      .find({ 'doors' : 4 })
 *      .where(function(obj) { return obj.name === 'Toyota' })
 *      .data();
 */
interface LokiResultset<E> {
    // retain reference to collection we are querying against
    collection: LokiCollection<E>;
    filterInitialized: boolean;
    filteredrows: string[]; // technically number[] (e.g. = Object.keys(this.collection.data))
    options: LokiResultsetOptions<E>;
    searchIsChained: boolean;


    /**
     * @constructor
     * @param {Collection} collection - The collection which this Resultset will query against.
     * @param {Object} options - Object containing one or more options.
     * @param {string} options.queryObj - Optional mongo-style query object to initialize resultset with.
     * @param {function} options.queryFunc - Optional javascript filter function to initialize resultset with.
     * @param {bool} options.firstOnly - Optional boolean used by collection.findOne().
     */
    new <E>(collection: LokiCollection<E>, options: LokiResultsetOptions<E>): LokiResultset<E> | E[];

    /** reset() - Reset the resultset to its initial state.
     *
     * @returns {Resultset} Reference to this resultset, for future chain operations.
     */
    reset(): LokiResultset<E>;

    /** toJSON() - Override of toJSON to avoid circular references
     */
    toJSON(): LokiResultset<E>;

    /** limit() - Allows you to limit the number of documents passed to next chain operation.
     *    A resultset copy() is made to avoid altering original resultset.
     *
     * @param {int} qty - The number of documents to return.
     * @returns {Resultset} Returns a copy of the resultset, limited by qty, for subsequent chain ops.
     */
    limit(qty: number): LokiResultset<E>;

    /** offset() - Used for skipping 'pos' number of documents in the resultset.
     *
     * @param {int} pos - Number of documents to skip; all preceding documents are filtered out.
     * @returns {Resultset} Returns a copy of the resultset, containing docs starting at 'pos' for subsequent chain ops.
     */
    offset(pos: number): LokiResultset<E>;

    /** copy() - To support reuse of resultset in branched query situations.
     *
     * @returns {Resultset} Returns a copy of the resultset (set) but the underlying document references will be the same.
     */
    copy(): LokiResultset<E>;
    // alias of copy()
    branch(): LokiResultset<E>;

    /**
     * transform() - executes a named collection transform or raw array of transform steps against the resultset.
     *
     * @param transform {string|array} : (Optional) name of collection transform or raw transform array
     * @param parameters {object} : (Optional) object property hash of parameters, if the transform requires them.
     * @returns {Resultset} : either (this) resultset or a clone of of this resultset (depending on steps)
     */
    transform(transform?: string | any[], parameters?: any): LokiResultset<E>;

    /** sort() - User supplied compare function is provided two documents to compare. (chainable)
     *    Example:
     *    rslt.sort(function(obj1, obj2) {
     *      if (obj1.name === obj2.name) return 0;
     *      if (obj1.name > obj2.name) return 1;
     *      if (obj1.name < obj2.name) return -1;
     *    });
     *
     * @param {function} comparefun - A javascript compare function used for sorting.
     * @returns {Resultset} Reference to this resultset, sorted, for future chain operations.
     */
    sort(comparefun: (a: E, b: E) => number): LokiResultset<E>;

    /** simplesort() - Simpler, loose evaluation for user to sort based on a property name. (chainable)
     *
     * @param {string} propname - name of property to sort by.
     * @param {bool} isdesc - (Optional) If true, the property will be sorted in descending order
     * @returns {Resultset} Reference to this resultset, sorted, for future chain operations.
     */
    simplesort(propname: string, isdesc?: boolean): LokiResultset<E>;

    /** compoundsort() - Allows sorting a resultset based on multiple columns.
     *    Example : rs.compoundsort(['age', 'name']); to sort by age and then name (both ascending)
     *    Example : rs.compoundsort(['age', ['name', true]); to sort by age (ascending) and then by name (descending)
     *
     * @param {array} properties - array of property names or subarray of [propertyname, isdesc] used evaluate sort order
     * @returns {Resultset} Reference to this resultset, sorted, for future chain operations.
     */
    compoundsort(properties: ([string, boolean] | [string])[]): LokiResultset<E>;

    /** calculateRange() - Binary Search utility method to find range/segment of values matching criteria.
     *    this is used for collection.find() and first find filter of resultset/dynview
     *    slightly different than get() binary search in that get() hones in on 1 value,
     *    but we have to hone in on many (range)
     * @param {string} op - operation, such as $eq
     * @param {string} prop - name of property to calculate range for
     * @param {object} val - value to use for range calculation.
     * @returns {array} [start, end] index array positions
     */
    calculateRange(op: "$eq", prop: string, val: any): [number/*start*/, number/*end*/];
    calculateRange(op: "$dteq", prop: string, val: any): [number/*start*/, number/*end*/];
    calculateRange(op: "$gt", prop: string, val: any): [number/*start*/, number/*end*/];
    calculateRange(op: "$gte", prop: string, val: any): [number/*start*/, number/*end*/];
    calculateRange(op: "$lt", prop: string, val: any): [number/*start*/, number/*end*/];
    calculateRange(op: "$lte", prop: string, val: any): [number/*start*/, number/*end*/];
    calculateRange(op: string, prop: string, val: any): [number/*start*/, number/*end*/];

    /** findOr() - oversee the operation of OR'ed query expressions.
     *    OR'ed expression evaluation runs each expression individually against the full collection,
     *    and finally does a set OR on each expression's results.
     *    Each evaluation can utilize a binary index to prevent multiple linear array scans.
     *
     * @param {array} expressionArray - array of expressions
     * @returns {Resultset} this resultset for further chain ops.
     */
    findOr(expressionArray: LokiQuery[]): LokiResultset<E>;
    $or(expressionArray: LokiQuery[]): LokiResultset<E>;

    /** findAnd() - oversee the operation of AND'ed query expressions.
     *    AND'ed expression evaluation runs each expression progressively against the full collection,
     *    internally utilizing existing chained resultset functionality.
     *    Only the first filter can utilize a binary index.
     *
     * @param {array} expressionArray - array of expressions
     * @returns {Resultset} this resultset for further chain ops.
     */
    findAnd(expressionArray: LokiQuery[]): LokiResultset<E>;
    $and(expressionArray: LokiQuery[]): LokiResultset<E>;

    /** find() - Used for querying via a mongo-style query object.
     *
     * @param {object} query - A mongo-style query object used for filtering current results.
     * @param {boolean} firstOnly - (Optional) Used by collection.findOne()
     * @returns {Resultset} this resultset for further chain ops.
     */
    //find(query: LokiQuery, firstOnly: boolean): E;
    //find(query?: any, firstOnly?: boolean): E[];
    find(query: LokiQuery, firstOnly?: boolean): LokiResultset<E>;

    /** where() - Used for filtering via a javascript filter function.
     *
     * @param {function} fun - A javascript function used for filtering current results by.
     * @returns {Resultset} this resultset for further chain ops.
     */
    where(fun: (obj: E) => boolean): LokiResultset<E>;

    /** count() - returns the number of documents in the resultset.
     *
     * @returns {number} The number of documents in the resultset.
     */
    count(): number;

    /** data() - Terminates the chain and returns array of filtered documents
     *
     * @param options {object} : allows specifying 'forceClones' and 'forceCloneMethod' options.
     *    options :
     *      forceClones {boolean} : Allows forcing the return of cloned objects even when
     *        the collection is not configured for clone object.
     *      forceCloneMethod {string} : Allows overriding the default or collection specified cloning method.
     *        Possible values include 'parse-stringify', 'jquery-extend-deep', and 'shallow'
     *
     * @returns {array} Array of documents in the resultset
     */
    data(options?: { forceClones?: string; forceCloneMethod?: string; }): E[];

    /** update() - used to run an update operation on all documents currently in the resultset.
     *
     * @param {function} updateFunction - User supplied updateFunction(obj) will be executed for each document object.
     * @returns {Resultset} this resultset for further chain ops.
     */
    update(updateFunction: (obj: E) => void): LokiResultset<E>;

    /** remove() - removes all document objects which are currently in resultset from collection (as well as resultset)
     *
     * @returns {Resultset} this (empty) resultset for further chain ops.
     */
    remove(): LokiResultset<E>;

    /** mapReduce() - data transformation via user supplied functions
     *
     * @param {function} mapFunction - this function accepts a single document for you to transform and return
     * @param {function} reduceFunction - this function accepts many (array of map outputs) and returns single value
     * @returns The output of your reduceFunction
     */
    mapReduce<T, U>(mapFunction: (value: E, index: number, array: E[]) => T, reduceFunction: (array: T[]) => U): U;

    /** eqJoin() - Left joining two sets of data. Join keys can be defined or calculated properties
     * eqJoin expects the right join key values to be unique.  Otherwise left data will be joined on the last joinData object with that key
     * @param {Array} joinData - Data array to join to.
     * @param {String,function} leftJoinKey - Property name in this result set to join on or a function to produce a value to join on
     * @param {String,function} rightJoinKey - Property name in the joinData to join on or a function to produce a value to join on
     * @param {function} (optional) mapFun - A function that receives each matching pair and maps them into output objects - function(left,right){return joinedObject}
     * @returns {Resultset} A resultset with data in the format [{left: leftObj, right: rightObj}]
     */
    eqJoin<T>(joinData: T[] | LokiResultset<T>, leftJoinKey: string | ((obj: E) => string), rightJoinKey: string | ((obj: T) => string)): LokiResultset<{ left: E; right: T; }>;
    eqJoin<T, U>(joinData: T[] | LokiResultset<T>, leftJoinKey: string | ((obj: E) => string), rightJoinKey: string | ((obj: T) => string), mapFun?: (a: E, b: T) => U): LokiResultset<U>;

    map<T>(mapFun: (currentValue: E, index: number, array: E[]) => T): LokiResultset<T>;
}




/** DynamicView class is a versatile 'live' view class which can have filters and sorts applied.
 *    Collection.addDynamicView(name) instantiates this DynamicView object and notifies it
 *    whenever documents are add/updated/removed so it can remain up-to-date. (chainable)
 *
 *    Examples:
 *    var mydv = mycollection.addDynamicView('test');  // default is non-persistent
 *    mydv.applyWhere(function(obj) { return obj.name === 'Toyota'; });
 *    mydv.applyFind({ 'doors' : 4 });
 *    var results = mydv.data();
 *
 */
interface LokiDynamicView<E> extends LokiEventEmitter {
    cachedresultset: LokiResultset<E>;
    collection: LokiCollection<E>;
    events: { [id: string]: ((...args: any[]) => void)[] }; /*{
        'rebuild': ((...args) => void)[];
    };*/
    // keep ordered filter pipeline
    filterPipeline: LokiFilter<E>[];
    minRebuildInterval: number;
    name: string;
    options: LokiDynamicViewOptions;
    persistent: boolean;
    rebuildPending: boolean;
    resultset: LokiResultset<E>;
    resultdata: E[];
    resultsdirty: boolean;
    // sorting member variables, we only support one active search, applied using applySort() or applySimpleSort()
    sortFunction: (a: E, b: E) => number;
    sortCriteria: ([string, boolean] | [string])[];
    sortDirty: boolean;
    sortPriority: string; // 'persistentSortPriority', 'passive' (will defer the sort phase until they call data(). most efficient overall), 'active' (will sort async whenever next idle. prioritizes read speeds)

    /**
     * @constructor
     * @param {Collection} collection - A reference to the collection to work against
     * @param {string} name - The name of this dynamic view
     * @param {object} options - (Optional) Pass in object with 'persistent' and/or 'sortPriority' options.
     */
    new <E>(collection: LokiCollection<E>, name: string, options?: LokiDynamicViewOptions): LokiDynamicView<E>;

    /** rematerialize() - intended for use immediately after deserialization (loading)
     *    This will clear out and reapply filterPipeline ops, recreating the view.
     *    Since where filters do not persist correctly, this method allows
     *    restoring the view to state where user can re-apply those where filters.
     *
     * @param {Object} options - (Optional) allows specification of 'removeWhereFilters' option
     * @returns {DynamicView} This dynamic view for further chained ops.
     */
    rematerialize(options?: { removeWhereFilters?: boolean; }): LokiDynamicView<E>;

    /** branchResultset() - Makes a copy of the internal resultset for branched queries.
     *    Unlike this dynamic view, the branched resultset will not be 'live' updated,
     *    so your branched query should be immediately resolved and not held for future evaluation.
     *
     * @param {string|array} transform: Optional name of collection transform, or an array of transform steps
     * @param {object} parameters: optional parameters (if optional transform requires them)
     * @returns {Resultset} A copy of the internal resultset for branched queries.
     */
    branchResultset(transform?: string | any[], parameters?: any): LokiResultset<E>;

    /** toJSON() - Override of toJSON to avoid circular references
     */
    toJSON(): LokiDynamicView<E>;

    /** removeFilters() - Used to clear pipeline and reset dynamic view to initial state.
     *     Existing options should be retained.
     */
    removeFilters(): void;

    /** applySort() - Used to apply a sort to the dynamic view
     *
     * @param {function} comparefun - a javascript compare function used for sorting
     * @returns {DynamicView} this DynamicView object, for further chain ops.
     */
    applySort(comparefun: (a: E, b: E) => number): LokiDynamicView<E>;

    /** applySimpleSort() - Used to specify a property used for view translation.
     *
     * @param {string} propname - Name of property by which to sort.
     * @param {boolean} isdesc - (Optional) If true, the sort will be in descending order.
     * @returns {DynamicView} this DynamicView object, for further chain ops.
     */
    applySimpleSort(propname: string, isdesc?: boolean): LokiDynamicView<E>;

    /** applySortCriteria() - Allows sorting a resultset based on multiple columns.
     *    Example : dv.applySortCriteria(['age', 'name']); to sort by age and then name (both ascending)
     *    Example : dv.applySortCriteria(['age', ['name', true]); to sort by age (ascending) and then by name (descending)
     *    Example : dv.applySortCriteria(['age', true], ['name', true]); to sort by age (descending) and then by name (descending)
     *
     * @param {array} properties - array of property names or subarray of [propertyname, isdesc] used evaluate sort order
     * @returns {DynamicView} Reference to this DynamicView, sorted, for future chain operations.
     */
    applySortCriteria(criteria: ([string, boolean] | [string])[]): LokiDynamicView<E>;

    /** startTransaction() - marks the beginning of a transaction.
     *
     * @returns {DynamicView} this DynamicView object, for further chain ops.
     */
    startTransaction(): LokiDynamicView<E>;

    /** commit() - commits a transaction.
     *
     * @returns {DynamicView} this DynamicView object, for further chain ops.
     */
    commit(): LokiDynamicView<E>;

    /** rollback() - rolls back a transaction.
     *
     * @returns {DynamicView} this DynamicView object, for further chain ops.
     */
    rollback(): LokiDynamicView<E>;

    /** Implementation detail.
     * _indexOfFilterWithId() - Find the index of a filter in the pipeline, by that filter's ID.
     *
     * @param {string|number} uid - The unique ID of the filter.
     * @returns {number}: index of the referenced filter in the pipeline; -1 if not found.
     */
    _indexOfFilterWithId(uid: string | number): number;

    /** Implementation detail.
     * _addFilter() - Add the filter object to the end of view's filter pipeline and apply the filter to the resultset.
     *
     * @param {object} filter - The filter object. Refer to applyFilter() for extra details.
     */
    _addFilter(filter: LokiFilter<E>): void;

    /** reapplyFilters() - Reapply all the filters in the current pipeline.
     *
     * @returns {DynamicView} this DynamicView object, for further chain ops.
     */
    reapplyFilters(): LokiDynamicView<E>;

    /** applyFilter() - Adds or updates a filter in the DynamicView filter pipeline
     *
     * @param {object} filter - A filter object to add to the pipeline.
     *    The object is in the format { 'type': filter_type, 'val', filter_param, 'uid', optional_filter_id }
     * @returns {DynamicView} this DynamicView object, for further chain ops.
     */
    applyFilter(filter: LokiFilter<E>): LokiDynamicView<E>;

    /** applyFind() - Adds or updates a mongo-style query option in the DynamicView filter pipeline
     *
     * @param {object} query - A mongo-style query object to apply to pipeline
     * @param {string|number} uid - Optional: The unique ID of this filter, to reference it in the future.
     * @returns {DynamicView} this DynamicView object, for further chain ops.
     */
    applyFind(query: LokiQuery, uid?: string | number): LokiDynamicView<E>;

    /** applyWhere() - Adds or updates a javascript filter function in the DynamicView filter pipeline
     *
     * @param {function} fun - A javascript filter function to apply to pipeline
     * @param {string|number} uid - Optional: The unique ID of this filter, to reference it in the future.
     * @returns {DynamicView} this DynamicView object, for further chain ops.
     */
    applyWhere(fun: (obj: E) => boolean, uid?: string | number): LokiDynamicView<E>;

    /** removeFilter() - Remove the specified filter from the DynamicView filter pipeline
     *
     * @param {string|number} uid - The unique ID of the filter to be removed.
     * @returns {DynamicView} this DynamicView object, for further chain ops.
     */
    removeFilter(uid: string | number): LokiDynamicView<E>;

    /** count() - returns the number of documents representing the current DynamicView contents.
     *
     * @returns {number} The number of documents representing the current DynamicView contents.
     */
    count(): number;

    /** data() - resolves and pending filtering and sorting, then returns document array as result.
     *
     * @returns {array} An array of documents representing the current DynamicView contents.
     */
    data(): E[];

    /** queueRebuildEvent() - When the view is not sorted we may still wish to be notified of rebuild events.
     *     This event will throttle and queue a single rebuild event when batches of updates affect the view.
     */
    queueRebuildEvent(): void;

    /** queueSortPhase : If the view is sorted we will throttle sorting to either :
     *    (1) passive - when the user calls data(), or
     *    (2) active - once they stop updating and yield js thread control
     */
    queueSortPhase(): void;

    /** performSortPhase() - invoked synchronously or asynchronously to perform final sort phase (if needed)
     */
    performSortPhase(options?: { suppressRebuildEvent?: boolean; }): void;

    /** evaluateDocument() - internal method for (re)evaluating document inclusion.
     *    Called by : collection.insert() and collection.update().
     *
     * @param {int} objIndex - index of document to (re)run through filter pipeline.
     * @param {bool} isNew - true if the document was just added to the collection.
     */
    evaluateDocument(objIndex: number, isNew?: boolean): void;

    /** removeDocument() - internal function called on collection.delete()
     */
    removeDocument(objIndex: number): void;

    /** mapReduce() - data transformation via user supplied functions
     *
     * @param {function} mapFunction - this function accepts a single document for you to transform and return
     * @param {function} reduceFunction - this function accepts many (array of map outputs) and returns single value
     * @returns The output of your reduceFunction
     */
    mapReduce<T, U>(mapFunction: (item: E, index: number, array: E[]) => T, reduceFunction: (array: T[]) => U): U;
}




/** Collection class that handles documents of same type
 */
interface LokiCollection<E> extends LokiEventEmitter {
    // option to observe objects and update them automatically, ignored if Object.observe is not supported
    autoupdate: boolean;
    // option to make event listeners async, default is sync
    asyncListeners: boolean;
    binaryIndices: { [id: string]: { name: string; dirty: boolean; values: number[] } };

    cachedIndex: number[];
    cachedBinaryIndex: { [id: string]: { name: string; dirty: boolean; values: number[] } };
    cachedData: E[];
    // changes are tracked by collection and aggregated by the db
    changes: LokiCollectionChange[];
    // default clone method (if enabled) is parse-stringify
    cloneMethod: string; // 'parse-stringify'
    // options to clone objects when inserting them
    cloneObjects: boolean;
    console: {
        log: () => void;
        warn: () => void;
        error: () => void;
    };
    constraints: {
        unique: { [id: string]: LokiUniqueIndex<E> };
        exact: { [id: string]: LokiExactIndex<E> };
    };
    data: E[];
    // in autosave scenarios we will use collection level dirty flags to determine whether save is needed.
    // currently, if any collection is dirty we will autosave the whole database if autosave is configured.
    // defaulting to true since this is called from addCollection and adding a collection should trigger save
    dirty: boolean;
    // disable track changes
    disableChangesApi: boolean;
    DynamicViews: LokiDynamicView<E>[];
    events: { [id: string]: ((...args: any[]) => void)[] }; /*{
        'insert': ((...args) => void)[];
        'update': ((...args) => void)[];
        'pre-insert': ((...args) => void)[];
        'pre-update': ((...args) => void)[];
        'close': ((...args) => void)[];
        'flushbuffer': ((...args) => void)[];
        'error': ((...args) => void)[];
        'delete': ((...args) => void)[];
        'warning': ((...args) => void)[];
    };*/
    idIndex: number[];
    maxId: number; // currentMaxId - change manually at your own peril!
    name: string;
    // is collection transactional
    transactional: boolean;
    objType: string;
    // transforms will be used to store frequently used query chains as a series of steps
    // which itself can be stored along with the database.
    transforms: { [id: string]: any };
    // unique contraints contain duplicate object references, so they are not persisted.
    // we will keep track of properties which have unique contraint applied here, and regenerate on load
    uniqueNames: string[];

    options: LokiCollectionOptions;

    // option to activate a cleaner daemon - clears "aged" documents at set intervals.
    ttl: {
        age: number;
        ttlInterval: number;
        daemon: number;
    };

    /** Collection class that handles documents of same type
     * @constructor
     * @param {string} collection name
     * @param {array} array of property names to be indicized
     * @param {object} configuration object
     */
    new <E>(name: string, options?: LokiCollectionOptions): LokiCollection<E>;

    getChanges(): LokiCollectionChange[];

    setChangesApi(enabled: boolean): void;

    flushChanges(): void;

    observerCallback: (changes: { object: any }[]) => void;

    addAutoUpdateObserver(object: any): void;

    removeAutoUpdateObserver(object: any): void;

    addTransform(name: string, transform: any): void;

    setTransform(name: string, transform: any): void;

    removeTransform(name: string): void;

    byExample(template: any): { '$and': any[] };

    findObject(template: any): E;

    findObjects(template: any): E[];

    /*----------------------------+
    | TTL daemon                  |
    +----------------------------*/
    ttlDaemonFuncGen(): () => void;

    setTTL(age: number, interval: number): void;

    /*----------------------------+
    | INDEXING                    |
    +----------------------------*/

    /**
     * create a row filter that covers all documents in the collection
     */
    prepareFullDocIndex(): number[];

    /** Ensure binary index on a certain field
     */
    ensureIndex(property: string, force?: boolean): void;

    ensureUniqueIndex(field: string): LokiUniqueIndex<E>;

    /** Ensure all binary indices
     */
    ensureAllIndexes(force?: boolean): void;

    flagBinaryIndexesDirty(): void;

    flagBinaryIndexDirty(index: string): void;

    count(query?: LokiQuery): number;

    /** Rebuild idIndex
     */
    ensureId(): void;

    /** Rebuild idIndex async with callback - useful for background syncing with a remote server
     */
    ensureIdAsync(callback: () => void): void;

    /** Each collection maintains a list of DynamicViews associated with it
     **/
    addDynamicView(name: string, options?: LokiDynamicViewOptions): LokiDynamicView<E>;

    removeDynamicView(name: string): void;

    getDynamicView(name: string): LokiDynamicView<E>;

    /** find and update: pass a filtering function to select elements to be updated
     * and apply the updatefunctino to those elements iteratively
     */
    findAndUpdate(filterFunction: (obj: E) => boolean, updateFunction: (obj: E) => E): void;

    /** generate document method - ensure object(s) have meta properties, clone it if necessary, etc.
     * @param {object} doc: the document to be inserted (or an array of objects)
     * @returns document or documents (if passed an array of objects)
     */
    insert(doc: E): E;
    insert(doc: E[]): E[];

    /** generate document method - ensure object has meta properties, clone it if necessary, etc.
     * @param {object} the document to be inserted
     * @returns document or 'undefined' if there was a problem inserting it
     */
    insertOne(doc: E): E;

    clear(): void;

    /** Update method
     */
    update(doc: E): E;
    update(doc: E[]): void;

    /** Add object to collection
     */
    add(obj: E): E;

    removeWhere(query: ((obj: E) => boolean) | LokiQuery): void;

    removeDataOnly(): void;

    /** delete wrapped
     */
    remove(doc: E): E;
    remove(doc: number): E;
    remove(doc: number[]): void;
    remove(doc: E[]): void;

    /*---------------------+
    | Finding methods     |
    +----------------------*/

    /** Get by Id - faster than other methods because of the searching algorithm
     */
    get(id: number | string): E;
    get(id: number | string, returnPosition?: boolean): E | [E, number];

    by(field: string): (value: any) => E;
    by(field: string, value: string): E;

    /** Find one object by index property, by property equal to value
     */
    findOne(query: LokiQuery): E;

    /** Chain method, used for beginning a series of chained find() and/or view() operations
     * on a collection.
     *
     * @param {array} transform : Ordered array of transform step objects similar to chain
     * @param {object} parameters: Object containing properties representing parameters to substitute
     * @returns {Resultset} : (or data array if any map or join functions where called)
     */
    chain(transform?: string | any[], parameters?: any): LokiResultset<E>;

    /**
     * Find method, api is similar to mongodb except for now it only supports one search parameter.
     * for more complex queries use view() and storeView()
     */
    find(): E[];
    find(query: LokiQuery): LokiResultset<E>;

    /** Find object by unindexed field by property equal to value,
     * simply iterates and returns the first element matching the query
     */
    findOneUnindexed(prop: string, value: any): E;

    /** Transaction methods */

    /** start the transation */
    startTransaction(): void;

    /** commit the transation */
    commit(): void;

    /** roll back the transation */
    rollback(): void;

    // async executor. This is only to enable callbacks at the end of the execution.
    async(fun: () => void, callback: () => void): void;

    /** Create view function - filter
     */
    where(fun: (obj: E) => boolean): LokiResultset<E>;

    /** Map Reduce
     */
    mapReduce<U, V>(mapFunction: (item: E, index: number, array: E[]) => U, reduceFunction: (array: U[]) => V): V;

    /** eqJoin - Join two collections on specified properties
     */
    eqJoin<T>(joinData: T[] | LokiResultset<T>, leftJoinProp: string | ((obj: E) => string), rightJoinProp: string | ((obj: T) => string)): LokiResultset<{ left: E; right: T; }>;
    eqJoin<T, U>(joinData: T[] | LokiResultset<T>, leftJoinProp: string | ((obj: E) => string), rightJoinProp: string | ((obj: T) => string), mapFun?: (a: E, b: T) => U): LokiResultset<U>;

    /* ------ STAGING API -------- */
    /** stages: a map of uniquely identified 'stages', which hold copies of objects to be
     * manipulated without affecting the data in the original collection
     */
    stages: { [id: string]: any };

    /** create a stage and/or retrieve it
     */
    getStage(name: string): E[];

    /** a collection of objects recording the changes applied through a commmitStage
     */
    commitLog: {
        timestamp: number; // timestamp (i.e. new Date().getTime())
        message: any;
        data: E;
    }[];

    /** create a copy of an object and insert it into a stage
     */
    stage(stageName: string, obj: E): E;

    /** re-attach all objects to the original collection, so indexes and views can be rebuilt
     * then create a message to be inserted in the commitlog
     */
    commitStage(stageName: string, message: any): void;

    no_op(): void;

    extract(field: string): any[];

    max(field: string): number;

    min(field: string): number;

    maxRecord(field: string): { index: number; value: any; };

    minRecord(field: string): { index: number; value: any; };

    extractNumerical(field: string): number[];

    avg(field: string): number;

    stdDev(field: string): number;

    mode(field: string): string | number;

    median(field: string): number;
}




/** comparison operators
 * a is the value in the collection
 * b is the query value
 */
interface LokiOps {
    $eq(a: any, b: any): boolean;
    $ne(a: any, b: any): boolean;
    $dteq(a: any, b: any): boolean;
    $gt(a: any, b: any): boolean;
    $gte(a: any, b: any): boolean;
    $lt(a: any, b: any): boolean;
    $lte(a: any, b: any): boolean;
    $in(a: any, b: { indexOf: (value: any) => boolean }): boolean;
    $nin(a: any, b: { indexOf: (value: any) => boolean }): boolean;
    $keyin(a: string, b: any): boolean;
    $nkeyin(a: string, b: any): boolean;
    $definedin(a: any, b: any): boolean;
    $undefinedin(a: any, b: any): boolean;
    $regex(a: any, b: RegExp | { test: (str: string) => boolean }): boolean;
    $containsString(a: string | any, b: string): boolean;
    $containsNone(a: any, b: any): boolean;
    $containsAny(a: any, b: any | any[]): boolean;
    $contains(a: any, b: any | any[]): boolean;
    $type(a: any, b: any): boolean;
    $size(a: any, b: any): boolean;
    $len(a: any, b: any): boolean;
    // field-level logical operators
    // a is the value in the collection
    // b is the nested query operation (for '$not')
    //   or an array of nested query operations (for '$and' and '$or')
    $not(a: any, b: any): boolean;
    $and(a: any, b: any[]): boolean;
    $or(a: any, b: any[]): boolean;
}


interface LokiKeyValueStore<K, V> {
    keys: K[];
    values: V[];

    sort(a: any, b: any): number;
    setSort(fun: (a: K, b: K) => number): void;
    bs(): LokiBSonSort<K>;
    set(key: K, value: V): void;
    get(key: K): V;
}


interface LokiUniqueIndex<E> {
    field: string;
    keyMap: { [id: string]: E };
    lokiMap: { [id: number]: any };

    new <E>(uniqueField: string): LokiUniqueIndex<E>;

    set(obj: E): void;
    get(key: string): E;
    byId(id: number): E;
    update(obj: E): void;
    remove(key: string): void;
    clear(): void;
}


interface LokiExactIndex<E> {
    index: { [id: string]: E[] };
    field: string;

    new <E>(exactField: string): LokiExactIndex<E>

    /** add the value you want returned to the key in the index */
    set(key: string, val: E): void;
    /** remove the value from the index, if the value was the last one, remove the key */
    remove(key: string, val: E): void;
    /** get the values related to the key, could be more than one */
    get(key: string): E[];
    /** clear will zap the index */
    clear(key?: any): void;
}


interface LokiSortedIndex<K, V> {
    field: string;
    keys: K[];
    values: V[][];

    new <K, V>(sortedField: string): LokiSortedIndex<K, V>;

    // set the default sort
    sort(a: any, b: any): number;
    bs(): LokiBSonSort<any>;
    // and allow override of the default sort
    setSort(fun: (a: any, b: any) => number): void;
    // add the value you want returned  to the key in the index
    set(key: K, value: V): void;
    // get all values which have a key == the given key
    get(key: K): V[];
    // get all values which have a key < the given key
    getLt(key: K): V[];
    // get all values which have a key > the given key
    getGt(key: K): V[];
    // get all vals from start to end
    getAll(key: K, start: number, end: number): V[];
    // just in case someone wants to do something smart with ranges
    getPos(key: K): { found: boolean; index: number; };
    // remove the value from the index, if the value was the last one, remove the key
    remove(key: K, value: V): void;
    // clear will zap the index
    clear(): void;
}


interface LokiConfigureOptions {
    adapter?: LokiPersistenceInterface;
    autoload?: boolean;
    autoloadCallback?: (dataOrErr: any | Error) => void;
    autosave?: boolean;
    autosaveCallback?: (err: any) => void;
    autosaveInterval?: number; // milliseconds between auto-saves
    env?: string; /*'NODEJS', 'BROWSER', 'CORDOVA'*/
    persistenceMethod?: string; /*'fs', 'localStorage', 'adapter'*/
    verbose?: boolean;
}


interface LokiCollectionOptions {
    asyncListeners?: boolean;
    autoupdate?: boolean;
    clone?: boolean;
    cloneMethod?: string;
    disableChangesApi?: boolean;
    exact?: string[];
    indices?: string | string[];
    transactional?: boolean;
    unique?: string | string[];
}


interface LokiDynamicViewOptions {
    minRebuildInterval?: number;
    persistent?: boolean;
    sortPriority: string; /*'active', 'passive'*/
}


interface LokiResultsetOptions<E> {
    firstOnly?: boolean;
    queryObj?: LokiQuery;
    queryFunc?: (item: E) => boolean;
}


interface LokiQuery {
}


interface LokiFilter<E> {
    type: string; /*'find', 'where'*/
    val: LokiQuery | ((obj: E, index: number, array: E[]) => boolean);
    uid: number | string;
}


interface LokiElementMetaData {
    created: number; // unix style timestamp (i.e. new Date().getTime())
    revision: number;
}


interface LokiCollectionChange {
    name: string;
    operation: string;/*'I', 'R', 'U'*/
    obj: any;
}


interface LokiBSonSort<T> {
    (fun: (a: T, b: T) => number): (array: T[], item: T) => { found: boolean; index: number; };
}


/*
interface LokiUtils {
    copyProperties(src: any, dest: any): void;

    // used to recursively scan hierarchical transform step object for param substitution
    resolveTransformObject<U>(subObj: U, params: any, depth?: number): U;

    // top level utility to resolve an entire (single) transform (array of steps) for parameter substitution
    resolveTransformParams<U>(transform: U[], params: any): U[];
}

// Sort helper that support null and undefined
declare function ltHelper(prop1: any, prop2: any, equal?: boolean): boolean;

declare function gtHelper(prop1: any, prop2: any, equal?: boolean): boolean;

declare function sortHelper(prop1: any, prop2: any, desc?: boolean): number;

declare function doQueryOp(val: any, op: any): boolean;

declare function containsCheckFn<T>(a: T[], b): (curr: T) => boolean;
declare function containsCheckFn(a: string, b): (curr: string) => boolean;
declare function containsCheckFn<T>(a: T, b): (curr: string) => boolean;
*/

/** General utils, including statistical functions
 */
/*
declare function isDeepProperty(field: string): boolean;

declare function parseBase10(num: string | number): number;

declare function isNotUndefined(obj: any): boolean;

declare function add(a: string | number, b: string | number): number;

declare function sub(a: string | number, b: string | number): number;

declare function median(values: number[]): number;

declare function average(array: (string | number)[]);

declare function standardDeviation(values: (string | number)[]): number;

declare function deepProperty(obj: any, property: string, isDeep?: boolean): any;

declare function binarySearch<U>(array: U[], item: U, fun: (a: U, b: U) => number): { found: boolean; index: number; };

// compoundeval() - helper function for compoundsort(), performing individual object comparisons
//
// @param {array} properties - array of property names, in order, by which to evaluate sort order
// @param {object} obj1 - first object to compare
// @param {object} obj2 - second object to compare
// @returns {integer} 0, -1, or 1 to designate if identical (sortwise) or which should be first
declare function compoundeval(properties: ([string, boolean] | [string])[], obj1: any, obj2: any): number;

// dotSubScan - helper function used for dot notation queries.
declare function dotSubScan<V>(root: any | any[], propPath: string[], fun: (root, value: V) => boolean, value: V): boolean;

// making indexing opt-in... our range function knows how to deal with these ops :
//var indexedOpsList = ['$eq', '$dteq', '$gt', '$gte', '$lt', '$lte'];

declare function clone<U>(data: U, method?: string): U; // stage: 'parse-stringify', 'jquery-extend-deep', 'shallow'

declare function cloneObjectArray<U>(objarray: U[], method?: string): U; // stage: 'parse-stringify', 'jquery-extend-deep', 'shallow'

declare function localStorageAvailable(): boolean;
*/




/* ======== loki-indexed-adapter.js ======== */
interface LokiIndexedAdapter {
    app: string;
    catalog: LokiCatalog;

    /** IndexedAdapter - Loki persistence adapter class for indexedDb.
     *     This class fulfills abstract adapter interface which can be applied to other storage methods
     *     Utilizes the included LokiCatalog app/key/value database for actual database persistence.
     * @param {string} appname - Application name context can be used to distinguish subdomains or just 'loki'
     */
    new (appname: string): LokiIndexedAdapter;

    /** checkAvailability - used to check if adapter is available
     * @returns {boolean} true if indexeddb is available, false if not.
     */
    checkAvailability(): boolean;

    /** loadDatabase() - Retrieves a serialized db string from the catalog.
     * @param {string} dbname - the name of the database to retrieve.
     * @param {function} callback - callback should accept string param containing serialized db string.
     */
    loadDatabase(dbname: string, callback?: (data: any) => void): void;

    // alias for loadDatabase
    loadKey(dbname: string, callback?: (data: any) => void): void;

    /** saveDatabase() - Saves a serialized db to the catalog.
     * @param {string} dbname - the name to give the serialized database within the catalog.
     * @param {string} dbstring - the serialized db string to save.
     * @param {function} callback - (Optional) callback passed obj.success with true or false
     */
    saveDatabase(dbname: string, dbstring: string, callback?: (err: Error | void) => void): void;

    // alias for saveDatabase
    saveKey(dbname: string, dbstring: string, callback?: (err: Error | void) => void): void;

    /** deleteDatabase() - Deletes a serialized db from the catalog.
     * @param {string} dbname - the name of the database to delete from the catalog.
     */
    deleteDatabase(dbname: string): void;

    // alias for deleteDatabase
    deleteKey(dbname: string): void;

    /** getDatabaseList() - Retrieves object array of catalog entries for current app.
     * @param {function} callback - should accept array of database names in the catalog for current app.
     */
    getDatabaseList(callback: (names: string[]) => void): void;

    // alias for getDatabaseList
    getKeyList(callback: (names: string[]) => void): void;

    /** getCatalogSummary - allows retrieval of list of all keys in catalog along with size
     * @param {function} callback - (Optional) callback to accept result array.
     */
    getCatalogSummary(callback: (entries: { app: string; key: string; size: number; }) => void): void;
}


/** LokiCatalog - underlying App/Key/Value catalog persistence
 *   This non-interface class implements the actual persistence.
 *   Used by the IndexedAdapter class.
 */
interface LokiCatalog {
    db: IDBDatabase;

    new (callback: (cat: LokiCatalog) => void): LokiCatalog;

    initializeLokiCatalog(callback: (cat: LokiCatalog) => void): void;

    getAppKey(app: string, key: string, callback: (resObj: any) => void): void;

    getAppKeyById<T>(id: any, callback: (result: any, data: T) => void, data: T): void;

    setAppKey(app: string, key: string, val: any, callback: (res: { success: boolean }) => void): void;

    deleteAppKey(id: any, callback: (res: { success: boolean; }) => void): void;

    getAppKeys(app: string, callback: (data: any[]) => void): void;

    // Hide 'cursoring' and return array of { id: id, key: key }
    getAllKeys(callback: (data: any[]) => void): void;
}
/* ======== END loki-indexed-adapter.js ======== */



/* ======== loki-crypted-file-adapter.js ======== */
/**
 * @file lokiCryptedFileAdapter.js
 * @author Hans Klunder <Hans.Klunder@bigfoot.com>
 */

/** require libs */
//var fs = require('fs');
//var cryptoLib = require('crypto');
//var isError = require('util').isError;

/* The default Loki File adapter uses plain text JSON files. This adapter crypts the database string and wraps the result
* in a JSON including enough info to be able to decrypt it (except for the 'secret' of course !)
*
* The idea is that the 'secret' does not reside in your source code but is supplied by some other source (e.g. the user in node-webkit)
*
* The idea + encrypt/decrypt routines are borrowed from  https://github.com/mmoulton/krypt/blob/develop/lib/krypt.js
* not using the krypt module to avoid third party dependencies
*/
interface LokiCryptedFileAdapter {
    secret: string;

    /** The constructor is automatically called on `require` , see examples below
     * @constructor
     */
    new (): LokiCryptedFileAdapter;

    /** setSecret() - set the secret to be used during encryption and decryption
     *
     * @param {string} secret - the secret to be used
     */
    setSecret(secret: string): void;

    /** loadDatabase() - Retrieves a serialized db string from the catalog.
     *
     *  @example
      // LOAD
        var cryptedFileAdapter = require('./lokiCryptedFileAdapter');
        cryptedFileAdapter.setSecret('mySecret'); // you should change 'mySecret' to something supplied by the user
        var db = new loki('test.crypted', { adapter: cryptedFileAdapter }); //you can use any name, not just '*.crypted'
        db.loadDatabase(function(result) {
            console.log('done');
        });
     *
     * @param {string} dbname - the name of the database to retrieve.
     * @param {function} callback - callback should accept string param containing serialized db string.
     */
    loadDatabase(dbname: string, callback: (decryptedDataOrErr: string | any) => void): void;

    /**
     *
     @example
      // SAVE : will save database in 'test.crypted'
        var cryptedFileAdapter = require('./lokiCryptedFileAdapter');
        cryptedFileAdapter.setSecret('mySecret'); // you should change 'mySecret' to something supplied by the user
        var loki=require('lokijs');
        var db = new loki('test.crypted',{ adapter: cryptedFileAdapter }); //you can use any name, not just '*.crypted'
        var coll = db.addCollection('testColl');
        coll.insert({test: 'val'});
        db.saveDatabase();  // could pass callback if needed for async complete

     @example
      // if you have the krypt module installed you can use:
        krypt --decrypt test.crypted --secret mySecret
      to view the contents of the database

     * saveDatabase() - Saves a serialized db to the catalog.
     *
     * @param {string} dbname - the name to give the serialized database within the catalog.
     * @param {string} dbstring - the serialized db string to save.
     * @param {function} callback - (Optional) callback passed obj.success with true or false
     */
    saveDatabase(dbname: string, dbstring: string, callback: (err: any) => void): void;
}


interface LokiCryptedFileAdapterEncryptResult {
    cipher: string;
    keyDerivation: string;
    keyLength: number;
    iterations: number;
    iv: string;
    salt: string;
    value: string;
}
/* ======== END loki-crypted-file-adapter.js ======== */




/* ======== loki-angular.js ======== */
/* introduces a angular module named lokijs that returns the 'lokijs' module
    var module = angular.module('lokijs', [])
        .factory('Loki', function Loki() {
            return lokijs;
        });
    return module;
*/
/* ======== END loki-angular.js ======== */




/* ======== jquery-sync-adapter.js ======== */

/** LokiJS JquerySyncAdapter
 * A remote sync adapter example for LokiJS
 * @author Joe Minichino <joe.minichino@gmail.com>
 */

/** this adapter assumes an object options is passed,
 * containing the following properties:
 * ajaxLib: jquery or compatible ajax library
 * save: { url: the url to save to, dataType [optional]: json|xml|etc., type [optional]: POST|GET|PUT}
 * load: { url: the url to load from, dataType [optional]: json|xml| etc., type [optional]: POST|GET|PUT }
 */
interface LokiJquerySyncAdapter {
    options: LokiJquerySyncAdapterOptions

    new (options: LokiJquerySyncAdapterOptions): LokiJquerySyncAdapter;

    saveDatabase(name: string, data: any, callback?: (data: any, textStatus: string, xhr: XMLHttpRequest) => any): void;

    loadDatabase(name: string, callback?: (data: any, textStatus: string, xhr: XMLHttpRequest) => any): void;
}


interface LokiJquerySyncAdapterOptions {
    ajaxLib: { ajax(options: any): any; };
    save: {
        url: any;
        type?: string; /*'GET', 'POST, 'DELETE', etc.*/
        dataType?: string; /*'json', 'xml', etc.*/
    };
    load: {
        url: any;
        type?: string; /*'GET', 'POST, 'DELETE', etc.*/
        dataType?: string; /*'json', 'xml', etc.*/
    };
}


interface LokiJquerySyncAdapterError extends Error {
    name: string; // "JquerySyncAdapterError"
    message: any;

    new (message: any): LokiJquerySyncAdapterError;
}
/* ======== END jquery-sync-adapter.js ======== */


declare var LokiCryptedFileAdapterConstructor: {
    new (): LokiCryptedFileAdapter;
}

declare module "lokiCryptedFileAdapter" {
    export = LokiCryptedFileAdapterConstructor;
}


declare var LokiIndexedAdapterConstructor: {
    new (filename: string): LokiIndexedAdapter;
}

declare module "loki-indexed-adapter" {
    export = LokiIndexedAdapterConstructor;
}


declare var LokiConstructor: {
    new (filename: string, options?: LokiConfigureOptions): Loki;
    LokiOps: LokiOps;
    Collection: LokiCollection<any>;
    KeyValueStore: LokiKeyValueStore<any, any>;
}

declare module "lokijs" {
    export = LokiConstructor;
}
