import type { BaseDBEntity, NonNegativeInteger, ObjectWithId, StringMap, Unsaved } from '@naturalcycles/js-lib/types';
import type { JsonSchema } from '@naturalcycles/nodejs-lib/ajv';
import type { Pipeline } from '@naturalcycles/nodejs-lib/stream';
import type { CommonDBTransactionOptions, RunQueryResult } from '../db.model.js';
import type { DBQuery } from '../query/dbQuery.js';
import { RunnableDBQuery } from '../query/dbQuery.js';
import type { CommonDaoCfg, CommonDaoCreateOptions, CommonDaoOptions, CommonDaoPatchByIdOptions, CommonDaoPatchOptions, CommonDaoReadOptions, CommonDaoSaveBatchOptions, CommonDaoSaveOptions, CommonDaoStreamDeleteOptions, CommonDaoStreamOptions, CommonDaoStreamSaveOptions } from './common.dao.model.js';
import { CommonDaoTransaction } from './commonDaoTransaction.js';
/**
 * Lowest common denominator API between supported Databases.
 *
 * BM = Backend model (optimized for API access)
 * DBM = Database model (logical representation, before compression)
 * TM = Transport model (optimized to be sent over the wire)
 *
 * Note: When auto-compression is enabled, the physical storage format differs from DBM.
 * Compression/decompression is handled transparently at the storage boundary.
 */
