type OrderBy<T> = [key: keyof T, order: 'asc' | 'desc'];
type SortOptions<T> = ((keyof T) | OrderBy<T>)[];
type DBEvent<T> = {
    /**
     * 'L' alias of load. fires when data loaded
     */
    type: 'L';
} | {
    /**
     * 'C' alias of change. fires on any changes happened like (insert/update/delete) after some time (debounced)
     */
    type: 'C';
} | {
    /**
     * 'I' fires on row/record insert
     */
    type: 'I';
    payload: [T];
} | {
    /**
     * 'U' fires on row/record update
     */
    type: 'U';
    payload: [doc: T, oldDoc: T];
} | {
    /**
     * 'D' fires on row/record delete
     */
    type: 'D';
    payload: [T];
} | {
    /**
     * 'Q' alias of Quit. fires on database clone
     */
    type: 'Q';
};
type Pretify<T> = {
    [K in keyof T]: T[K];
} & unknown;
type MaybePartial<T> = T | Partial<T>;
interface BaseQueryBuilder<T extends Record<any, any>, SecondaryBuilder> {
    /**
     * return rows where field value is eqaul to given value
     * @example
     * pd.select().eq('name', 'Apple').data()
     */
    eq: <K extends keyof T>(field: K, value: T[K]) => SecondaryBuilder & BaseQueryBuilder<T, SecondaryBuilder>;
    /**
     * return rows where field value is in array of values
     * @throws if values are not in array
     * @example
     * pd.select().nin('category', ['Fruit', 'Vegetable']).data()
     */
    in: <K extends keyof T>(field: K, values: T[K][]) => SecondaryBuilder & BaseQueryBuilder<T, SecondaryBuilder>;
    /**
     * return rows where field value is not in array of values
     * @throws if values are not in array
     * @example
     * pd.select().nin('name', ['Apple', 'Banana']).data()
     */
    nin: <K extends keyof T>(field: K, values: T[K][]) => SecondaryBuilder & BaseQueryBuilder<T, SecondaryBuilder>;
    /**
     * field value is between of 2 value, within a given range
     * @throws if values are not in array and the array is not length of 2
     * @example
     * pd.select().between('price', [140, 200]).data()
     */
    between: <K extends keyof T>(field: K, values: [T[K], T[K]]) => SecondaryBuilder & BaseQueryBuilder<T, SecondaryBuilder>;
    /**
     * field value is not between of 2 value, not within a given range
     * @throws if values are not in array and the array is not length of 2
     * @example
     * pd.select().nbetween('price', [120, 400]).data()
     */
    nbetween: <K extends keyof T>(field: K, values: [T[K], T[K]]) => SecondaryBuilder & BaseQueryBuilder<T, SecondaryBuilder>;
    /**
     * return rows where field value is not eqaul to given value
     * @example
     * pd.select().neq('name', 'Apple').data()
     */
    neq: <K extends keyof T>(field: K, value: T[K]) => SecondaryBuilder & BaseQueryBuilder<T, SecondaryBuilder>;
    /**
     * return rows where field value is greater than to given value
     * @example
     * pd.select().gt('price', 150).data()
     */
    gt: <K extends keyof T>(field: K, value: T[K]) => SecondaryBuilder & BaseQueryBuilder<T, SecondaryBuilder>;
    /**
     * return rows where field value is greater than and eqaul to given value.
     * rows are order by the given field in ascending order
     * @example
     * pd.select().gte('price', 100).data()
     */
    gte: <K extends keyof T>(field: K, value: T[K]) => SecondaryBuilder & BaseQueryBuilder<T, SecondaryBuilder>;
    /**
     * return rows where field value is lower than to given value
     * @example
     * pd.select().lt('price', 120).data()
     */
    lt: <K extends keyof T>(field: K, value: T[K]) => SecondaryBuilder & BaseQueryBuilder<T, SecondaryBuilder>;
    /**
     * return rows where field value is lower than and eqaul to given value.
     * rows are order by the given field in ascending order
     * @example
     * pd.select().lte('price', 120).data()
     */
    lte: <K extends keyof T>(field: K, value: T[K]) => SecondaryBuilder & BaseQueryBuilder<T, SecondaryBuilder>;
}
interface QueryBuilder<T extends Record<any, any>, SecondaryBuilder> {
    /**
     * used to tell the starting point and length to return rows from a result set
     * @example
     * pd.select().range(2, 10).data()
     */
    range: (offset: number, count?: number) => SecondaryBuilder & Omit<QueryBuilder<T, SecondaryBuilder>, 'range'>;
    /**
     * used to sort data with single or multiple keys
     *
     * @example
     * pd.select().orderBy('name', 'price').data()
     * pd.select().orderBy('name', ['price', 'desc']).data()
     */
    orderBy: (opt: SortOptions<T>) => SecondaryBuilder & Omit<QueryBuilder<T, SecondaryBuilder>, 'orderBy'>;
}
interface SelectBaseQueryBuilder<T extends Record<any, any>, Fields extends readonly (keyof T)[] | []> {
    /**
     * returns the first filtered row
     * @example
     * pd.select().eq('id', 2).single()
     * pd.select('name', 'price').in('id', [2, 3, 8]).single()
     */
    single: () => Pretify<Fields extends [] ? T : Pretify<Pick<T, Fields[number]>>>;
    /**
     * returns all the filtered rows
     * @example
     * pd.select('name', 'price').in('id', [2, 3, 8]).data()
     */
    data: () => Pretify<Fields extends [] ? T[] : Pretify<Pick<T, Fields[number]>[]>>;
    /**
     * count of the filtered rows/docs/records
     * @example
     * pd.select().between('price', [8, 12]).count()
     */
    count: () => number;
}
type SelectQueryBuilder<T extends Record<any, any>, Fields extends readonly (keyof T)[] | []> = Pretify<SelectBaseQueryBuilder<T, Fields> & BaseQueryBuilder<T, SelectBaseQueryBuilder<T, Fields> & QueryBuilder<T, SelectBaseQueryBuilder<T, Fields>>> & QueryBuilder<T, SelectBaseQueryBuilder<T, Fields>>>;
interface WhereBaseQueryBuilder<T extends Record<any, any>> {
    /**
     * update all the docs with the given doc keys
     * @returns all updated docs if passed true as the param
     * @example
     * pd.where().in('id', [2, 3, 8]).update({ price: 113 })
     */
    update: (data: MaybePartial<T>) => T[];
    /**
     * delete all the filtered docs/rows
     * @returns all removed docs
     * @example
     * pd.where().between('id', [8, 10]).delete()
     */
    delete: () => T[];
}
type WhereQueryBuilder<T extends Record<any, any>> = Pretify<WhereBaseQueryBuilder<T> & BaseQueryBuilder<T, WhereBaseQueryBuilder<T>>>;

