import { INDEX_ID_KEY } from './IndexedStorage';
import type { CreateTypeObject, IndexedQuery, IndexedStorage, IndexTypeCollection, StringKey, TypeObject, ValueType } from './IndexedStorage';
import type { KeyValueStorage } from './KeyValueStorage';
/**
 * Key used to link to child objects in a {@link WrappedIndexedStorage}.
 */
export type VirtualKey<TChild> = TChild extends string ? `**${TChild}**` : never;
/**
 * Object stored in the wrapped {@link KeyValueStorage} in a {@link WrappedIndexedStorage}.
 */
export type VirtualObject = {
    [key: VirtualKey<string>]: Record<string, VirtualObject>;
    [key: string]: unknown;
    [INDEX_ID_KEY]: string;
};
/**
 * A parent/child relation description in a {@link WrappedIndexedStorage}.
 */
export type IndexRelation<TTypes> = {
    parent: {
        key: VirtualKey<string>;
        type: StringKey<TTypes>;
    };
    child: {
        key: string;
        type: StringKey<TTypes>;
    };
};
/**
 * An {@link IndexedStorage} that makes use of 2 {@link KeyValueStorage}s to implement the interface.
 * Due to being limited by key/value storages, there are some restrictions on the allowed type definitions:
 *
 *   * There needs to be exactly 1 type with no references to other types.
 *   * All other types need to have exactly 1 reference to another type.
 *   * Types can't reference each other to create a cycle of references.
 *
 * All of the above to create a tree-like structure of references.
 * Such a tree is then stored in one of the storages.
 * The other storage is used to store all indexes that are used to find the correct tree object when solving queries.
 */
