@askdb/client
v1.0.0-beta.5
Published
Config-aware AskDB facade: resolves schema, model, and dialect from config so callers only pass a question.
Readme
@askdb/client
Config-aware AskDB facade. Resolves schema, model, and dialect from your runtime config so callers only pass a question.
Quick start
import { bootstrapAskDbEnv, getAskDbRuntimeConfig } from "@askdb/config";
import { openaiProvider } from "@askdb/ai-openai";
import { createAskDb } from "@askdb/client";
// bootstrapAskDbEnv() reads .env and askdb.config.* into an in-memory snapshot.
// getAskDbRuntimeConfig() then returns a typed view over that snapshot.
// Both calls are needed: bootstrap populates the store; getAskDbRuntimeConfig reads it.
bootstrapAskDbEnv();
const askdb = createAskDb({
config: getAskDbRuntimeConfig(),
providers: [openaiProvider], // the client builds its AI registry from these adapters
});
const { sql } = await askdb.ask("top 10 customers by revenue");Pass the adapter(s) for whichever ai.provider your config selects. Advanced alternative: build a registry yourself with createAiRegistry from @askdb/ai and pass it as registry instead (e.g. to share one registry across several clients) — exactly one of providers or registry is required.
Per-call overrides
All three resolution axes accept optional per-call overrides:
| Override | Type | Default |
|---|---|---|
| schema | { path } | { json } | { schema } | NormalizedSchema | From createAskDb({ schema }) → config host.schemaPath/host.schemaJson → env |
| model | AskDbLanguageModel | From registry via config ai.aiEnv |
| dialect | AskDialectInput | Config dialect → schema provider → "postgres" |
const { sql } = await askdb.ask("count active users", {
dialect: "mysql",
schema: { path: "./schemas/prod.schema" },
});Parameterized output
askdb.ask() returns the same AskPipelineResult as @askdb/core's ask(), including optional unboundSql, params, parameters, and preparedQuery when the model complies (default parameterize: true). The facade forwards options and returns the core result verbatim — no client-side binding logic.
import { bindPreparedQuery } from "@askdb/core";
const result = await askdb.ask("How many cities does Colorado have?", { tenantScope });
await pool.query(result.sql);
await pool.query(result.unboundSql!, result.params);
const rebound = bindPreparedQuery(result.preparedQuery!, {
state_name: "Utah",
":tenant_agency_ids": authorizedAgencyIds,
});
await pool.query(rebound.sql);Every ask() is still one model call. Set { parameterize: false } to opt out of the extra output tokens. bindPreparedQuery does not authorize tenant IDs — that remains the host's job when building tenantScope. Prefer params over tenantParams when using the new fields.
Multi-tenant usage
The schema and model caches are per-client-instance. For multi-tenant servers where each tenant has a different schema, either:
- Create one
AskDbClientper tenant, or - Pass per-call
schemaand/ormodeloverrides (bypasses the cache).
reload()
Drops the cached schema and model so the next ask() re-resolves them from config:
askdb.reload();onResolve hook
Inspect how schema, model, and dialect resolved on each call — useful for logging or debugging:
const askdb = createAskDb({
config,
providers: [openaiProvider],
onResolve: ({ dialect, modelSource }) => {
console.log(`dialect=${dialect.dialect} (${dialect.source}), model=${modelSource}`);
},
});