prisma-safe-ai-query 1.0.5 → 2.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -1,125 +1,506 @@
1
- # 🛡️ Prisma Safe AI Query
2
-
3
- A lightweight, enterprise-grade utility that securely translates natural language into strongly typed, safe Prisma queries using Google Gemini. By bundling `@google/genai` and `zod` directly, this package eliminates all backend boilerplate. You pass in a user prompt, and the package handles the AI orchestration, schema enforcement, and SQL-injection prevention automatically.
4
-
5
- ## 📦 Installation
6
-
7
- ```bash
8
- npm install prisma-safe-ai-query
9
- ```
10
- *(Requires `@prisma/client` as a peer dependency)*
11
-
12
- ## 🚀 Quick Start (The PrismaGeminiAgent)
13
-
14
- Instead of manually configuring the Gemini SDK, our built-in `PrismaGeminiAgent` handles everything end-to-end.
15
-
16
- ### 1. Set your API Key
17
- Ensure you have your Gemini API key in your `.env` file:
18
- ```env
19
- GEMINI_API_KEY=your_copied_api_key_here
20
- GEMINI_MODEL=gemini-3.8-flash
21
-
22
- ```
23
- *(Need an API key? Get one for free at [aistudio.google.com](https://aistudio.google.com/))*
24
-
25
- ## 💬 The Chat Flow (Frontend to Backend)
26
-
27
- This package makes it incredibly easy to build a "Chat with your Database" UI. Your frontend remains a simple chat interface, while `PrismaGeminiAgent` handles the heavy lifting on your backend.
28
-
29
- ### Step 1: The Frontend (React/Vue/Vanilla)
30
- The user types a natural language query in your chat UI (e.g., *"Show me all active users in London"*). The frontend simply POSTs this message to your backend.
31
-
32
- ```javascript
33
- // Example React submit handler
34
- async function handleChatSubmit(userInput) {
35
- const response = await fetch('/api/chat', {
36
- method: 'POST',
37
- headers: { 'Content-Type': 'application/json' },
38
- body: JSON.stringify({ userPrompt: userInput })
39
- });
40
-
41
- const result = await response.json();
42
-
43
- if (result.error) return alert(result.error);
44
-
45
- console.log("Safe Database Results:", result.data);
46
- // Render results in your chat UI...
47
- }
48
- ```
49
-
50
- ### Step 2: The Backend API Route (Next.js / Express)
51
- Your backend receives the prompt, safely converts it to a Prisma query, executes it dynamically across ANY table, and returns the data.
52
-
53
- ```typescript
54
- //Import your Prisma Client in your project
55
- //prisma v5 import
56
- import { PrismaClient } from '@prisma/client';
57
- const prisma = new PrismaClient();
58
-
59
- //prisma v6+ import from helper file
60
- const prisma = require("./helper/prisma");
61
-
62
- // You can also follow my tutorial here to learn how to install prisma v7.8.0 in your project
63
- ```
64
- *https://charisintelligence.com.ng/prisma-v7-express-postgresql/*
65
-
66
-
67
- ```typescript
68
- import { PrismaGeminiAgent } from 'prisma-safe-ai-query';
69
- // Automatically picks up GEMINI_API_KEY from process.env
70
- const aiAgent = new PrismaGeminiAgent();
71
-
72
- export async function POST(req: Request) {
73
- const { userPrompt } = await req.json(); // e.g. "Show me all active users in London"
74
-
75
- try {
76
- // 1. One line handles Gemini generation, dynamic routing, and Zod security!
77
- const { targetTable, where } = await aiAgent.generateSafeQuery(userPrompt, {
78
- dbContext: "Available tables are user, product, and order.", // Helps Gemini route correctly
79
- blockList: ['password', 'credit_card'] // Automatically blocks these columns
80
- });
81
-
82
- // 2. DYNAMIC ROUTING: Validate the table and execute
83
- if (!targetTable || !(targetTable in prisma)) {
84
- return new Response(JSON.stringify({ error: "Invalid table requested." }), { status: 400 });
85
- }
86
-
87
- // Executes equivalent of prisma.user.findMany(...)
88
- const dbResults = await (prisma as any)[targetTable].findMany({
89
- where: where,
90
- take: 10
91
- });
92
-
93
- // 3. Return the data to the frontend chat UI
94
- return new Response(JSON.stringify({ data: dbResults }), { status: 200 });
95
-
96
- } catch (error) {
97
- return new Response(JSON.stringify({ error: error.message }), { status: 400 });
98
- }
99
- }
100
- ```
101
-
102
- ## 🔒 Security Configuration
103
-
104
- You can strictly control what the AI can and cannot access by passing a configuration object as the second parameter to the aiAgent.generateSafeQuery() method.
105
-
106
- Where to configure this: You should update these settings directly inside your backend controller or API route file (e.g., app/api/chat/route.ts in Next.js, or your Express endpoint function) exactly where you process the incoming chat request.
107
-
108
- ```typescript
109
- const { targetTable, where } = await aiAgent.generateSafeQuery(userPrompt, {
110
-
111
- // OPTION 1: Describe your DB to Gemini so it doesn't hallucinate tables. A fast hack is to use AI such as chatgpt to generate your dbContext by giving it your prisma schema
112
- dbContext: "We have tables: user, product. User has fields: email, role, status.",
113
-
114
- // OPTION 2: Block specific sensitive fields across all tables
115
- blockList: ['passwordHash', 'billingDetails'],
116
-
117
- // OPTION 3: Strict Mode - allow ONLY these fields
118
- // allowList: ['status', 'role', 'city']
119
- });
120
- ```
121
-
122
- ## 👨‍💻 Author
123
- https://www.linkedin.com/in/taiwo-olugbenga
124
- https://github.com/oluibk2020/
125
- Created by **Olugbenga Taiwo**
1
+ # 🛡️ Prisma Safe AI Query
2
+
3
+ [![npm version](https://img.shields.io/npm/v/prisma-safe-ai-query.svg?color=blue)](https://www.npmjs.com/package/prisma-safe-ai-query)
4
+ [![License: MIT](https://img.shields.io/badge/License-MIT-green.svg)](https://opensource.org/licenses/MIT)
5
+ [![Build & Tests](https://img.shields.io/badge/tests-89%20passing-brightgreen.svg)](https://github.com/gbengataiwo)
6
+
7
+ > **Translate Natural Language into Safe, Typed, Multi-Level Prisma Queries with Google Gemini.**
8
+
9
+ Eliminate backend AI prompt engineering, parsing logic, and SQL injection risks. `prisma-safe-ai-query` takes user prompts (e.g., *"Who are the inactive subscribers and what were their last login attempts?"*), auto-inspects your Prisma schema, and generates fully structured, nested Prisma queries with dynamic table targeting, recursive field security, and type enforcement.
10
+
11
+ ---
12
+
13
+ ## 📑 Table of Contents
14
+
15
+ - [🛡️ Prisma Safe AI Query](#️-prisma-safe-ai-query)
16
+ - [📑 Table of Contents](#-table-of-contents)
17
+ - [⚡ Why Prisma Safe AI Query?](#-why-prisma-safe-ai-query)
18
+ - [🏗️ Architecture Overview](#️-architecture-overview)
19
+ - [📦 Installation](#-installation)
20
+ - [⚙️ Environment Setup](#️-environment-setup)
21
+ - [💬 Natural Language vs. Raw Structured Response](#-natural-language-vs-raw-structured-response)
22
+ - [🖥️ Backend Setup (Next.js, Express, Fastify)](#️-backend-setup-nextjs-express-fastify)
23
+ - [Next.js App Router (`app/api/chat-query/route.ts`)](#nextjs-app-router-appapichat-queryroutets)
24
+ - [Express.js Example (`server.ts` or `server.js`)](#expressjs-example-serverts-or-serverjs)
25
+ - [🎨 Frontend Integration (React, Vue.js)](#-frontend-integration-react-vuejs)
26
+ - [1. React / Next.js Client Component](#1-react--nextjs-client-component)
27
+ - [🔗 Multi-Level Relational Queries](#-multi-level-relational-queries)
28
+ - [🧬 Zero-Config Schema Introspection](#-zero-config-schema-introspection)
29
+ - [Privacy \& Security Guarantee](#privacy--security-guarantee)
30
+ - [🔒 Security \& Access Control](#-security--access-control)
31
+ - [Fail-Closed Behavior](#fail-closed-behavior)
32
+ - [🤖 Dynamic Gemini Model Selection](#-dynamic-gemini-model-selection)
33
+ - [🛠️ Standalone Parser for Custom LLMs](#️-standalone-parser-for-custom-llms)
34
+ - [📖 API Reference](#-api-reference)
35
+ - [`new PrismaGeminiAgent(apiKey?: string, model?: string)`](#new-prismageminiagentapikey-string-model-string)
36
+ - [`agent.generateSafeQuery(prompt: string, config?: AgentConfig)`](#agentgeneratesafequeryprompt-string-config-agentconfig)
37
+ - [`agent.execute(prompt: string, prismaClient: PrismaClient, config?: AgentConfig)`](#agentexecute-prompt-string-prismaclient-prismaclient-config-agentconfig)
38
+ - [Standalone schema utilities](#standalone-schema-utilities)
39
+ - [Returns `ParsedDynamicQuery`:](#returns-parseddynamicquery)
40
+ - [📄 License](#-license)
41
+
42
+ ---
43
+
44
+ ## ⚡ Why Prisma Safe AI Query?
45
+
46
+ - **Zero SQL / Raw Injection Risk**: Never executes unvalidated strings or concatenated queries. Outputs strictly validated Prisma query AST objects (`where`, `include`, `select`, `orderBy`, `take`).
47
+ - **Multi-Level Relational Queries**: Connects multiple models automatically (e.g., `Subscription` -> `User` -> `LoginAttempts`).
48
+ - **Zero-Config Schema Introspection**: Reads your `schema.prisma` automatically without sending sensitive credentials or database URLs to the AI.
49
+ - **Deep Recursive Security**: Blocks unauthorized columns and relational paths (like `passwordHash` or `user.privateMeta`) across root and nested levels.
50
+ - **Universal Compatibility**: Works out of the box with `@prisma/client` (`v5.x`, `v6.x`, `v7.x+`) and Zod (`v3.x` & `v4.x`).
51
+
52
+ ---
53
+
54
+ ## 🏗️ Architecture Overview
55
+
56
+ ```
57
+ User (Browser Chat UI)
58
+ │
59
+ ▼ POST /api/ai-query { prompt: "Show inactive subscribers and their last login" }
60
+ Backend API Route
61
+ │
62
+ ├─► PrismaGeminiAgent.execute(prompt, prisma)
63
+ │ ├── 1. Auto-introspects schema.prisma (models, fields, relations)
64
+ │ ├── 2. Calls Google Gemini (structured JSON mode)
65
+ │ └── 3. Validates with Zod & enforces blockList/allowList recursively
66
+ │
67
+ ├─► Prisma Client findMany execution
68
+ └─► Optional natural-language summary
69
+ │
70
+ ▼ Returns: { data, query, naturalLanguage? }
71
+ Frontend Render
72
+ ```
73
+
74
+ ---
75
+
76
+ ## 📦 Installation
77
+
78
+ ```bash
79
+ npm install prisma-safe-ai-query
80
+ ```
81
+
82
+ > **Requirements:** Ensure your project has `@prisma/client` (`>=5.0.0`) and `zod` installed.
83
+
84
+ ---
85
+
86
+ ## ⚙️ Environment Setup
87
+
88
+ Add your Gemini credentials and optional model selection to your `.env` file:
89
+
90
+ ```env
91
+ # Required: Get a free API key at https://aistudio.google.com/
92
+ GEMINI_API_KEY=your_gemini_api_key_here
93
+
94
+ # Optional: Choose a Gemini model (defaults to gemini-3.1-flash-lite-preview)
95
+ GEMINI_MODEL=gemini-2.5-flash
96
+
97
+ # Optional: Return a human-like conversational answer alongside structured data (defaults to true)
98
+ # Set to false to return ONLY raw database records
99
+ NATURAL_LANGUAGE_RESPONSE=true
100
+ ```
101
+
102
+ ---
103
+
104
+ ## 💬 Natural Language vs. Raw Structured Response
105
+
106
+ `prisma-safe-ai-query` gives you the best of both worlds:
107
+
108
+ - **When `NATURAL_LANGUAGE_RESPONSE=true` (Default)**:
109
+ Returns conversational human text answering the prompt **plus** the raw database records:
110
+ > *"There is only one inactive subscriber and the full name is Kate Michael and his last logged In date is 28th April 2026"*
111
+ - **When `NATURAL_LANGUAGE_RESPONSE=false`**:
112
+ Bypasses the second AI pass and returns **only** the exact structured JSON records from Prisma.
113
+
114
+ You can also toggle this dynamically per request in code:
115
+
116
+ ```typescript
117
+ // Explicit override in code:
118
+ const result = await aiAgent.execute(userPrompt, prisma, {
119
+ naturalLanguageResponse: false // forces raw database results only
120
+ });
121
+ ```
122
+
123
+ The `execute()` result contains the structured `data` and sanitized `query`. When natural language output is enabled, it also contains `naturalLanguage`. This method executes a Prisma `findMany` call, so the supplied Prisma client must expose a delegate for the selected model.
124
+
125
+ ---
126
+
127
+ ## 🖥️ Backend Setup (Next.js, Express, Fastify)
128
+
129
+ Here is a full backend API route demonstrating how to receive a prompt, translate it, and execute it across dynamic tables.
130
+
131
+ ### Next.js App Router (`app/api/chat-query/route.ts`)
132
+
133
+ ```typescript
134
+ import { NextResponse } from 'next/server';
135
+ import { PrismaGeminiAgent } from 'prisma-safe-ai-query';
136
+ import { PrismaClient } from '@prisma/client';
137
+
138
+ const prisma = new PrismaClient();
139
+ const aiAgent = new PrismaGeminiAgent();
140
+
141
+ export async function POST(req: Request) {
142
+ try {
143
+ const { prompt } = await req.json();
144
+
145
+ if (!prompt) {
146
+ return NextResponse.json({ error: "Prompt is required" }, { status: 400 });
147
+ }
148
+
149
+ // Single one-liner: generates safe query, executes on Prisma, and formats response
150
+ const result = await aiAgent.execute(prompt, prisma, {
151
+ blockList: ['password', 'passwordHash', 'creditCard', 'refreshToken']
152
+ });
153
+
154
+ return NextResponse.json({
155
+ targetTable: result.query.targetTable,
156
+ data: result.data,
157
+ naturalLanguage: result.naturalLanguage // e.g. "There is only one inactive subscriber..."
158
+ });
159
+
160
+ } catch (error: any) {
161
+ return NextResponse.json(
162
+ { error: error.message || "Query generation failed" },
163
+ { status: 400 }
164
+ );
165
+ }
166
+ }
167
+ ```
168
+
169
+ ### Express.js Example (`server.ts` or `server.js`)
170
+
171
+ **Using TypeScript or JavaScript (ES Modules):**
172
+
173
+ ```javascript
174
+ import express from 'express';
175
+ import { PrismaGeminiAgent } from 'prisma-safe-ai-query';
176
+ import { PrismaClient } from '@prisma/client';
177
+
178
+ const app = express();
179
+ app.use(express.json());
180
+
181
+ const prisma = new PrismaClient();
182
+ const aiAgent = new PrismaGeminiAgent();
183
+
184
+ app.post('/api/chat-query', async (req, res) => {
185
+ try {
186
+ const { prompt } = req.body;
187
+
188
+ // Single call: generates query, executes on Prisma, and returns data + naturalLanguage
189
+ const result = await aiAgent.execute(prompt, prisma, {
190
+ blockList: ['password', 'secretToken', 'refreshToken']
191
+ });
192
+
193
+ res.json({
194
+ targetTable: result.query.targetTable,
195
+ data: result.data,
196
+ naturalLanguage: result.naturalLanguage // e.g. "There is only one inactive subscriber..."
197
+ });
198
+ } catch (err) {
199
+ res.status(400).json({ error: err.message });
200
+ }
201
+ });
202
+
203
+ app.listen(3000, () => console.log('Server running on port 3000'));
204
+ ```
205
+
206
+ **Using Node.js CommonJS (`require`):**
207
+
208
+ ```javascript
209
+ const express = require('express');
210
+ const { PrismaGeminiAgent } = require('prisma-safe-ai-query');
211
+ const { PrismaClient } = require('@prisma/client');
212
+
213
+ const app = express();
214
+ app.use(express.json());
215
+
216
+ const prisma = new PrismaClient();
217
+ const aiAgent = new PrismaGeminiAgent();
218
+
219
+ app.post('/api/chat-query', async (req, res) => {
220
+ try {
221
+ const { prompt } = req.body;
222
+
223
+ const result = await aiAgent.execute(prompt, prisma, {
224
+ blockList: ['password', 'creditCard']
225
+ });
226
+
227
+ res.json({
228
+ targetTable: result.query.targetTable,
229
+ data: result.data,
230
+ naturalLanguage: result.naturalLanguage
231
+ });
232
+ } catch (err) {
233
+ res.status(400).json({ error: err.message });
234
+ }
235
+ });
236
+
237
+ app.listen(3000, () => console.log('Server running on port 3000'));
238
+ ```
239
+
240
+ ---
241
+
242
+ ## 🎨 Frontend Integration (React, Vue.js)
243
+
244
+ Because database operations must run securely on your server, your frontend connects to your backend API endpoint.
245
+
246
+ ### 1. React / Next.js Client Component
247
+
248
+ ```tsx
249
+ import React, { useState } from 'react';
250
+
251
+ export default function DatabaseChat() {
252
+ const [prompt, setPrompt] = useState('');
253
+ const [loading, setLoading] = useState(false);
254
+ const [response, setResponse] = useState<any>(null);
255
+ const [error, setError] = useState<string | null>(null);
256
+
257
+ const handleSubmit = async (e: React.FormEvent) => {
258
+ e.preventDefault();
259
+ setLoading(true);
260
+ setError(null);
261
+
262
+ try {
263
+ const res = await fetch('/api/chat-query', {
264
+ method: 'POST',
265
+ headers: { 'Content-Type': 'application/json' },
266
+ body: JSON.stringify({ prompt }),
267
+ });
268
+
269
+ const json = await res.json();
270
+ if (!res.ok) throw new Error(json.error || 'Request failed');
271
+
272
+ setResponse(json);
273
+ } catch (err: any) {
274
+ setError(err.message);
275
+ } finally {
276
+ setLoading(false);
277
+ }
278
+ };
279
+
280
+ return (
281
+ <div style={{ maxWidth: 800, margin: '2rem auto', fontFamily: 'sans-serif' }}>
282
+ <h2>💬 Natural Language Database Query</h2>
283
+
284
+ <form onSubmit={handleSubmit} style={{ display: 'flex', gap: '0.5rem' }}>
285
+ <input
286
+ type="text"
287
+ value={prompt}
288
+ onChange={(e) => setPrompt(e.target.value)}
289
+ placeholder="e.g. Find inactive subscribers and show their full name and last login"
290
+ style={{ flex: 1, padding: '0.75rem', borderRadius: 6, border: '1px solid #ccc' }}
291
+ />
292
+ <button type="submit" disabled={loading} style={{ padding: '0.75rem 1.5rem', cursor: 'pointer' }}>
293
+ {loading ? 'Querying...' : 'Ask DB'}
294
+ </button>
295
+ </form>
296
+
297
+ {error && <p style={{ color: 'red', marginTop: '1rem' }}>❌ {error}</p>}
298
+
299
+ {response && (
300
+ <div style={{ marginTop: '1.5rem', background: '#f5f5f5', padding: '1.25rem', borderRadius: 8 }}>
301
+ {/* Natural Language Response */}
302
+ {response.naturalLanguage && (
303
+ <div style={{ background: '#e8f4fd', borderLeft: '4px solid #0288d1', padding: '0.75rem 1rem', marginBottom: '1rem', borderRadius: 4 }}>
304
+ <strong>🤖 AI Answer:</strong>
305
+ <p style={{ margin: '0.25rem 0 0' }}>{response.naturalLanguage}</p>
306
+ </div>
307
+ )}
308
+
309
+ <h4>Target Model: <code>{response.targetTable}</code> ({response.data.length} records)</h4>
310
+ <pre style={{ maxHeight: 350, overflow: 'auto', background: '#fff', padding: '1rem', borderRadius: 4 }}>
311
+ {JSON.stringify(response.data, null, 2)}
312
+ </pre>
313
+ </div>
314
+ )}
315
+ </div>
316
+ );
317
+ }
318
+ ```
319
+
320
+ ---
321
+
322
+ ## 🔗 Multi-Level Relational Queries
323
+
324
+ When users ask relational questions across connected tables, `prisma-safe-ai-query` constructs nested Prisma relations:
325
+
326
+ **Prompt:**
327
+ > *"Who are the inactive subscribers on the database and tell me their full names and their last login attempt?"*
328
+
329
+ **Generated Query Object:**
330
+ ```javascript
331
+ {
332
+ targetTable: 'subscription',
333
+ where: {
334
+ AND: [
335
+ { status: { equals: 'inactive' } }
336
+ ]
337
+ },
338
+ include: {
339
+ user: {
340
+ include: {
341
+ loginAttempts: {
342
+ take: 1,
343
+ orderBy: { createdAt: 'desc' }
344
+ }
345
+ }
346
+ }
347
+ }
348
+ }
349
+ ```
350
+
351
+ This output matches standard Prisma syntax directly:
352
+ ```typescript
353
+ const result = await prisma[query.targetTable].findMany({
354
+ where: query.where,
355
+ include: query.include
356
+ });
357
+ ```
358
+
359
+ ---
360
+
361
+ ## 🧬 Zero-Config Schema Introspection
362
+
363
+ Schema introspection is enabled by default. The agent looks for `./prisma/schema.prisma` and then `./schema.prisma`; you can provide a custom path or an initialized Prisma client:
364
+
365
+ ```typescript
366
+ // 1. Automatic: Finds ./prisma/schema.prisma or ./schema.prisma
367
+ const query = await aiAgent.generateSafeQuery(prompt);
368
+
369
+ // 2. Custom schema location
370
+ const query = await aiAgent.generateSafeQuery(prompt, {
371
+ schemaPath: './db/prisma/schema.prisma'
372
+ });
373
+
374
+ // 3. Or pass your initialized PrismaClient directly
375
+ const query = await aiAgent.generateSafeQuery(prompt, {
376
+ prismaClient: prisma
377
+ });
378
+
379
+ // 4. Disable automatic schema discovery and provide your own context
380
+ const query = await aiAgent.generateSafeQuery(prompt, {
381
+ autoIntrospect: false,
382
+ dbContext: 'Model User { id: Int, email: String }'
383
+ });
384
+ ```
385
+
386
+ ### Privacy & Security Guarantee
387
+ The introspection utilities extract only model and field metadata for:
388
+ - Models & relation fields
389
+ - Scalar columns & types
390
+ - Enums & enum values
391
+
392
+ Datasource URLs and generator configuration are not included in the formatted context sent to Gemini. `parsePrismaSchemaText`, `extractPrismaSchema`, `formatSchemaForAIContext`, and `findPrismaSchemaPath` are also exported for applications that need to inspect or format schema metadata directly.
393
+
394
+ ---
395
+
396
+ ## 🔒 Security & Access Control
397
+
398
+ Control what tables, columns, and relations the AI is permitted to touch using recursive validation rules:
399
+
400
+ ```typescript
401
+ const query = await aiAgent.generateSafeQuery(prompt, {
402
+ // Option A: Block specific sensitive fields anywhere in the query
403
+ blockList: [
404
+ 'passwordHash', // Blocks any column named passwordHash
405
+ 'creditCardNumber', // Blocks creditCardNumber
406
+ 'auditLogs', // Blocks the auditLogs table
407
+ 'user.privateNotes' // Blocks relational dot-paths
408
+ ],
409
+
410
+ // Option B: Allow only these target models and fields / relation paths
411
+ // A model entry permits the target model; it does not by itself permit every field.
412
+ // allowList: ['user', 'email', 'fullName']
413
+ });
414
+ ```
415
+
416
+ Rules support exact names and dot-path descendants. For example, `user.email` permits that relation path, and `user` permits a target table named `user`. Add explicit field names when filtering by scalar columns. `blockList` takes precedence over `allowList`.
417
+
418
+ ### Fail-Closed Behavior
419
+ If an AI attempt targets a blocked column (e.g. `include: { user: { select: { passwordHash: true } } }`), the engine throws a `Security Exception` immediately before execution.
420
+
421
+ ---
422
+
423
+ ## 🤖 Dynamic Gemini Model Selection
424
+
425
+ Switch Gemini models dynamically via environment variables or per agent instance:
426
+
427
+ ```env
428
+ # In your .env file
429
+ GEMINI_MODEL=gemini-2.5-flash
430
+ ```
431
+
432
+ Or pass it directly in code:
433
+
434
+ ```typescript
435
+ // 1. Explicit model override in constructor
436
+ const agent = new PrismaGeminiAgent('api-key-here', 'gemini-1.5-pro');
437
+
438
+ // 2. Default fallback
439
+ const agent = new PrismaGeminiAgent(); // Uses GEMINI_MODEL env or gemini-3.1-flash-lite-preview
440
+ ```
441
+
442
+ ---
443
+
444
+ ## 🛠️ Standalone Parser for Custom LLMs
445
+
446
+ If you use OpenAI, Anthropic Claude, or local Ollama models, you can still use the core sanitization engine independently:
447
+
448
+ ```typescript
449
+ import { PrismaAIEngine } from 'prisma-safe-ai-query';
450
+
451
+ // Accepts raw LLM JSON string or parsed object
452
+ const query = PrismaAIEngine.parseDynamicQuery(rawAiResponse, {
453
+ blockList: ['password', 'secretKey']
454
+ });
455
+
456
+ console.log(query.targetTable); // 'user'
457
+ console.log(query.where); // { AND: [...] }
458
+ console.log(query.include); // Nested relations
459
+ ```
460
+
461
+ ---
462
+
463
+ ## 📖 API Reference
464
+
465
+ ### `new PrismaGeminiAgent(apiKey?: string, model?: string)`
466
+ Creates a new AI agent instance.
467
+ - `apiKey`: Optional. Defaults to `process.env.GEMINI_API_KEY`.
468
+ - `model`: Optional. Defaults to `process.env.GEMINI_MODEL` or `"gemini-3.1-flash-lite-preview"`.
469
+
470
+ ### `agent.generateSafeQuery(prompt: string, config?: AgentConfig)`
471
+ Generates and sanitizes a complete Prisma query.
472
+ - `config.blockList`: Array of string patterns to block.
473
+ - `config.allowList`: Array of allowed models and fields (strict mode).
474
+ - `config.schemaPath`: Path to `schema.prisma`.
475
+ - `config.prismaClient`: PrismaClient instance for introspection.
476
+ - `config.autoIntrospect`: Boolean (default: `true`).
477
+ - `config.dbContext`: Custom text to supplement database context.
478
+ - `config.schemaContext`: Optional extracted schema metadata used to coerce filter values to Prisma field types.
479
+ - `config.naturalLanguageResponse`: Controls `execute()` summaries; defaults to `NATURAL_LANGUAGE_RESPONSE`, then `true`.
480
+
481
+ ### `agent.execute(prompt: string, prismaClient: PrismaClient, config?: AgentConfig)`
482
+ Generates a safe query, executes it through the matching Prisma model's `findMany`, and returns `{ data, query, naturalLanguage? }`. It can use an explicit `select` or `include`, sorting, and pagination from the generated query. If neither `select` nor `include` is provided, it may build a field selection from introspected model metadata when access rules are configured. When the prompt requests related data but no relation was included, it can also attempt to fetch related records through detected foreign-key fields. Blocked or non-allowlisted data is filtered from the returned records.
483
+
484
+ When `naturalLanguageResponse` is disabled, `naturalLanguage` is omitted and no summary generation call is made.
485
+
486
+ ### Standalone schema utilities
487
+ The package also exports `extractPrismaSchema`, `parsePrismaSchemaText`, `formatSchemaForAIContext`, `findPrismaSchemaPath`, and the `ExtractedSchema`, `ExtractedModel`, `ExtractedField`, and `ExtractedEnum` types. `PrismaAIEngine.filterBlockedData(data, config?)` is available to recursively filter records with the same security configuration.
488
+
489
+ ### Returns `ParsedDynamicQuery`:
490
+ ```typescript
491
+ interface ParsedDynamicQuery {
492
+ targetTable: string;
493
+ where: Record<string, any>;
494
+ include?: Record<string, any>;
495
+ select?: Record<string, any>;
496
+ orderBy?: any;
497
+ take?: number;
498
+ skip?: number;
499
+ }
500
+ ```
501
+
502
+ ---
503
+
504
+ ## 📄 License
505
+
506
+ MIT © [Olugbenga Taiwo](https://github.com/gbengataiwo)