Aviary
Packages

@dudousxd/nestjs-agent-data

createExecuteSqlTool — governed, read-only SQL as a prebuilt tool. AST-validated single SELECTs, a fail-closed table allowlist, tenant scoping, and a row cap.

pnpm add @dudousxd/nestjs-agent-data
npm install @dudousxd/nestjs-agent-data

@dudousxd/nestjs-agent-data ships one tool — executeSql — that lets the model answer questions about your real data by writing SQL, without ever holding a connection. The model proposes a query; the library validates, gates, rewrites, and caps it before your injected runner ever executes it.

import { createExecuteSqlTool, GroupTableAccessPolicy, TenantScopeRewriter } from '@dudousxd/nestjs-agent-data';

const { spec, handler } = createExecuteSqlTool({
  runner: { run: (sql) => readOnlyPool.query(sql) },
  tableAccess: new GroupTableAccessPolicy({
    roleGroups: { ADMIN: ['orders'] },
    tablesByGroup: { orders: ['orders', 'order_items'] },
  }),
  tenantScope: new TenantScopeRewriter({ tenantColumn: 'tenant_id', scopedTables: ['orders'] }),
});

createExecuteSqlTool returns a plain { spec, handler } pair (a FunctionalTool), not an @AiTool class — register it with the umbrella's provideAgentTool (or list it under AgentModule.forRoot({ tools: [...] })) rather than declaring it as a provider.

Exports

ExportWhat it is
createExecuteSqlTool(deps: ExecuteSqlDeps)Assembles the guardrail pipeline; returns { spec: ToolSpec, handler: ToolHandler }.
SqlValidatorLayer 1 — AST-parses the statement, asserts it's exactly one SELECT, extracts the referenced base tables.
TableAccessPolicyThe interface GroupTableAccessPolicy implements — canAccess(roles, table): boolean.
GroupTableAccessPolicyLayer 2 — a fail-closed, group-based table allowlist (roleGroups + tablesByGroup).
TenantScopeRewriterLayer 3 — AND-s tenantColumn = '<tenantRef>' into every scoped table's WHERE, or rejects what it can't statically verify.
injectLimitLayer 4 — wraps an un-LIMITed query in a bounding subquery.

ExecuteSqlDeps:

OptionTypeDefaultPurpose
runner{ run(sql): Promise<Record<string, unknown>[]> }Your read-only pool. The package opens no connection of its own.
tableAccessTableAccessPolicyRequired. The Layer 2 allowlist.
tenantScopeTenantScopeRewriterOptional. Omit only if tableAccess alone is sufficient isolation.
validatorSqlValidatorfresh instanceSwap in a custom validator.
maxRowsnumber100Row cap injected when the query has no LIMIT.

Defense-in-depth, not a substitute for read-only credentials

Point runner at a read-only pool with a least-privilege DB user. The four guardrails (single-SELECT, table allowlist, tenant scoping, row cap) are the mechanism; a read-only connection is your backstop.

When to use it

Reach for -data when you want the model to answer ad-hoc questions over real tables (counts, lookups, aggregates) without writing a bespoke read tool per query shape. It's a read-kind tool by design — auto-executing is safe because the pipeline, not the model, decides what's reachable.

The full pipeline — why the layer order matters, worked examples for each layer, and wiring the factory form of provideAgentTool — is in Governed SQL.

On this page