declare class EvEmit<T> {
    /**
     * Events listners
    */
    private events;
    /**
     * used to emit change event after a fixed time of last change event
    */
    private cEv;
    /**
     * @param cTime Change emit fire throttle time
    */
    constructor(cTime: number);
    /**
     * set change event listener fire throttle time
     * @param cTime Change emit fire throttle time
    */
    setCEVTime(cTime: number): void;
    /**
     * add event listner to the given event
     * @param ev event name 'L': 'load', 'C': 'change', 'I': 'insert', 'U': 'insert', 'D': 'delete', 'Q': 'quit/close'.
     * @param fn Listner/Handler
     * ```
     * 'L' alias of load. fires when data loaded
     * 'C' alias of change. fires on any changes happened like (insert/update/delete) after some time (debounced)
     * 'I' fires on row/record insert
     * 'U' fires on row/record update
     * 'D' fires on row/record delete
     * 'Q' alias of Quit. fires on database clone
     * ```
     * -----
     * @example
     * pd.on('I', (ev, doc) => { console.log(ev, doc) }) // 'I', { id: 3, name: 'Orange', price: 20, category: 'Fruit' } // on insert event
     * pd.on('C', (evs)=>{}) // on change event with list of changes
     * pd.on('L', (ev)=>{ console.log('database loaded') }) // on load event
     * pd.on('Q', (ev)=>{}) // on quit event
    */
    on<Type extends DBEvent<T>['type'] | DBEvent<T>['type'][], Ev extends Type extends any[] ? Type[number] : Type>(event: Type, fn: ((...args: Extract<DBEvent<T>, {
        type: Ev;
    }> extends {
        payload: infer Payload;
    } ? [event: Ev, data: Payload extends any[] ? Payload : [Payload]] : [event: Ev]) => void)): typeof fn;
    protected offAll(): void;
    /**
     * remove event listner from the given event
     * @param ev event name 'L': 'load', 'C': 'change', 'I': 'insert', 'U': 'insert', 'D': 'delete', 'Q': 'quit/close'.
     * @param fn Listner/Handler
     * @example
     * pd.off('Q', (ev)=>{})
    */
    off<Type extends DBEvent<T>['type'] | DBEvent<T>['type'][], Ev extends Type extends any[] ? Type[number] : Type>(event: Type, fn: ((...args: Extract<DBEvent<T>, {
        type: Ev;
    }> extends {
        payload: infer Payload;
    } ? [event: Ev, data: Payload extends any[] ? Payload : [Payload]] : [event: Ev]) => void)): typeof fn;
    /**
     * emit events
     * @param ev event name 'L': 'load', 'C': 'change', 'I': 'insert', 'U': 'update', 'D': 'delete', 'Q': 'quit/close'.
     * @param data data payload to emit
     * @example
     * pd.emit('I', { id: 3, name: 'Orange', price: 20, category: 'Fruit' }) // emit insert event
     * pd.emit('C', 'I', 'U', 'D') // emit change event with list of changes
     * pd.emit('L') // emit load event
     * pd.emit('Q') // emit quit event
    */
    emit<Type extends DBEvent<T>['type']>(...args: Extract<DBEvent<T>, {
        type: Type;
    }> extends {
        payload: infer Payload;
    } ? [event: Type, ...data: Payload extends any[] ? Payload : [Payload]] : [event: Type]): void;
}

