@askdb/client API reference
Reference · @askdb/client
@askdb/client is a config-driven facade over @askdb/core. createAskDb() resolves the schema, model, and dialect from askdb.config.*, so call sites pass only a question.
The experimental @askdb/client/charts entry point, for planning a chart from a query result, has its own page.
To choose between the client and calling @askdb/core’s ask() with your own model, see Bring your own model → Which should I use?.
createAskDb(options)
Section titled “createAskDb(options)”Builds an AskDbClient bound to one config snapshot and one AI provider registry. Nothing loads until the first ask() or bind() call:
- Schema: the default schema loads on first use and is cached on this client. A per-call
schemais loaded for that call and never cached. - Model: built from the registry on the first
ask()that needs it, and cached on this client with its resolved AI config. - Dialect: resolved on every
ask()call (seeDialectResolution). It isn’t cached.
Each client has its own cache, so two clients never share a schema or a model. Call reload() to clear it.
import { createAskDb } from "@askdb/client";import { bootstrapAskDbEnv, getAskDbRuntimeConfig } from "@askdb/config";
bootstrapAskDbEnv({ cwd: process.cwd() });
const askdb = createAskDb({ config: getAskDbRuntimeConfig(), // ai.provider picks one of @askdb/ai's built-in providers schema: { path: "./my-app.schema" },});Install the AI SDK package for the provider your config selects, for example @ai-sdk/openai. It’s an optional peer of @askdb/ai and is loaded the first time the client builds a model.
Options — CreateAskDbOptions
Section titled “Options — CreateAskDbOptions”| Field | Type | Required | Description |
|---|---|---|---|
config | AskDbRuntimeConfig | yes | Runtime config snapshot from getAskDbRuntimeConfig(). Call bootstrapAskDbEnv() at startup first. |
providers | AiProviderAdapters | no | Which providers the client can build models for. Default: every provider built into @askdb/ai (openai, anthropic, google, azure/foundry, gateway). Pass built-in names to restrict the set (["openai"]) or AiProviderAdapter objects to add custom providers; both can be mixed. |
registry | AiRegistry | no | Prebuilt registry from createAiRegistry(...) — advanced alternative to providers (e.g. to share one registry across clients). Pass at most one of providers or registry: passing both throws an Error from createAskDb(). Build it with the @askdb/ai version @askdb/client depends on: one from an older @askdb/ai has no section methods. |
schema | SchemaSource | no | Default schema source. Falls back to host.schemaJson, then host.schemaPath, from askdb.config.ts. With none of the three, the first call throws SchemaNotConfiguredError. |
dialect | BuiltInDialectId | no | Default dialect override. Falls back to dialect in askdb.config.ts, then schema.provider, then "postgres" when the schema records no provider. |
unknownDialect | "throw" | "fallback-postgres" | no | What to do when no dialect is set and schema.provider isn’t a built-in dialect id. Default "throw": ask() throws DialectNotSupportedError. "fallback-postgres" uses "postgres" and reports it in DialectResolution.note. |
reasoningEffort | ReasoningEffort | no | "minimal", "low", "medium" or "high". The default reasoning effort for this client’s ask() calls. It takes precedence over ai.language.reasoning in askdb.config.ts (see Reasoning effort), and a per-call reasoningEffort takes precedence over it. |
onResolve | (info: { dialect: DialectResolution; modelSource: "override" | "registry" | "mock" }) => void | no | Called on every ask() call after the schema, dialect and model resolve, before the model call. Useful for logging which dialect and model were chosen. bind() doesn’t call it. |
modelSource says where the model came from: "override" for a per-call model, "mock" for a per-call deps.generateText or dev.mockSql in askdb.config.ts, and "registry" for a model built from ai in the config.
DialectResolution
Section titled “DialectResolution”What resolveDialect() returns, and the dialect that onResolve receives:
type DialectResolution = { dialect: AskDialectInput; source: "override" | "config" | "schema" | "default"; note?: string;};source | Where dialect came from |
|---|---|
"override" | The call’s dialect override. |
"config" | createAskDb({ dialect }), else dialect in askdb.config.ts. |
"schema" | The schema’s provider, a built-in dialect id. |
"default" | "postgres": the schema records no provider, or unknownDialect is "fallback-postgres". |
note is set when the config dialect and the schema’s provider disagree (the config wins), and when "fallback-postgres" applies. askdb ask prints it to stderr as Note: ….
resolveDialect(schema, options?)
Section titled “resolveDialect(schema, options?)”The rule createAskDb()’s ask() runs to pick a dialect, exported so a host that calls @askdb/core’s ask() itself picks it the same way (Studio does). It returns a DialectResolution and reads no config: pass the values in.
| Option | Type | Description |
|---|---|---|
override | AskDialectInput | A per-call dialect. It wins over everything (source: "override"). |
configDialect | BuiltInDialectId | The configured dialect, such as getAskDbRuntimeConfig().nlToSql.dialect (source: "config"). |
unknownDialect | "throw" | "fallback-postgres" | What to do when neither is set and schema.provider isn’t a built-in dialect id. Default "throw". |
Without override or configDialect, it uses the schema’s provider when that’s a built-in dialect id, and "postgres" when the schema records no provider. For any other provider, it throws DialectNotSupportedError, naming the provider and the dialect config key, unless unknownDialect is "fallback-postgres". Checking SQL for one engine under another engine’s rules can pass SQL the real engine reads differently (ADR 0039).
SchemaSource
Section titled “SchemaSource”Accepted forms for the schema option and per-call overrides:
{ path: string } // directory, bundle file, or schema.json path{ json: string } // raw JSON string{ schema: AnyNormalizedSchema} // pre-loaded schema objectclient.ask(question, overrides?)
Section titled “client.ask(question, overrides?)”const { sql, usage, unboundSql, params, preparedQuery } = await askdb.ask( "How many orders shipped last month?",);Runs the same pipeline as @askdb/core’s ask(). Returns AskPipelineResult — see @askdb/core API → Result. The facade forwards options and returns the core result verbatim, including verdict and the optional unboundSql / params / parameters / preparedQuery when parameterize is on (default). Rebind a stored preparedQuery with client.bind().
The model comes from the first of these that is set: the call’s model, the call’s deps.generateText, dev.mockSql in askdb.config.ts (every call returns that SQL, with no model call), then the registry’s model for ai in the config.
Overrides — AskOverrides
Section titled “Overrides — AskOverrides”All AskPipelineOptions fields can be overridden per call, except that the two config modes settings work as floors: a call can tighten them, never loosen them. In addition, schema, model, and dialect can be swapped for a single request:
| Field | Type | Description |
|---|---|---|
schema | SchemaSource | AnyNormalizedSchema | Override the default schema for this call only. |
model | AskDbLanguageModel | Override the default model for this call only. The registry isn’t used, so reasoningEffort doesn’t apply. |
dialect | AskDialectInput | Override the dialect for this call only. |
reasoningEffort | ReasoningEffort | Reasoning effort for this call: "minimal", "low", "medium" or "high". It takes precedence over createAskDb({ reasoningEffort }) and the config. It applies only to the registry’s model: it’s ignored with model, deps.generateText or dev.mockSql, and when deps.providerOptions is set. |
explain | boolean | Include metadata on the read-only checks in the result. |
omitSensitiveIdentifiersFromNlToSqlPrompt | boolean | Strip sensitive identifiers, and any describable text that names one, from the prompt. Config modes.omitSensitiveFromPrompt: true is a floor: true here tightens a single call, but false can’t loosen the config setting. |
abortSignal | AbortSignal | Core’s ask() option, passed through: cancels the model call (generateText({ abortSignal })). Use AbortSignal.timeout(ms) to enforce a timeout. With a built-in dialect, an aborted call rejects with SqlGenerationError; a custom AskDialect receives it as options.abortSignal and maps its own errors. |
mode | AskDbModeV1 | Trust boundary for this call. Config modes.askdbMode (unset: "schema_only") is the loosest mode a call gets: "schema_only" here tightens it, but "bounded_results" can’t loosen it. A looser request runs at the config mode, with no error. The call’s value is parsed like the config’s, so an unknown mode throws AskDbError. |
tenantScope | TenantScope | Required when the schema has a tenant policy. |
tenantSqlMode | TenantSqlOutputMode | "sql-params" (the default) binds tenant IDs through driver markers and returns them as tenantParams; "sql-only" inlines them in sql as escaped literals. A custom AskDialect has no default with a tenant scope and must set it, or the call throws TENANT_SQL_MODE_REQUIRED; see ask() options. See Executing the result. |
resolveTenantDescendants | ResolveTenantDescendants | Expands a subtree tenant scope: returns the subtree’s IDs grouped by tenant root (TenantIdsByRoot), which ask() binds to each root’s own placeholder. Required for subtree scopes. See ask() options. |
parameterize | boolean | Default true. Set false to skip unbound SQL / parameter manifest output tokens. |
sensitiveGuardrailMode | "warn" | "strict" | "off" | Default "warn". How ask() treats SQL that references identifiers marked sensitive; see ask() options. |
retriever | Retriever | RAG retriever. When supplied, retrieval replaces the full DDL, unless totalSchemaChunkCount is at or below retrievalThresholdChunks. |
retrievalK | number | Top-k for the retriever. |
retrievalThresholdChunks | number | Chunk count at or below which the full DDL is used even with a retriever (default 30). Compared with totalSchemaChunkCount. |
totalSchemaChunkCount | number | Total indexed chunk count, from buildSchemaIndex’s stats.chunksTotal. Default Infinity: with a retriever, every call retrieves. |
logger | AskDbLogger | Per-call structured logger. |
deps | AskGenerateDeps | { generateText?, providerOptions? }. generateText replaces the model call, for tests; the registry isn’t used. providerOptions is passed to the model call as is, and takes precedence over the options the client resolves from reasoningEffort. |
client.bind(prepared, values, options?)
Section titled “client.bind(prepared, values, options?)”const { preparedQuery } = await askdb.ask("How many orders shipped in March?", { tenantScope });
// Later, with a new value and no model call:const rebound = await askdb.bind(preparedQuery!, { month: "2026-04" }, { tenantScope });await pool.query(rebound.unboundSql, rebound.params);Rebinds a stored preparedQuery with new values, without calling the model. It resolves the schema the way ask() does, expands a subtree scope with core’s expandTenantScope() and your resolveTenantDescendants, and forwards to core’s bindPreparedQuery(), which re-checks the template under that schema, the scope and the modes before binding. Returns Promise<BoundQuery>: sql (with tenantParams in "sql-params" mode), unboundSql + params, bindings, and the verdict. It throws the same errors as bindPreparedQuery().
Options — BindOptions
Section titled “Options — BindOptions”| Field | Type | Description |
|---|---|---|
tenantScope | TenantScope | Required when the schema has a tenant policy. Tenant IDs are rendered from it, never from values. |
resolveTenantDescendants | ResolveTenantDescendants | Expands a subtree tenantScope, as for ask(). Required for subtree scopes. |
sensitiveGuardrailMode | "warn" | "strict" | "off" | Default "warn": sensitive references bind and are reported in verdict. "strict" refuses them. |
acceptWarnings | ReadonlyArray<"tenant"> | With a policy whose enforcement is warn, a template that fails the tenant check is refused unless this includes "tenant". |
tenantSqlMode | TenantSqlOutputMode | As for ask(). "sql-params" (the default) puts tenant markers in the rebound sql and the IDs in tenantParams; "sql-only" inlines them as escaped literals. See bindPreparedQuery(). |
dialect | DialectSpec | BuiltInDialectId | The dialect rules to check and render under. Required when ask() ran with your own DialectSpec (preparedQuery.dialectRules: "host"), and for a template stored without dialectRules. See Which dialect rules a rebind uses. |
schema | SchemaSource | AnyNormalizedSchema | Override the schema for this call only. Default: the schema ask() resolves (createAskDb({ schema }), then host.schemaJson, then host.schemaPath). |
client.reload()
Section titled “client.reload()”askdb.reload();Drops this client’s cached schema, model and resolved AI config. The next call resolves them again from the config snapshot the client was created with, so a schema path is read from disk again. To pick up a changed askdb.config.*, create a new client from a fresh getAskDbRuntimeConfig().
AskDbClient
Section titled “AskDbClient”The type createAskDb() returns:
type AskDbClient = { ask(question: string, overrides?: AskOverrides): Promise<AskPipelineResult>; bind( prepared: PreparedQuery, values: Record<string, QueryParameterValue | readonly QueryParameterValue[]>, options?: BindOptions, ): Promise<BoundQuery>; reload(): void;};Errors
Section titled “Errors”@askdb/client exports four error classes. Each extends AskDbError from @askdb/core and sets name to its class name. ask() and bind() also throw @askdb/core’s errors unchanged; see @askdb/core API → Errors.
| Class | Thrown when | Fields |
|---|---|---|
SchemaNotConfiguredError | No schema to load: no per-call schema, no createAskDb({ schema }), and neither host.schemaJson nor host.schemaPath in askdb.config.ts. | — |
SchemaLoadError | Loading a { path } or { json } schema source fails: the path doesn’t exist, the JSON is invalid, or loadSchema() throws SchemaParseError. | source: which source failed, such as host.schemaPath (./my-app.schema) or request. cause: the original error. |
DialectNotSupportedError | No dialect is set, the schema’s provider isn’t a built-in dialect id, and unknownDialect is "throw" (the default). | provider: the schema’s provider. |
ModelNotConfiguredError | The call needs the registry’s model, and the config’s ai block doesn’t resolve to one, for example because the API key is missing. Also thrown when the registry you passed comes from an @askdb/ai older than the one @askdb/client depends on. | — |
createAskDb() itself throws a plain Error when you pass both providers and registry.
Read next
Section titled “Read next”© 2026 Yahya Gilany

