import { RequestHandler } from 'express'; import { FastifyRequest, FastifyReply } from 'fastify'; import { Context } from 'koa'; import { Request as Request$1, ResponseToolkit } from '@hapi/hapi'; import { Request as Request$2, Response as Response$1, Next } from 'restify'; import { IncomingMessage, ServerResponse } from 'http'; import * as hono from 'hono'; import { Context as Context$1 } from 'hono'; import * as hono_utils_http_status from 'hono/utils/http-status'; import * as hono_utils_types from 'hono/utils/types'; import { Knex } from 'knex'; interface Logger { error(message: string, context?: LogContext): void; warn(message: string, context?: LogContext): void; info(message: string, context?: LogContext): void; debug(message: string, context?: LogContext): void; } interface LogContext { component?: string; operation?: string; requestId?: string; userId?: string; table?: string; query?: Record; error?: Error | string; stack?: string; timestamp?: string; [key: string]: unknown; } type LogLevel = 'error' | 'warn' | 'info' | 'debug' | 'silent'; interface LoggerOptions { level?: LogLevel; includeStack?: boolean; includeTimestamp?: boolean; colorize?: boolean; format?: 'json' | 'text' | 'pretty'; } declare function generateId(): string; declare function maskSensitiveData(data: string): string; declare function createLogger(options?: LoggerOptions): Logger; /** * Supported database types for Tabula Lens */ type DatabaseType = 'pg' | 'mysql' | 'sqlite' | 'mssql'; /** * Configuration object for Tabula Lens initialization * Extends TabulaLensOptions to include database connection details */ interface TabulaLensConfig { /** Database connection URL */ url: string; /** Database type (optional - auto-detected from URL if not provided) */ type?: DatabaseType; /** Logger instance */ logger?: Logger; /** Log level */ logLevel?: LogLevel; /** Enable query logging */ enableQueryLogging?: boolean; /** Enable request logging */ enableRequestLogging?: boolean; /** Mask sensitive data in logs */ sensitiveDataMasking?: boolean; /** Log format */ logFormat?: 'json' | 'text' | 'pretty'; } /** * Detects the database type from a connection URL or file path. * * Supports standard connection strings plus common hosted service variations: * - PostgreSQL/CockroachDB: postgresql://, postgres://, pgsql:// * (covers Neon, Supabase, AWS RDS, Heroku Postgres, Railway, TimescaleDB, * Azure Database for PostgreSQL, Google Cloud SQL, CockroachDB, DigitalOcean) * - MySQL/MariaDB: mysql://, mariadb://, mysql2://, mysqlx:// * (covers PlanetScale, AWS RDS MariaDB, Azure Database for MySQL, Google Cloud SQL MySQL, * DigitalOcean Managed MySQL, Upstash) * - SQLite: sqlite://, sqlite:, file paths ending in .db/.sqlite/.sqlite3/.db3, :memory: * (covers Turso local files; managed platforms like Turso Cloud/LibSQL are intentionally * out of scope for v1 because they require the libsql driver, not a standard SQLite driver) * - SQL Server: mssql://, sqlserver://, mssql+tcp://, mssql+udp:// * (covers Azure SQL Database, AWS RDS SQL Server) * * @param url - Database connection URL or file path * @returns The detected database type * @throws TabulaLensError if the database type cannot be detected * * @example * ```ts * detectDatabaseType('postgresql://localhost/mydb') // returns 'pg' * detectDatabaseType('mysql://localhost/mydb') // returns 'mysql' * detectDatabaseType('./database.db') // returns 'sqlite' * detectDatabaseType('mssql://localhost/mydb') // returns 'mssql' * detectDatabaseType(':memory:') // returns 'sqlite' * ``` */ declare function detectDatabaseType(url: string): DatabaseType; /** * Validates that a database type is supported * * @param type - The database type to validate * @throws TabulaLensError if the type is invalid * * @example * ```ts * validateDatabaseType('pg') // valid, no error * validateDatabaseType('oracle') // throws TabulaLensError * ``` */ declare function validateDatabaseType(type: string): DatabaseType; interface TabulaLensOptions { logger?: Logger; logLevel?: LogLevel; enableQueryLogging?: boolean; enableRequestLogging?: boolean; sensitiveDataMasking?: boolean; logFormat?: 'json' | 'text' | 'pretty'; } declare class TabulaLensError extends Error { statusCode: number; code: string; details?: unknown | undefined; constructor(statusCode: number, code: string, message: string, details?: unknown | undefined); } interface QueryOptions { table?: string; page?: number; limit?: number; sort?: string; filter?: string; columns?: string[]; filterColumns?: string[]; } interface SortOption { column: string; direction: 'asc' | 'desc'; } interface FilterOption { column: string; operator: 'eq' | 'ne' | 'gt' | 'lt' | 'gte' | 'lte' | 'like' | 'ilike'; value: string | number; } interface QueryResult { data: Record[]; columns: string[]; pagination: { page: number; limit: number; total: number; totalPages: number; }; } interface RequestContext { method: string; path: string; query: Record; body?: unknown; } interface ResponseContext { status: number; headers: Record; body: unknown; } declare class TabulaLens { private db; private logger; private enableQueryLogging; private enableRequestLogging; private sensitiveDataMasking; private columnCache; private databaseType; private dialect; /** * Constructor overloads for backward compatibility * * @param config - Either a database URL string or a TabulaLensConfig object * @param options - Optional TabulaLensOptions (only used when config is a string) * * @example * ```ts * // String form (existing behavior) * const tabulaLens = new TabulaLens('postgresql://localhost/mydb', { logLevel: 'info' }); * * // Config object form (new) * const tabulaLens = new TabulaLens({ * url: 'mysql://localhost/mydb', * type: 'mysql', * logLevel: 'info' * }); * ``` */ constructor(config: string, options?: TabulaLensOptions); constructor(config: TabulaLensConfig); /** * Maps database type to Knex client name * * @param type - Database type * @returns Knex client name */ private getKnexClient; getLogger(): Logger; query(options?: QueryOptions): Promise; getTables(): Promise; getColumns(table: string): Promise<{ name: string; type: string; }[]>; getTableMetadata(table: string): Promise<{ name: string; columns: { name: string; type: string; }[]; }>; /** * Get filterable (text-based) columns for a table * This is a public method that can be used by frontend components to validate filter column selection */ getFilterableColumns(table: string): Promise; private parseSort; close(): Promise; handle(request: RequestContext): Promise; } /** * Express adapter for Express 5.0+ * For Express 4.x support, use express4Adapter instead */ declare function expressAdapter(tabulaLens: TabulaLens): RequestHandler; /** * Express adapter for Express 4.x * Note: This adapter requires @types/express@4.x to be installed in your project * For Express 5.0+ support, use expressAdapter instead */ declare function express4Adapter(tabulaLens: TabulaLens): RequestHandler; declare function fastifyAdapter(tabulaLens: TabulaLens): (request: FastifyRequest, reply: FastifyReply) => Promise; declare function koaAdapter(tabulaLens: TabulaLens): (ctx: Context) => Promise; declare function hapiAdapter(tabulaLens: TabulaLens): (request: Request$1, h: ResponseToolkit) => Promise>; declare function restifyAdapter(tabulaLens: TabulaLens): (req: Request$2, res: Response$1, next: Next) => Promise; interface NativeAdapterOptions { parseBody?: (req: IncomingMessage) => Promise; } declare function nativeAdapter(tabulaLens: TabulaLens, options?: NativeAdapterOptions): (req: IncomingMessage, res: ServerResponse) => Promise; interface NextAdapterOptions { parseBody?: boolean; } declare function createNextRouteHandler(tabulaLens: TabulaLens, options?: NextAdapterOptions): (request: Request) => Promise; interface TanStackStartAdapterOptions { parseBody?: boolean; } declare function createTanStackStartHandler(tabulaLens: TabulaLens, options?: TanStackStartAdapterOptions): (request: Request) => Promise; interface RemixAdapterOptions { parseBody?: boolean; } declare function createRemixHandler(tabulaLens: TabulaLens, options?: RemixAdapterOptions): (request: Request) => Promise; interface SvelteKitAdapterOptions { parseBody?: boolean; } declare function createSvelteKitHandler(tabulaLens: TabulaLens, options?: SvelteKitAdapterOptions): (event: { request: Request; url: URL; }) => Promise; interface HonoAdapterOptions { parseBody?: boolean; } declare function createHonoMiddleware(tabulaLens: TabulaLens, options?: HonoAdapterOptions): (c: Context$1) => Promise>; interface ElysiaAdapterOptions { parseBody?: boolean; } declare function createElysiaHandler(tabulaLens: TabulaLens, options?: ElysiaAdapterOptions): (ctx: { request: Request; path: string; query: Record; body?: unknown; set: { headers: Record; status?: number; }; }) => Promise; interface FreshAdapterOptions { parseBody?: boolean; } declare function createFreshHandler(tabulaLens: TabulaLens, options?: FreshAdapterOptions): (request: Request) => Promise; /** * Column metadata returned by dialect implementations */ interface ColumnInfo { name: string; type: string; } /** * Dialect strategy interface for database-specific operations * * This interface defines the contract that all database dialect implementations must follow. * Each dialect handles the specific SQL syntax and metadata queries required for its database type. * * @example * ```ts * import { PostgresDialect } from './dialects/postgres'; * * const dialect = new PostgresDialect(); * const tables = await dialect.getTables(knexInstance); * const columns = await dialect.getColumns(knexInstance, 'users'); * const filterableTypes = dialect.getFilterableTypes(); * const likeOperator = dialect.getLikeOperator(); // 'ILIKE' for PostgreSQL * ``` */ interface DialectStrategy { /** * Get a list of all table names in the database * * This method should return only base tables (not views, system tables, etc.) * and should use the appropriate metadata query for the database type. * * @param db - Knex instance configured for the database * @returns Promise resolving to an array of table names * * @example * ```ts * const tables = await dialect.getTables(knexInstance); * // ['users', 'products', 'orders', ...] * ``` */ getTables(db: Knex): Promise; /** * Get column metadata for a specific table * * This method should return column information including name and data type. * The columns should be ordered by their ordinal position in the table. * * @param db - Knex instance configured for the database * @param table - Name of the table to get columns for * @returns Promise resolving to an array of column information objects * * @example * ```ts * const columns = await dialect.getColumns(knexInstance, 'users'); * // [ * // { name: 'id', type: 'integer' }, * // { name: 'email', type: 'character varying' }, * // { name: 'created_at', type: 'timestamp without time zone' } * // ] * ``` */ getColumns(db: Knex, table: string): Promise; /** * Get the list of data type names that are considered filterable (text-based) * * These are the types that support LIKE/ILIKE operations for text search. * The type names should match the exact strings returned by the database's * information schema or PRAGMA queries. * * @returns Array of type names that support text filtering * * @example * ```ts * const filterableTypes = dialect.getFilterableTypes(); * // PostgreSQL: ['character varying', 'text', 'varchar', 'char', 'character', 'uuid'] * // MySQL: ['varchar', 'text', 'tinytext', 'mediumtext', 'longtext', 'char'] * // SQLite: ['TEXT', 'text'] * // MSSQL: ['varchar', 'nvarchar', 'text', 'char', 'nchar'] * ``` */ getFilterableTypes(): string[]; /** * Get the LIKE operator for case-insensitive text matching * * Some databases support ILIKE for case-insensitive matching (PostgreSQL), * while others use LIKE which is case-insensitive by default (MySQL, SQLite, MSSQL). * * @returns Either 'LIKE' or 'ILIKE' based on database capabilities * * @example * ```ts * const likeOperator = dialect.getLikeOperator(); * // PostgreSQL: 'ILIKE' * // MySQL: 'LIKE' * // SQLite: 'LIKE' * // MSSQL: 'LIKE' * ``` */ getLikeOperator(): 'LIKE' | 'ILIKE'; } /** * PostgreSQL dialect implementation * * This dialect handles PostgreSQL-specific SQL syntax and metadata queries. * It uses the information_schema for metadata and supports ILIKE for case-insensitive matching. * * @example * ```ts * import { PostgresDialect } from './dialects/postgres'; * * const dialect = new PostgresDialect(); * const tables = await dialect.getTables(knexInstance); * const columns = await dialect.getColumns(knexInstance, 'users'); * ``` */ declare class PostgresDialect implements DialectStrategy { /** * Get all base tables from the PostgreSQL database * * Queries information_schema.tables for tables in the 'public' schema * with table_type = 'BASE TABLE' (excludes views and system tables). * * @param db - Knex instance configured for PostgreSQL * @returns Promise resolving to an array of table names */ getTables(db: Knex): Promise; /** * Get column metadata for a specific table * * Queries information_schema.columns for column information including * name and data type. Results are ordered by ordinal_position. * * @param db - Knex instance configured for PostgreSQL * @param table - Name of the table to get columns for * @returns Promise resolving to an array of column information objects */ getColumns(db: Knex, table: string): Promise; /** * Get PostgreSQL text-based data types that support LIKE/ILIKE operations * * PostgreSQL uses specific type names for character data: * - character varying / varchar: Variable-length character strings * - text: Variable-length character strings (no length limit) * - char / character: Fixed-length character strings * - uuid: UUID type (supports text operations) * * @returns Array of PostgreSQL text type names */ getFilterableTypes(): string[]; /** * Get the LIKE operator for PostgreSQL * * PostgreSQL supports ILIKE for case-insensitive matching, * which is preferred over LIKE for user-facing search. * * @returns 'ILIKE' for case-insensitive matching */ getLikeOperator(): 'LIKE' | 'ILIKE'; } /** * MySQL dialect implementation * * This dialect handles MySQL-specific SQL syntax and metadata queries. * It uses the information_schema for metadata and LIKE for case-insensitive matching * (MySQL LIKE is case-insensitive by default for most collations). * * @example * ```ts * import { MySQLDialect } from './dialects/mysql'; * * const dialect = new MySQLDialect(); * const tables = await dialect.getTables(knexInstance); * const columns = await dialect.getColumns(knexInstance, 'users'); * ``` */ declare class MySQLDialect implements DialectStrategy { /** * Get all base tables from the MySQL database * * Queries information_schema.tables for tables in the current database * (using DATABASE() function) with table_type = 'BASE TABLE'. * * @param db - Knex instance configured for MySQL * @returns Promise resolving to an array of table names */ getTables(db: Knex): Promise; /** * Get column metadata for a specific table * * Queries information_schema.columns for column information including * name and data type. Results are ordered by ordinal_position. * * @param db - Knex instance configured for MySQL * @param table - Name of the table to get columns for * @returns Promise resolving to an array of column information objects */ getColumns(db: Knex, table: string): Promise; /** * Get MySQL text-based data types that support LIKE operations * * MySQL supports several text types with different storage limits: * - varchar: Variable-length character strings (up to 65,535 bytes) * - text: Variable-length character strings (up to 65,535 bytes) * - tinytext: Variable-length character strings (up to 255 bytes) * - mediumtext: Variable-length character strings (up to 16MB) * - longtext: Variable-length character strings (up to 4GB) * - char: Fixed-length character strings * * @returns Array of MySQL text type names */ getFilterableTypes(): string[]; /** * Get the LIKE operator for MySQL * * MySQL LIKE is case-insensitive by default for most collations, * so we use LIKE instead of ILIKE (which MySQL doesn't support). * * @returns 'LIKE' for case-insensitive matching */ getLikeOperator(): 'LIKE' | 'ILIKE'; } /** * SQLite dialect implementation * * This dialect handles SQLite-specific SQL syntax and metadata queries. * SQLite doesn't use information_schema - instead it uses PRAGMA commands * and sqlite_master for metadata queries. * * @example * ```ts * import { SQLiteDialect } from './dialects/sqlite'; * * const dialect = new SQLiteDialect(); * const tables = await dialect.getTables(knexInstance); * const columns = await dialect.getColumns(knexInstance, 'users'); * ``` */ declare class SQLiteDialect implements DialectStrategy { /** * Get all tables from the SQLite database * * Queries sqlite_master for all tables (type = 'table'). * This excludes system tables and views. * * @param db - Knex instance configured for SQLite * @returns Promise resolving to an array of table names */ getTables(db: Knex): Promise; /** * Get column metadata for a specific table * * Uses PRAGMA table_info(?) to get column information. * This is SQLite-specific and returns different column names than information_schema. * * @param db - Knex instance configured for SQLite * @param table - Name of the table to get columns for * @returns Promise resolving to an array of column information objects */ getColumns(db: Knex, table: string): Promise; /** * Get SQLite text-based data types that support LIKE operations * * SQLite uses dynamic typing - any column can store any type, but it does * declare type affinity. The TEXT type affinity is used for text storage. * SQLite returns type names in various cases, so we include both. * * @returns Array of SQLite text type names */ getFilterableTypes(): string[]; /** * Get the LIKE operator for SQLite * * SQLite LIKE is case-insensitive for ASCII characters by default, * so we use LIKE instead of ILIKE (which SQLite doesn't support). * * @returns 'LIKE' for case-insensitive matching */ getLikeOperator(): 'LIKE' | 'ILIKE'; } /** * SQL Server (MSSQL) dialect implementation * * This dialect handles Microsoft SQL Server-specific SQL syntax and metadata queries. * It uses the information_schema for metadata and LIKE for case-insensitive matching * (MSSQL LIKE is case-insensitive depending on collation, typically case-insensitive). * * @example * ```ts * import { MSSQLDialect } from './dialects/mssql'; * * const dialect = new MSSQLDialect(); * const tables = await dialect.getTables(knexInstance); * const columns = await dialect.getColumns(knexInstance, 'users'); * ``` */ declare class MSSQLDialect implements DialectStrategy { /** * Get all base tables from the SQL Server database * * Queries information_schema.tables for tables with table_type = 'BASE TABLE'. * This excludes views and system tables. * * @param db - Knex instance configured for SQL Server * @returns Promise resolving to an array of table names */ getTables(db: Knex): Promise; /** * Get column metadata for a specific table * * Queries information_schema.columns for column information including * name and data type. Results are ordered by ordinal_position. * * @param db - Knex instance configured for SQL Server * @param table - Name of the table to get columns for * @returns Promise resolving to an array of column information objects */ getColumns(db: Knex, table: string): Promise; /** * Get SQL Server text-based data types that support LIKE operations * * SQL Server supports both ASCII and Unicode text types: * - varchar: Variable-length ASCII character strings * - nvarchar: Variable-length Unicode character strings * - text: Variable-length ASCII character strings (deprecated but still supported) * - ntext: Variable-length Unicode character strings (deprecated but still supported) * - char: Fixed-length ASCII character strings * - nchar: Fixed-length Unicode character strings * * @returns Array of SQL Server text type names */ getFilterableTypes(): string[]; /** * Get the LIKE operator for SQL Server * * SQL Server LIKE behavior depends on collation, but most installations * use case-insensitive collations by default. SQL Server doesn't support ILIKE. * * @returns 'LIKE' for case-insensitive matching (depends on collation) */ getLikeOperator(): 'LIKE' | 'ILIKE'; } /** * Factory function to create a dialect strategy instance based on database type * * This factory centralizes dialect creation and provides type-safe instantiation * of the appropriate dialect implementation. * * @param type - The database type (pg, mysql, sqlite, mssql) * @returns A dialect strategy instance for the specified database type * @throws Error if an invalid database type is provided * * @example * ```ts * import { createDialect } from './dialects'; * import { DatabaseType } from './database'; * * const dialect = createDialect('mysql'); * const tables = await dialect.getTables(knexInstance); * ``` */ declare function createDialect(type: DatabaseType): DialectStrategy; export { type ColumnInfo, type DatabaseType, type DialectStrategy, type ElysiaAdapterOptions, type FilterOption, type FreshAdapterOptions, type HonoAdapterOptions, type LogContext, type LogLevel, type Logger, type LoggerOptions, MSSQLDialect, MySQLDialect, type NativeAdapterOptions, type NextAdapterOptions, PostgresDialect, type QueryOptions, type QueryResult, type RemixAdapterOptions, type RequestContext, type ResponseContext, SQLiteDialect, type SortOption, type SvelteKitAdapterOptions, TabulaLens, type TabulaLensConfig, TabulaLensError, type TabulaLensOptions, type TanStackStartAdapterOptions, createDialect, createElysiaHandler, createFreshHandler, createHonoMiddleware, createLogger, createNextRouteHandler, createRemixHandler, createSvelteKitHandler, createTanStackStartHandler, detectDatabaseType, express4Adapter, expressAdapter, fastifyAdapter, generateId, hapiAdapter, koaAdapter, maskSensitiveData, nativeAdapter, restifyAdapter, validateDatabaseType };