Sign In

@vistal/prisma

Package Overview
Dependencies
Maintainers
1
Versions
7
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

@vistal/prisma - npm Package Compare versions

Comparing version
0.1.0
to
0.1.1
+163
README.md
# @vistal/prisma
**Row-level security and access control for AI agents — Prisma adapter.**
[![npm](https://img.shields.io/npm/v/@vistal/prisma)](https://www.npmjs.com/package/@vistal/prisma) [![license](https://img.shields.io/npm/l/@vistal/prisma)](../../LICENSE) [![TypeScript](https://img.shields.io/badge/types-TypeScript-blue)](./src/index.ts)
Reads your Prisma schema, generates typed LLM tools, and enforces row-level security and field-level access control server-side on every query — in code, not prompts.
---
## Installation
```bash
npm install @vistal/prisma @vistal/core
npm install --save-dev prisma
```
Requires Prisma 5+ and `@prisma/client` as peer dependencies.
---
## Setup
```ts
import { PrismaClient } from "@prisma/client"
import { createVistal } from "@vistal/prisma"
const prisma = new PrismaClient()
const vistal = createVistal(prisma, { defaultPolicy: "deny-all" })
```
`createVistal` infers resource names from your Prisma client type — policy keys are type-checked, so a typo is a compile error. Prisma model names are converted to `snake_case` resource names (`OrderItem` → `order_item`).
If your schema isn't at `./prisma/schema.prisma`, pass `schemaPath`:
```ts
const vistal = createVistal(prisma, {
defaultPolicy: "deny-all",
schemaPath: "./db/schema.prisma",
})
```
---
## Schema annotations
Use `///` doc comments to give the LLM better context and mark fields that should never leave the server:
```prisma
/// @vistal:description "A customer purchase order"
model Order {
id String @id @default(uuid())
tenant_id String
status OrderStatus
total Decimal
/// @vistal:description "Order total in cents"
total Decimal
/// @vistal:sensitive
internal_notes String?
}
```
| Annotation | Effect |
|---|---|
| `@vistal:description "..."` | Added to the tool description so the LLM understands the resource or field |
| `@vistal:sensitive` | Field is stripped at introspection — never appears in tool schemas, arguments, or results |
`@vistal:sensitive` is enforced before policy runs. The field doesn't exist as far as the LLM is concerned.
---
## Policies
```ts
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 into 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" },
}))
// 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) |
---
## Connecting to your LLM provider
```ts
// Vercel AI SDK (requires the `ai` package)
const tools = await vistal.tools.vercel(ctx)
const { text } = await generateText({ model, tools, maxSteps: 8, prompt })
// Anthropic
const tools = await vistal.tools.anthropic(ctx)
await anthropic.messages.create({ tools: tools.map(t => t.definition) })
const result = await tools.find(t => t.name === block.name)!.execute(block.input)
// OpenAI
const tools = await vistal.tools.openai(ctx)
// Gemini
const tools = await vistal.tools.gemini(ctx)
```
---
## How Prisma model names map to resource names
Prisma model names (PascalCase) are converted to snake_case resource names:
| Prisma model | vistal resource |
|---|---|
| `Order` | `order` |
| `OrderItem` | `order_item` |
| `UserProfile` | `user_profile` |
These are the strings you pass to `policy()` and that appear in generated tool names (`query_order_item`, `create_order_item`, …).
---
## Security properties
| Property | How Prisma enforces it |
|---|---|
| Row filters on read | `where` clause passed to `findMany` / `findFirst` |
| Write scoping | `write: { tenant_id }` is injected into `create` data and AND-ed into `updateMany` / `deleteMany` WHERE — cross-tenant records won't match |
| `update` / `delete` use `Many` | Ensures the full policy `where` (id + forced filter) is applied — a mismatched tenant gets `{ count: 0 }`, not an error that leaks existence |
| `belongsTo` relation filters | Enforced post-fetch in memory (Prisma doesn't support `where` on to-one includes) |
| Sensitive fields | Stripped at introspection via `@vistal:sensitive` — never passed to Prisma in `select`, `data`, or returned in results |
---
## Exports
| Export | Purpose |
|---|---|
| `createVistal(prisma, config?)` | Main entry point — creates a `Vistal` instance with the Prisma adapter wired up and resource types inferred |
| `PrismaAdapter` | The adapter class if you need to instantiate it separately |
| `translateFilter` | Converts a vistal `FilterNode` to a Prisma `where` object — useful when building a custom adapter on top of Prisma |
---
## License
MIT
+17
-2
{
"name": "@vistal/prisma",
"version": "0.1.0",
"description": "Prisma adapter for vistal",
"version": "0.1.1",
"description": "Prisma adapter for vistal — row-level security and access control for AI agents",
"keywords": [
"prisma",
"ai agent",
"llm",
"access control",
"authorization",
"row-level security",
"multi-tenant",
"tool use",
"function calling",
"vercel ai sdk",
"anthropic",
"openai",
"typescript"
],
"author": "Nicolò D'Addabbo <nicolodaddabbo@gmail.com> (https://nicolod.com)",

@@ -6,0 +21,0 @@ "license": "MIT",