export declare class WrappedIndexedStorage<T extends IndexTypeCollection<T>> implements IndexedStorage<T> {
    protected readonly logger: import("global-logger-factory").Logger<unknown>;
    private readonly valueStorage;
    private readonly indexStorage;
    /**
     * For every type, the keys on which an index tracks the values and which root object they are contained in.
     * All types for which a `defineType` call was made will have a key in this object.
     * For all types that are not the root, there will always be an index on their ID value.
     */
    private readonly indexes;
    /**
     * Keeps track of type validation.
     * If true the defined types create a valid structure that can be used.
     */
    private validDefinition;
    /**
     * The variable in which the root type is stored.
     * A separate getter is used to always return the value
     * so the potential `undefined` does not have to be taken into account.
     */
    private rootTypeVar;
    /**
     * All parent/child relations between all types in the storage,
     * including the keys in both types that are used to reference each other.
     */
    private readonly relations;
    constructor(valueStorage: KeyValueStorage<string, VirtualObject>, indexStorage: KeyValueStorage<string, string[]>);
    defineType<TType extends StringKey<T>>(type: TType, description: T[TType]): Promise<void>;
    createIndex<TType extends StringKey<T>>(type: TType, key: StringKey<T[TType]>): Promise<void>;
    has<TType extends StringKey<T>>(type: TType, id: string): Promise<boolean>;
    get<TType extends StringKey<T>>(type: TType, id: string): Promise<TypeObject<T[TType]> | undefined>;
    create<TType extends StringKey<T>>(type: TType, value: CreateTypeObject<T[TType]>): Promise<TypeObject<T[TType]>>;
    set<TType extends StringKey<T>>(type: TType, value: TypeObject<T[TType]>): Promise<void>;
    setField<TType extends StringKey<T>, TKey extends StringKey<T[TType]>>(type: TType, id: string, key: TKey, value: ValueType<T[TType][TKey]>): Promise<void>;
    delete<TType extends StringKey<T>>(type: TType, id: string): Promise<void>;
    findIds<TType extends StringKey<T>>(type: TType, query: IndexedQuery<T, TType>): Promise<string[]>;
    find<TType extends StringKey<T>>(type: TType, query: IndexedQuery<T, TType>): Promise<(TypeObject<T[TType]>)[]>;
    entries<TType extends StringKey<T>>(type: TType): AsyncIterableIterator<TypeObject<T[TType]>>;
    /**
     * Converts a {@link VirtualObject} into a {@link TypeObject}.
     * To be used when outputting results.
     */
    protected toTypeObject<TType extends StringKey<T>>(type: TType, obj: VirtualObject): TypeObject<T[TType]>;
    /**
     * The root type for this storage.
     * Use this instead of rootTypeVar to prevent having to check for `undefined`.
     * This value will always be defined if the type definitions have been validated.
     */
    protected get rootType(): string;
    /**
     * Finds the root object that contains the requested type/id combination.
     */
    protected getRoot<TType extends StringKey<T>>(type: TType, id: string): Promise<VirtualObject | undefined>;
    /**
     * Returns the sequence of virtual keys that need to be accessed to reach the given type, starting from the root.
     */
    protected getPathToType(type: string): VirtualKey<string>[];
    /**
     * Finds all records that can be found in the given object by following the given path of virtual keys.
     */
    protected getPathRecords(obj: VirtualObject, path: VirtualKey<string>[]): Record<string, VirtualObject>[];
    /**
     * Finds all objects in the provided object that can be found by following the provided path of virtual keys.
     */
    protected getChildObjects(obj: VirtualObject, path: VirtualKey<string>[]): VirtualObject[];
    /**
     * Finds the record in the given object that contains the given type/id combination.
     * This function assumes it was already verified through an index that this object contains the given combination.
     */
    protected getContainingRecord<TType extends StringKey<T>>(rootObj: VirtualObject, type: TType, id: string): Record<string, VirtualObject>;
    /**
     * Replaces an object of the given type.
     * The identifier in the value is used to determine which object.
     */
    protected updateValue<TType extends StringKey<T>>(type: TType, value: TypeObject<T[TType]>, replace: true): Promise<void>;
    /**
     * Replaces part of an object of the given type with the given partial value.
     * The identifier in the value is used to determine which object.
     */
    protected updateValue<TType extends StringKey<T>>(type: TType, partial: Partial<TypeObject<T[TType]>> & {
        [INDEX_ID_KEY]: string;
    }, replace: false): Promise<void>;
    /**
     * Returns all relations where the given type is the parent.
     */
    protected getChildRelations<TType extends StringKey<T>>(type: TType): IndexRelation<T>[];
    /**
     * Returns the relation where the given type is the child.
     * Will return `undefined` for the root type as that one doesn't have a parent.
     */
    protected getParentRelation<TType extends StringKey<T>>(type: TType): IndexRelation<T> | undefined;
    /**
     * Makes sure the defined types fulfill all the requirements necessary for types on this storage.
     * Will throw an error if this is not the case.
     * This should be called before doing any data interactions.
     * Stores success in a variable so future calls are instantaneous.
     */
    protected validateDefinition(type: string): void;
    /**
     * Finds the IDs of all root objects that contain objects of the given type matching the given query
     * by making use of the indexes applicable to the keys in the query.
     * This function only looks at the keys in the query with primitive values,
     * object values in the query referencing parent objects are not considered.
     * Similarly, only indexes are used, keys without index are also ignored.
     *
     * If an array of root IDs is provided as input,
     * the result will be an intersection of this array and the found identifiers.
     *
     * If the result is an empty array, it means that there is no valid identifier matching the query,
     * while an `undefined` result means there is no index matching any of the query keys,
     * so a result can't be determined.
     */
    protected findIndexedRoots<TType extends StringKey<T>>(type: TType, match: IndexedQuery<T, TType>, rootIds?: string[]): Promise<string[] | undefined>;
    /**
     * Finds all objects of the given type matching the query.
     * The `rootIds` array can be used to restrict the IDs of root objects to look at,
     * which is relevant for the recursive calls the function does.
     *
     * Will throw an error if there is no index that can be used to solve the query.
     */
    protected solveQuery<TType extends StringKey<T>>(type: TType, query: IndexedQuery<T, TType>, rootIds?: string[]): Promise<VirtualObject[]>;
    /**
     * Generate the key used to store the index in the index storage.
     */
    protected getIndexKey(type: string, key: string, value: string | number): string;
    /**
     * Update all indexes for an object of the given type, and all its children.
     */
    protected updateDeepTypeIndex<TType extends StringKey<T>>(type: TType, rootId: string, oldObj: VirtualObject, newObj?: VirtualObject): Promise<void>;
    /**
     * Updates all indexes for an object of the given type.
     */
    protected updateTypeIndex<TType extends StringKey<T>>(type: TType, rootId: string, oldObj?: VirtualObject, newObj?: VirtualObject): Promise<void>;
    /**
     * Updates the index for a specific key of an object of the given type.
     */
    protected updateKeyIndex(type: string, key: string, value: string, rootId: string, add: boolean): Promise<void>;
}
