/**
 * @fileoverview Source-agnostic spillover helper. Drains rows from any
 * tabular source up to a character budget for the inline preview, then — if
 * the source exceeds the budget — registers the full source (preview rows +
 * sentinel + remaining tail) to a canvas table and returns a handle. Same
 * input domain as {@link CanvasInstance.registerTable}: arrays, sync
 * generators, paginated APIs wrapped as async generators, etc. The seam is
 * the row iterable itself; everything BrAPI- or pagination-specific stays
 * out of this layer.
 * @module src/services/canvas/spillover
 */
import type { CanvasInstance } from './core/CanvasInstance.js';
import type { ColumnSchema, RegisterTableResult } from './types.js';
/** Row shape — mirrors what registerTable accepts. */
type Row = Record<string, unknown>;
/** Options for {@link spillover}. */
export interface SpilloverOptions<T extends Row> {
    /** Pre-acquired canvas the caller already controls. */
    canvas: CanvasInstance;
    /** Drain caps applied to the spill phase. */
    caps?: {
        /**
         * Hard upper bound on rows registered to the canvas table. When the cap
         * is hit while the source still has more rows, the table holds exactly
         * `maxRows` rows and the result reports `truncated: true`.
         */
        maxRows?: number;
    };
    /**
     * Character budget for the inline preview. The helper drains rows,
     * accumulating `JSON.stringify(row).length` per row. The first row whose
     * serialization pushes the running total past `previewChars` is the
     * overflow sentinel — its existence proves the source has more rows than
     * fit, and it is itself spilled (not previewed). Heuristic: ~4× the
     * desired token budget (e.g. 100_000 chars ≈ 25k tokens). Must be ≥ 1.
     */
    previewChars: number;
    /**
     * Explicit table schema. Forwarded to canvas. Auto-derived from the
     * preview buffer when omitted.
     */
    schema?: ColumnSchema[];
    /**
     * Cancellation. Throws on abort during drain or registration; partial
     * canvas tables are best-effort dropped before the throw propagates.
     */
    signal?: AbortSignal;
    /** Row source. Anything DataCanvas.registerTable accepts. */
    source: AsyncIterable<T> | Iterable<T>;
    /**
     * Auto-generated (e.g. `spilled_<8-hex>`) when omitted. Caller-supplied
     * names are validated against the canvas identifier rules up-front,
     * before any draining begins.
     */
    tableName?: string;
    /**
     * Per-table sliding TTL forwarded to {@link CanvasInstance.registerTable}.
     * When set, the spilled table ages independently of the canvas; the TTL
     * window slides on reads or writes against this specific table.
     */
    ttlMs?: number;
}
/** Return value when the source fit inside the preview budget. No canvas call was made. */
export interface SpilloverFitResult<T> {
    previewRows: T[];
    spilled: false;
}
/** Return value when the source exceeded the preview budget and was spilled to a canvas table. */
export interface SpilloverSpillResult<T> {
    /** Result from `canvas.registerTable` — `tableName`, `rowCount`, `columns`. */
    handle: RegisterTableResult;
    previewRows: T[];
    spilled: true;
    /**
     * `true` when `caps.maxRows` was hit during the spill drain — the
     * registered table holds exactly `caps.maxRows` rows but the upstream
     * source had more. `false` when the full source was registered.
     */
    truncated: boolean;
}
/** Discriminated union return for {@link spillover}. */
export type SpilloverResult<T> = SpilloverFitResult<T> | SpilloverSpillResult<T>;
/**
 * Drain a tabular source into an inline preview slice and (if the source
 * exceeds the budget) register the full source to a canvas table. The
 * registered table holds every row — the preview slice plus the rows beyond
 * it — so callers SQL the canvas table for the complete result rather than
 * mentally unioning the preview and the table.
 *
 * The character budget is enforced by serializing each drained row with
 * `JSON.stringify`. The first row that pushes the running total past
 * `previewChars` is the overflow sentinel: its existence proves the source
 * has more rows than fit, and it is registered on the canvas (not echoed in
 * the preview). When the source exhausts under budget no canvas call is
 * made.
 *
 * Schema is inferred from the preview buffer (plus the overflow sentinel)
 * when no explicit schema is supplied. Pass `schema` directly when type
 * fidelity matters more than the buffer-sized sniff window.
 *
 * @example
 * ```ts
 * async function* fetchAll(): AsyncIterable<Row> {
 *   for (let p = 0; ; p++) {
 *     const page = await fetchPage(p);
 *     yield* page.rows;
 *     if (page.last) return;
 *   }
 * }
 *
 * const result = await spillover({
 *   canvas,
 *   source: fetchAll(),
 *   previewChars: 100_000,         // ≈ 25k tokens
 *   caps: { maxRows: 50_000 },
 *   signal: ctx.signal,
 * });
 * if (result.spilled) {
 *   // result.handle.tableName, result.truncated, result.previewRows
 * } else {
 *   // result.previewRows
 * }
 * ```
 */
export declare function spillover<T extends Row>(opts: SpilloverOptions<T>): Promise<SpilloverResult<T>>;
export {};
//# sourceMappingURL=spillover.d.ts.map