export declare class CommonDao<BM extends BaseDBEntity, DBM extends BaseDBEntity = BM, ID extends string = BM['id']> {
    cfg: CommonDaoCfg<BM, DBM, ID>;
    constructor(cfg: CommonDaoCfg<BM, DBM, ID>);
    create(part?: Partial<BM>, opt?: CommonDaoOptions): BM;
    requireById(id: ID, opt?: CommonDaoReadOptions): Promise<BM>;
    requireByIdAsDBM(id: ID, opt?: CommonDaoReadOptions): Promise<DBM>;
    getByIdOrEmpty(id: ID, part?: Partial<BM>, opt?: CommonDaoReadOptions): Promise<BM>;
    getById(id?: ID | null, opt?: CommonDaoReadOptions): Promise<BM | null>;
    getByIdAsDBM(id?: ID | null, opt?: CommonDaoReadOptions): Promise<DBM | null>;
    getByIds(ids: ID[], opt?: CommonDaoReadOptions): Promise<BM[]>;
    getByIdsAsDBM(ids: ID[], opt?: CommonDaoReadOptions): Promise<DBM[]>;
    private loadByIds;
    getBy(by: keyof DBM, value: any, limit?: number, opt?: CommonDaoReadOptions): Promise<BM[]>;
    getOneBy(by: keyof DBM, value: any, opt?: CommonDaoReadOptions): Promise<BM | null>;
    getAll(opt?: CommonDaoReadOptions): Promise<BM[]>;
    /**
     * Pass `table` to override table
     */
    query(table?: string): RunnableDBQuery<BM, DBM, ID>;
    runQuery(q: DBQuery<DBM>, opt?: CommonDaoReadOptions): Promise<BM[]>;
    runQuerySingleColumn<T = any>(q: DBQuery<DBM>, opt?: CommonDaoReadOptions): Promise<T[]>;
    /**
     * Convenience method that runs multiple queries in parallel and then merges their results together.
     * Does deduplication by id.
     * Order is not guaranteed, as queries run in parallel.
     */
    runUnionQueries(queries: DBQuery<DBM>[], opt?: CommonDaoReadOptions): Promise<BM[]>;
    runQueryExtended(q: DBQuery<DBM>, opt?: CommonDaoReadOptions): Promise<RunQueryResult<BM>>;
    runQueryAsDBM(q: DBQuery<DBM>, opt?: CommonDaoReadOptions): Promise<DBM[]>;
    runQueryExtendedAsDBM(q: DBQuery<DBM>, opt?: CommonDaoReadOptions): Promise<RunQueryResult<DBM>>;
    runQueryCount(q: DBQuery<DBM>, opt?: CommonDaoReadOptions): Promise<number>;
    streamQueryAsDBM(q: DBQuery<DBM>, opt?: CommonDaoStreamOptions<DBM>): Pipeline<DBM>;
    streamQuery(q: DBQuery<DBM>, opt?: CommonDaoStreamOptions<BM>): Pipeline<BM>;
    queryIds(q: DBQuery<DBM>, opt?: CommonDaoReadOptions): Promise<ID[]>;
    streamQueryIds(q: DBQuery<DBM>, opt?: CommonDaoStreamOptions<ID>): Pipeline<ID>;
    /**
     * Mutates!
     */
    assignIdCreatedUpdated<T extends BaseDBEntity>(obj: Partial<T>, opt?: CommonDaoOptions): void;
    /**
     * Convenience method to replace 3 operations (loading+patching+saving) with one:
     *
     * 1. Loads the row by id.
     * 1.1 Creates the row (via this.create()) if it doesn't exist
     * (this will cause a validation error if Patch has not enough data for the row to be valid).
     * 2. Applies the patch on top of loaded data.
     * 3. Saves (as fast as possible since the read) with the Patch applied, but only if the data has changed.
     */
    patchById(id: ID, patch: Partial<BM>, opt?: CommonDaoPatchByIdOptions<DBM>): Promise<BM>;
    /**
     * Like patchById, but runs all operations within a Transaction.
     */
    patchByIdInTransaction(id: ID, patch: Partial<BM>, opt?: CommonDaoPatchByIdOptions<DBM>): Promise<BM>;
    /**
     * Same as patchById, but takes the whole object as input.
     * This "whole object" is mutated with the patch and returned.
     * Otherwise, similar behavior as patchById.
     * It still loads the row from the DB.
     */
    patch(bm: BM, patch: Partial<BM>, opt?: CommonDaoPatchOptions<DBM>): Promise<BM>;
    /**
     * Like patch, but runs all operations within a Transaction.
     */
    patchInTransaction(bm: BM, patch: Partial<BM>, opt?: CommonDaoSaveBatchOptions<DBM>): Promise<BM>;
    /**
     * Mutates with id, created, updated
     */
    save(bm: Unsaved<BM>, opt?: CommonDaoSaveOptions<BM, DBM>): Promise<BM>;
    saveAsDBM(dbm: Unsaved<DBM>, opt?: CommonDaoSaveOptions<BM, DBM>): Promise<DBM>;
    saveBatch(bms: Unsaved<BM>[], opt?: CommonDaoSaveBatchOptions<DBM>): Promise<BM[]>;
    saveBatchAsDBM(dbms: Unsaved<DBM>[], opt?: CommonDaoSaveBatchOptions<DBM>): Promise<DBM[]>;
    private prepareSaveOptions;
    /**
     * "Streaming" is implemented by buffering incoming rows into **batches**
     * (of size opt.chunkSize, which defaults to 500),
     * and then executing db.saveBatch(chunk) with the concurrency
     * of opt.chunkConcurrency (which defaults to 32).
     *
     * It takes a Pipeline as input, appends necessary saving transforms to it,
     * and calls .run() on it.
     */
    streamSave(p: Pipeline<BM>, opt?: CommonDaoStreamSaveOptions<DBM>): Promise<void>;
    /**
     * @returns number of deleted items
     */
    deleteById(id?: ID | null, opt?: CommonDaoOptions): Promise<number>;
    deleteByIds(ids: ID[], opt?: CommonDaoOptions): Promise<number>;
    /**
     * Pass `chunkSize: number` (e.g 500) option to use Streaming: it will Stream the query, chunk by 500, and execute
     * `deleteByIds` for each chunk concurrently (infinite concurrency).
     * This is expected to be more memory-efficient way of deleting large number of rows.
     */
    deleteByQuery(q: DBQuery<DBM>, opt?: CommonDaoStreamDeleteOptions<DBM>): Promise<number>;
    patchByIds(ids: ID[], patch: Partial<DBM>, opt?: CommonDaoOptions): Promise<number>;
    patchByQuery(q: DBQuery<DBM>, patch: Partial<DBM>, opt?: CommonDaoOptions): Promise<number>;
    /**
     * Caveat: it doesn't update created/updated props.
     *
     * @experimental
     */
    increment(prop: keyof DBM, id: ID, by?: number, opt?: CommonDaoOptions): Promise<number>;
    /**
     * Caveat: it doesn't update created/updated props.
     *
     * @experimental
     */
    incrementBatch(prop: keyof DBM, incrementMap: StringMap<number>, opt?: CommonDaoOptions): Promise<StringMap<number>>;
    dbmToBM(_dbm: undefined, opt?: CommonDaoOptions): null;
    dbmToBM(_dbm?: DBM, opt?: CommonDaoOptions): BM;
    dbmsToBM(dbms: DBM[], opt?: CommonDaoOptions): BM[];
    /**
     * Mutates object with properties: id, created, updated.
     * Returns DBM (new reference).
     */
    bmToDBM(bm: undefined, opt?: CommonDaoOptions): null;
    bmToDBM(bm?: BM, opt?: CommonDaoOptions): DBM;
    bmsToDBM(bms: BM[], opt?: CommonDaoOptions): DBM[];
    /**
     * Converts a DBM to storage format, applying compression if configured.
     *
     * Use this when you need to write directly to the database, bypassing the DAO save methods.
     * The returned value is opaque and should only be passed to db.saveBatch() or similar.
     *
     * @example
     * const storageRow = await dao.dbmToStorageRow(dbm)
     * await db.saveBatch(table, [storageRow])
     */
    dbmToStorageRow(dbm: DBM): Promise<ObjectWithId>;
    /**
     * Converts multiple DBMs to storage rows.
     */
    dbmsToStorageRows(dbms: DBM[]): Promise<ObjectWithId[]>;
    /**
     * Converts a storage row back to a DBM, applying decompression if needed.
     *
     * Use this when you need to read directly from the database, bypassing the DAO load methods.
     *
     * @example
     * const rows = await db.getByIds(table, ids)
     * const dbms = await Promise.all(rows.map(row => dao.storageRowToDBM(row)))
     */
    storageRowToDBM(row: ObjectWithId): DBM;
    /**
     * Converts multiple storage rows to DBMs.
     */
    storageRowsToDBM(rows: ObjectWithId[]): DBM[];
    private compress;
    /**
     * Mutates `dbm`.
     */
    private decompress;
    anyToDBM(dbm: undefined, opt?: CommonDaoOptions): null;
    anyToDBM(dbm?: any, opt?: CommonDaoOptions): DBM;
    anyToDBMs(rows: DBM[], opt?: CommonDaoOptions): DBM[];
    /**
     * Returns *converted value* (NOT the same reference).
     * Does NOT mutate the object.
     * Validates (unless `skipValidation=true` passed).
     */
    private validateAndConvert;
    getTableSchema(): Promise<JsonSchema<DBM>>;
    createTable(schema: JsonSchema<DBM>, opt?: CommonDaoCreateOptions): Promise<void>;
    /**
     * Proxy to this.cfg.db.ping
     */
    ping(): Promise<void>;
    withId(id: ID): DaoWithId<CommonDao<BM, DBM, ID>>;
    withIds(ids: ID[]): DaoWithIds<CommonDao<BM, DBM, ID>>;
    withRowsToSave(rows: Unsaved<BM>[]): DaoWithRows<CommonDao<BM, DBM, ID>>;
    withRowToSave(row: Unsaved<BM>, opt?: DaoWithRowOptions<BM>): DaoWithRow<CommonDao<BM, DBM, ID>>;
    /**
     * Load rows (by their ids) from Multiple tables at once.
     * An optimized way to load data, minimizing DB round-trips.
     *
     * @experimental
     */
    static multiGet<MAP extends Record<string, DaoWithIds<AnyDao> | DaoWithId<AnyDao>>>(inputMap: MAP, opt?: CommonDaoReadOptions): Promise<{
        [K in keyof MAP]: MAP[K] extends DaoWithIds<any> ? InferBM<MAP[K]['dao']>[] : InferBM<MAP[K]['dao']> | null;
    }>;
    private static prepareMultiGetIds;
    private static multiGetMapByTableById;
    private static prepareMultiGetOutput;
    /**
     * @experimental
     */
    static multiDelete(inputs: (DaoWithId<AnyDao> | DaoWithIds<AnyDao>)[], opt?: CommonDaoOptions): Promise<NonNegativeInteger>;
    static multiSave(inputs: (DaoWithRows<AnyDao> | DaoWithRow<AnyDao>)[], opt?: CommonDaoSaveBatchOptions<any>): Promise<void>;
    createTransaction(opt?: CommonDBTransactionOptions): Promise<CommonDaoTransaction>;
    runInTransaction<T = void>(fn: CommonDaoTransactionFn<T>, opt?: CommonDBTransactionOptions): Promise<T>;
    private ensureRequired;
    /**
     * Throws if readOnly is true
     */
    private requireWriteAccess;
    /**
     * Throws if readOnly is true
     */
    private requireObjectMutability;
    /**
     * Throws if query uses a property that is in `excludeFromIndexes` list.
     */
    private validateQueryIndexes;
}
/**
 * Transaction is committed when the function returns resolved Promise (aka "returns normally").
 *
 * Transaction is rolled back when the function returns rejected Promise (aka "throws").
 */
export type CommonDaoTransactionFn<T = void> = (tx: CommonDaoTransaction) => Promise<T>;
export interface DaoWithIds<DAO extends AnyDao> {
    dao: DAO;
    ids: string[];
}
export interface DaoWithId<DAO extends AnyDao> {
    dao: DAO;
    id: string;
}
export interface DaoWithRows<DAO extends AnyDao, BM = InferBM<DAO>> {
    dao: DAO;
    rows: Unsaved<BM>[];
}
export interface DaoWithRow<DAO extends AnyDao, BM = InferBM<DAO>> {
    dao: DAO;
    row: Unsaved<BM>;
    opt?: DaoWithRowOptions<BM>;
}
export interface DaoWithRowOptions<BM> {
    skipIfEquals?: BM;
}
export type InferBM<DAO> = DAO extends CommonDao<infer BM> ? BM : never;
export type InferDBM<DAO> = DAO extends CommonDao<any, infer DBM> ? DBM : never;
export type InferID<DAO> = DAO extends CommonDao<any, any, infer ID> ? ID : never;
export type AnyDao = CommonDao<any>;
