import { createQueryBuilder as createBunQueryBuilder } from 'bun-query-builder'; import { setConfig as setBunQueryBuilderConfig } from 'bun-query-builder'; import * as bunQueryBuilder from 'bun-query-builder'; import type { DatabaseSchema } from 'bun-query-builder'; export declare function registerPersistentQueryHooks(hooks: QueryHooks): () => void; export declare function setConfig(config: QueryBuilderConfig): void; /** * Apply the bootstrap pragmas to the connection of the CURRENT process-wide * config. bun-query-builder's `qb.unsafe()` routes through its global * lazily-resolved connection — NOT the instance's captured one — so this * only targets `instance`'s connection when called in the same synchronous * tick as `createQueryBuilder()`, before any intervening `setConfig()` can * swap the signature-keyed singleton. The wrapped `createQueryBuilder` * below guarantees that ordering structurally; standalone callers must * preserve it themselves. */ export declare function applySqlitePragmas(instance: SqlitePragmaExecutor): void; /** * Bootstrap the MODEL-EXECUTOR connection — the raw `bun:sqlite` `Database` * that `Model.create()/save()/delete()` (createModel → getExecutor) write * through. Upstream creates it with `new Database(path)` and applies no * pragmas whatsoever, so without this the ORM write path runs with * `foreign_keys = OFF` (silent orphan rows) and default WAL checkpointing * regardless of what the query-builder connection was bootstrapped with. * * `getDatabase()` returns the executor's live handle for the sqlite dialect * (creating the executor if needed) and throws for mysql/postgres — where * there is nothing to bootstrap, hence the silent catch. Safe to call * repeatedly: the WeakSet makes it a no-op after the first hit per * connection, and a config change that swaps the executor's Database * produces a fresh (unseen) handle that gets bootstrapped on the next call. */ export declare function bootstrapModelExecutorPragmas(): void; /** * Drop-in replacement for bun-query-builder's `configureOrm` that * bootstraps the model-executor connection it (re)creates. * * This is the exact entry point `@stacksjs/orm` calls at import time * (`autoConfigureOrm()`), which in production builds the raw `Database` * every model write goes through — pre-fix, with no pragmas at all. */ export declare function configureOrm(options: Parameters[0]): void; /** * Drop-in replacement for bun-query-builder's `createQueryBuilder` that * bootstraps every fresh SQLite connection with the pragmas above. * * The upstream builder captures its connection eagerly at creation * (`state?.sql ?? getOrCreateBunSql()`) and its `SQLiteWrapper` only sets * `journal_mode = WAL` — never `foreign_keys` — so any instance created * outside this wrapper runs with FK enforcement off. Applying in the same * synchronous tick as creation pins the pragmas to the exact connection the * instance captured (see `applySqlitePragmas`). Skipped when the caller * supplies its own `state.sql` (reserved/transaction connections derive * from an already-bootstrapped parent). * * Also re-asserts the model-executor bootstrap: framework entry points run * through here on boot and on config reloads, so an executor Database that * was lazily (re)created from a config change gets its pragmas without any * caller having to know the two-connection topology. */ export declare function createQueryBuilder> = DatabaseSchema>>(state?: Parameters[0]): ReturnType>; /** * Register a model with bun-query-builder when the installed version * exposes that hook, or fall back to `defineModel()` for older releases. */ export declare function registerModel(name: string, model: unknown): unknown; /** * Validate a single SQL identifier (column, table, or alias name). * * - Must match `SAFE_IDENTIFIER` (letters/digits/underscores; must * start with letter or underscore). * - Must be ≤ 64 characters (matches MySQL's identifier limit; the * shortest cap across supported dialects). * - When `allowlist` is provided, the value must appear in it. Use * this with a model's known column names (`Object.keys(definition.attributes)`) * so a query that builds column names from `req.query.sortBy` etc. * can't smuggle anything past the schema. * * @example * ```ts * import { assertSafeIdentifier } from '@stacksjs/query-builder' * * const sortBy = req.query.sortBy as string * assertSafeIdentifier(sortBy, { allowlist: ['name', 'email', 'created_at'] }) * Model.orderBy(sortBy as keyof Model) * ``` */ export declare function validateIdentifier(value: unknown, opts?: { allowlist?: readonly string[] }): IdentifierValidation; /** * Throwing variant of {@link validateIdentifier}. Use at the boundary * where user input meets SQL identifier interpolation. * * @throws {Error} when the value isn't a safe identifier. */ export declare function assertSafeIdentifier(value: unknown, opts?: { allowlist?: readonly string[], context?: string }): asserts value is string; export declare function isSafeOperator(op: unknown): op is string; export declare function assertSafeOperator(op: unknown, context?: string): asserts op is string; /** * Per-connection SQLite bootstrap pragmas (stacksjs/stacks#1951). * `foreign_keys` does not persist in the database file — SQLite ships with * enforcement OFF on every new connection — so the inline * `REFERENCES … ON DELETE CASCADE` emitted by migrations (#1916) is inert * unless the connection bootstrap turns it on. bun-query-builder delegates * this to the consumer, and it opens sqlite connections in TWO independent * layers — both are bootstrapped here: * * 1. The query-builder connection (`getOrCreateBunSql` → `SQLiteWrapper`, * which only sets `journal_mode = WAL`): covered by the wrapped * `createQueryBuilder` below. * 2. The MODEL-EXECUTOR connection (`configureOrm`/`getExecutor` → a raw * `bun:sqlite` `Database` with NO pragmas at all) — the connection every * `Model.create()/save()/delete()` actually writes through: covered by * the wrapped `configureOrm` + `bootstrapModelExecutorPragmas` below. * Pre-fix, production model writes ran with `foreign_keys = OFF` while * the (idle) query-builder connection was correctly bootstrapped — * confirmed on a live deploy via lsof (two connections) and WAL frames * from ORM inserts never auto-checkpointing. */ export declare const SQLITE_BOOTSTRAP_PRAGMAS: readonly [unknown, unknown, unknown, unknown]; /** * Return value of `validateIdentifier` / `assertSafeIdentifier`. */ export declare interface IdentifierValidation { valid: boolean reason?: 'invalid-shape' | 'not-in-allowlist' | 'empty' | 'too-long' } /** * Dialects Stacks accepts in `config/database.ts`. * * A superset of what bun-query-builder renders for: `vitess` speaks MySQL's * wire protocol and identical DML, so it collapses onto the `mysql` renderer * (see `toQueryBuilderDialect` in @stacksjs/database) and diverges only in * DDL and transaction semantics, which the framework handles itself. */ export type StacksDialect = import('bun-query-builder').SupportedDialect | 'singlestore' | 'vitess'; declare type QueryHooks = import('bun-query-builder').QueryHooks; declare type QueryBuilderConfig = Parameters[0] & { /** Directory containing the active SQL migration corpus. */ migrationDir?: string /** Vitess topology controls forwarded to bun-query-builder's DDL driver. */ vitess?: { sharded: boolean } } declare type UnsafeReturn = Promise & { execute: () => Promise } declare type SqlitePragmaExecutor = { unsafe: (query: string, params?: readonly unknown[]) => UnsafeReturn } // Re-export everything from bun-query-builder export * from 'bun-query-builder'; export { saveMigrationSnapshot } from 'bun-query-builder'; // For backwards compatibility, export QueryBuilder as an alias export { createQueryBuilder as QueryBuilder };