
Company News
Free Business Plan Upgrades for Open Source Maintainers
Open source maintainers are under more pressure than ever. We're raising our open source program from the Team plan to the Business plan, free.
@vistal/core
Advanced tools
The authorization layer for AI agents — zero-dependency core.
Reads an ORM schema, generates typed LLM tools, and enforces row-level security and field-level access control server-side on every query — in code, not prompts. Adapter-agnostic: works with any ORM or database through a two-method interface.
Most users should install
@vistal/prisma, which wraps this package with a Prisma adapter and schema introspection. Use@vistal/coredirectly only if you're building a custom adapter.
npm install @vistal/core
| Export | Purpose |
|---|---|
Vistal | Main class — instantiate with an adapter, register policies, get tools |
formats.anthropic / openai / gemini | Tool formatters — convert provider-neutral tools to provider-specific shapes |
PolicyViolationError, ValidationError | Error types thrown by the policy engine |
serializeResult | Serializes Decimal, Date, BigInt in query results |
Types: VistalAdapter, SchemaMap, ResolvedQuery, FilterNode, PolicyFn, PolicyResult, … | All types needed to build a custom adapter |
An adapter is two methods: introspect() returns a SchemaMap describing your resources; execute() runs a ResolvedQuery against your database.
import type { VistalAdapter, SchemaMap, ResolvedQuery } from "@vistal/core"
class MyAdapter implements VistalAdapter {
async introspect(): Promise<SchemaMap> {
return {
resources: {
order: {
name: "order",
tableName: "Order",
fields: {
id: { name: "id", type: "uuid", isId: true, isNullable: false },
tenant_id: { name: "tenant_id", type: "string", isId: false, isNullable: false },
total: { name: "total", type: "number", isId: false, isNullable: false },
status: { name: "status", type: "enum", isId: false, isNullable: false, enumValues: ["pending", "shipped", "delivered"] },
},
relations: {},
},
},
}
}
async execute(query: ResolvedQuery): Promise<unknown> {
// query.resource — resource name, e.g. "order"
// query.operation — "findMany" | "findOne" | "create" | "update" | "delete" | "aggregate"
// query.filters — row filters AND-ed from the policy + the model's arguments
// query.data — write payload (create/update) with forced fields injected
// query.include — relation names to eager-load
// query.sort / query.limit / query.offset
// query.aggregations / query.groupBy
// ... translate this into your ORM/DB call
}
}
Then pass your adapter to Vistal:
import { Vistal } from "@vistal/core"
const vistal = new Vistal({
adapter: new MyAdapter(),
defaultPolicy: "deny-all",
})
Register policies per resource. Each policy is a function that receives a context object and returns what is allowed:
vistal.policy("order", (ctx) => ({
read: { tenant_id: ctx.tenant.id }, // row filter — AND-ed into every read
write: { tenant_id: ctx.tenant.id }, // force-injected on INSERT, AND-ed on UPDATE WHERE
delete: false, // delete_order tool never generated
fields: { deny: ctx.user.role === "support" ? ["internal_notes"] : [] },
relations: { items: true, customer: ctx.user.role === "admin" },
}))
// "*" is a wildcard fallback for resources without an explicit policy()
vistal.policy("*", (ctx) => ({
read: { tenant_id: ctx.tenant.id },
write: false,
delete: false,
}))
read, write, and delete accept:
| Value | Meaning |
|---|---|
true | allow |
false | deny — no tool generated for this operation |
{ field: value } | row filter (read/delete) or force-injected field (write) |
For each resource, vistal generates up to six tools depending on policy:
| Tool | Operation |
|---|---|
query_{resource} | findMany with filters, sort, pagination, relation includes |
get_{resource} | findOne by id |
create_{resource} | insert one row |
update_{resource} | update by id |
delete_{resource} | delete by id |
aggregate_{resource} | count / sum / avg / min / max with optional groupBy |
delete: false → no delete_ tool generated. A required write field that is denied and not force-injected → create_ suppressed entirely.
// Vercel AI SDK (requires `ai` peer dep)
const tools = await vistal.tools.vercel(ctx)
await generateText({ model, tools, maxSteps: 5, prompt })
// Anthropic
const tools = await vistal.tools.anthropic(ctx)
// tools[i].definition → pass to the API
// tools[i].execute(args) → dispatch on tool call
// OpenAI
const tools = await vistal.tools.openai(ctx)
// Gemini
const tools = await vistal.tools.gemini(ctx)
// Custom formatter
const tools = await vistal.tools.format(ctx, (t) => ({
id: t.name,
schema: t.parameters,
}))
Use InferResources to derive resource names from an existing typed client (e.g. Prisma):
import { Vistal, InferResources } from "@vistal/core"
import { PrismaClient } from "@prisma/client"
const prisma = new PrismaClient()
const vistal = new Vistal<DefaultContext, InferResources<typeof prisma>>({
adapter: myAdapter,
defaultPolicy: "deny-all",
})
// policy() and getTools() autocomplete and type-check resource names
vistal.policy("order", ...)
new Vistal({
adapter,
onQuery: ({ toolName, resource, operation, durationMs, error }) => {
logger.info({ toolName, resource, durationMs })
if (error) logger.error({ toolName, error: error.message })
},
})
MIT
FAQs
ORM + Access Control Layer for AI Agents
The npm package @vistal/core receives a total of 0 weekly downloads. As such, @vistal/core popularity was classified as not popular.
We found that @vistal/core demonstrated a healthy version release cadence and project activity because the last version was released less than a year ago. It has 1 open source maintainer collaborating on the project.
Did you know?

Socket for GitHub automatically highlights issues in each pull request and monitors the health of all your open source dependencies. Discover the contents of your packages and block harmful activity before you install or update your dependencies.

Company News
Open source maintainers are under more pressure than ever. We're raising our open source program from the Team plan to the Business plan, free.

Security News
The supply chain control that delays freshly published gems now covers lockfile generation and gem vendoring in Ruby projects.

Security News
During a UK cyber test, a Mythos 5 agent used sockpuppets, social engineering, and prompt injection to try to get a maintainer to merge malware.