declare function deepClone<T>(o: T): T;

/**
 * -----------------------------------------------------
 * A tiny in-memory javascript database with indexing and filters.
 *
 * @author Praveen yadav
 * @see https://github.com/pixiedevpraveen/pixiedb/tree/master/README.md
 * ---
 * @example
 * const pd = new PixieDb('id', ['price', 'category'], products) // data is optional can be load after using the load method
 * const byId = pd.select().eq('id', 2).single() // { id: 2, name: 'Banana', price: 10, category: 'Fruit' }
 * const allByName = pd.select().eq('name', 'Apple').orderBy(['name', ['price', 'desc']]).data() // [{ id: 1, name: 'Apple', price: 10, category: 'Fruit' }, ...]
 */
declare class PixieDb<T extends Record<any, any>, Key extends keyof T> extends EvEmit<T> {
    #private;
    /**
     * primary key or unique key for the database
    */
    readonly key: Key;
    /**
     * method to clone data
     * default clone method is JSON stringify and parse
     */
    cloneMethod: typeof deepClone;
    /**
     * @param key primary key or unique key for the database (key should be primitive).
     * @param indexes list of index names or for unique indexes { name: 'nameOfIndex', unique: true }
     * @param data data list/array to load (with clone)
    */
    constructor(key: Key, indexes?: Array<keyof T | {
        name: keyof T;
        unique: boolean;
    }>, data?: T[]);
    /**
     * used to perform select after complex filtering
     * @example
     * pd.select(['name', 'price']).eq('category', 'Fruit').data() // [{ name: 'Apple', price: 10 }, { name: 'Banana', price: 10 }, ...]
     * pd.select().eq('category', 'Fruit').orderBy('name', ['price', 'desc']).data() // [{ id: 1, name: 'Apple', price: 10, category: 'Fruit' }, { id: 2, name: 'Banana', price: 10, category: 'Fruit' }, ...]
    */
    select<Fields extends readonly (keyof T)[]>(fields?: Fields): SelectQueryBuilder<T, Fields>;
    /**
     * used to perform delete/update with complex filtering
     * @example
     * pd.where().eq('category', 'Fruit').delete() // delete all fruit products
     * pd.where().eq('category', 'Fruit').update({ price: 20 }) // update all fruit products price to 20
    */
    where(): WhereQueryBuilder<T>;
    /**
     * Insert data or array of data into database
     * @example
     * pd.insert({ id: 3, name: 'Orange', price: 20, category: 'Fruit' }) // insert single record
     * pd.insert([{ id: 3, name: 'Orange', price: 20, category: 'Fruit' }, { id: 4, name: 'Mango', price: 30, category: 'Fruit' }]) // insert multiple records
     * @param docs data or array of data to insert
     * @param upsert set true to update if found
     * @param silent true to not emit events default false
     * @param clone false to not clone data before insert
    */
    insert<Doc extends T | T[]>(docs: Doc, { silent, clone, upsert }?: {
        silent?: boolean;
        clone?: boolean;
        upsert?: boolean;
    }): Doc extends any[] ? T[] : (T | undefined);
    /**
     * @param data data list/array to load (without clone)
     * @param clear true to clear existing data
    */
    load(data: T[], clear?: boolean): void;
    /**
     * get doc using key (primary key/unique id)
     * @example
     * const getByKey = pd.get(2)  // { id: 2, name: 'Banana', price: 10, category: 'Fruit' }
     *
     * @param {T[Key]} key key to get data by
     */
    get(key: T[Key]): T | undefined;
    /**
     * @param clone
     * clone the returning rows (don't modify values if pass clone as false)
     * default `true`
     * @example
     * const allData = pd.data() // [{ id: 1, name: 'Apple', price: 10, category: 'Fruit' }, ...]
    */
    data(clone?: boolean): T[];
    /**
     * get all indexes names (including unique and key)
    */
    get indexes(): (keyof T)[];
    /**
     * tell is the key is an unique index name
     * @param k name of the index
    */
    isUniqIdx(k: keyof T): boolean;
    /**
     * to close/quit/terminate the database
     * @param silent true to not emit events default false
    */
    close(silent?: boolean): void;
    /**
     * return JSON of all data without cloning, key and index names
     * @example
     * const json = pd.toJSON() // { key: 'id', indexes: ['price', 'category', {name: 'id', unique: true}], data: [{ id: 1, name: 'Apple', price: 10, category: 'Fruit' }, ...] }
    */
    toJSON(): {
        key: Key;
        indexes: (keyof T)[];
        data: T[];
    };
}

export { PixieDb };
