prisma-safe-ai-query 0.0.0-stage → 1.0.6

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,3 +1,124 @@
1
- # Temporary Holding Version
1
+ # 🛡️ Prisma Safe AI Query
2
2
 
3
- This version is a temporary placeholder for this package. An operational version to replace this has been submitted for review and is awaiting a staged release.
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_model_name_here
21
+ ```
22
+ *(Need an API key? Get one for free at [aistudio.google.com](https://aistudio.google.com/))*
23
+
24
+ ## 💬 The Chat Flow (Frontend to Backend)
25
+
26
+ 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.
27
+
28
+ ### Step 1: The Frontend (React/Vue/Vanilla)
29
+ 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.
30
+
31
+ ```javascript
32
+ // Example React submit handler
33
+ async function handleChatSubmit(userInput) {
34
+ const response = await fetch('/api/chat', {
35
+ method: 'POST',
36
+ headers: { 'Content-Type': 'application/json' },
37
+ body: JSON.stringify({ userPrompt: userInput })
38
+ });
39
+
40
+ const result = await response.json();
41
+
42
+ if (result.error) return alert(result.error);
43
+
44
+ console.log("Safe Database Results:", result.data);
45
+ // Render results in your chat UI...
46
+ }
47
+ ```
48
+
49
+ ### Step 2: The Backend API Route (Next.js / Express)
50
+ Your backend receives the prompt, safely converts it to a Prisma query, executes it dynamically across ANY table, and returns the data.
51
+
52
+ **Import your Prisma Client**
53
+ ```typescript
54
+ // Prisma v5 import
55
+ import { PrismaClient } from '@prisma/client';
56
+ const prisma = new PrismaClient();
57
+
58
+ // Prisma v6+ import from helper file
59
+ // const prisma = require("./helper/prisma");
60
+ ```
61
+ *(Check out my tutorial on [how to install Prisma v7.8.0 in your Express/PostgreSQL project](https://charisintelligence.com.ng/prisma-v7-express-postgresql/))*
62
+
63
+ **Execute the AI Agent**
64
+ ```typescript
65
+ import { PrismaGeminiAgent } from 'prisma-safe-ai-query';
66
+
67
+ // Automatically picks up GEMINI_API_KEY from process.env
68
+ const aiAgent = new PrismaGeminiAgent();
69
+
70
+ export async function POST(req: Request) {
71
+ const { userPrompt } = await req.json(); // e.g. "Show me all active users in London"
72
+
73
+ try {
74
+ // 1. One line handles Gemini generation, dynamic routing, and Zod security!
75
+ const { targetTable, where } = await aiAgent.generateSafeQuery(userPrompt, {
76
+ dbContext: "Available tables are user, product, and order.", // Helps Gemini route correctly
77
+ blockList: ['password', 'credit_card'] // Automatically blocks these columns
78
+ });
79
+
80
+ // 2. DYNAMIC ROUTING: Validate the table and execute
81
+ if (!targetTable || !(targetTable in prisma)) {
82
+ return new Response(JSON.stringify({ error: "Invalid table requested." }), { status: 400 });
83
+ }
84
+
85
+ // Executes equivalent of prisma.user.findMany(...)
86
+ const dbResults = await (prisma as any)[targetTable].findMany({
87
+ where: where,
88
+ take: 10
89
+ });
90
+
91
+ // 3. Return the data to the frontend chat UI
92
+ return new Response(JSON.stringify({ data: dbResults }), { status: 200 });
93
+
94
+ } catch (error) {
95
+ return new Response(JSON.stringify({ error: error.message }), { status: 400 });
96
+ }
97
+ }
98
+ ```
99
+
100
+ ## 🔒 Security Configuration
101
+
102
+ 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.
103
+
104
+ **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.
105
+
106
+ ```typescript
107
+ const { targetTable, where } = await aiAgent.generateSafeQuery(userPrompt, {
108
+
109
+ // OPTION 1: Describe your DB to Gemini so it doesn't hallucinate tables.
110
+ // A fast hack is to use an AI (like ChatGPT) to generate your dbContext by giving it your prisma schema.
111
+ dbContext: "We have tables: user, product. User has fields: email, role, status.",
112
+
113
+ // OPTION 2: Block specific sensitive fields across all tables
114
+ blockList: ['passwordHash', 'billingDetails'],
115
+
116
+ // OPTION 3: Strict Mode - allow ONLY these fields
117
+ // allowList: ['status', 'role', 'city']
118
+ });
119
+ ```
120
+
121
+ ## 👨‍💻 Author
122
+ Created by **Olugbenga Taiwo**
123
+ - [LinkedIn](https://www.linkedin.com/in/taiwo-olugbenga)
124
+ - [GitHub Profile](https://github.com/oluibk2020/)
@@ -0,0 +1,17 @@
1
+ import { SecurityConfig } from "./engine";
2
+ export interface AgentConfig extends SecurityConfig {
3
+ /** Describe your database tables/columns to help Gemini generate accurate queries (e.g. "Tables available: user, product") */
4
+ dbContext?: string;
5
+ }
6
+ export declare class PrismaGeminiAgent {
7
+ private ai;
8
+ private model;
9
+ constructor(apiKey?: string, model?: string);
10
+ /**
11
+ * Translates a natural language prompt directly into a validated Prisma query.
12
+ */
13
+ generateSafeQuery(userPrompt: string, config?: AgentConfig): Promise<{
14
+ targetTable?: string;
15
+ where: any;
16
+ }>;
17
+ }
package/dist/agent.js ADDED
@@ -0,0 +1,78 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.PrismaGeminiAgent = void 0;
4
+ const genai_1 = require("@google/genai");
5
+ const engine_1 = require("./engine");
6
+ const DEFAULT_MODEL = "gemini-3.1-flash-lite-preview";
7
+ class PrismaGeminiAgent {
8
+ constructor(apiKey, model) {
9
+ const key = apiKey || process.env.GEMINI_API_KEY;
10
+ if (!key) {
11
+ throw new Error("FATAL ERROR: GEMINI_API_KEY is missing. Pass it to the constructor or set it in your .env file.");
12
+ }
13
+ this.ai = new genai_1.GoogleGenAI({ apiKey: key });
14
+ this.model = model || process.env.GEMINI_MODEL || DEFAULT_MODEL;
15
+ }
16
+ /**
17
+ * Translates a natural language prompt directly into a validated Prisma query.
18
+ */
19
+ async generateSafeQuery(userPrompt, config) {
20
+ const GeminiQuerySchema = {
21
+ type: genai_1.Type.OBJECT,
22
+ properties: {
23
+ targetTable: {
24
+ type: genai_1.Type.STRING,
25
+ description: "The dynamic database table to query (e.g., 'user', 'product')",
26
+ },
27
+ logicalOperator: { type: genai_1.Type.STRING, enum: ["AND", "OR"] },
28
+ filters: {
29
+ type: genai_1.Type.ARRAY,
30
+ items: {
31
+ type: genai_1.Type.OBJECT,
32
+ properties: {
33
+ field: { type: genai_1.Type.STRING },
34
+ operator: {
35
+ type: genai_1.Type.STRING,
36
+ enum: [
37
+ "equals",
38
+ "not",
39
+ "in",
40
+ "notIn",
41
+ "lt",
42
+ "lte",
43
+ "gt",
44
+ "gte",
45
+ "contains",
46
+ "startsWith",
47
+ "endsWith",
48
+ ],
49
+ },
50
+ value: { type: genai_1.Type.STRING },
51
+ },
52
+ required: ["field", "operator", "value"],
53
+ },
54
+ },
55
+ },
56
+ required: ["targetTable", "logicalOperator", "filters"],
57
+ };
58
+ const instruction = config?.dbContext
59
+ ? `Convert the user request to database filters. Database Context: ${config.dbContext}`
60
+ : `Convert the user request to database filters.`;
61
+ const intentResponse = await this.ai.models.generateContent({
62
+ model: this.model,
63
+ contents: [{ role: "user", parts: [{ text: userPrompt }] }],
64
+ config: {
65
+ systemInstruction: instruction,
66
+ responseMimeType: "application/json",
67
+ responseSchema: GeminiQuerySchema,
68
+ temperature: 0.1,
69
+ },
70
+ });
71
+ if (!intentResponse.text) {
72
+ throw new Error("Gemini failed to generate a response.");
73
+ }
74
+ // Pass the Gemini JSON through our Zod engine to ensure Allowlist/Blocklist rules are enforced
75
+ return engine_1.PrismaAIEngine.parseDynamicQuery(intentResponse.text, config);
76
+ }
77
+ }
78
+ exports.PrismaGeminiAgent = PrismaGeminiAgent;
@@ -0,0 +1,14 @@
1
+ export interface SecurityConfig {
2
+ /** Explicitly allow only these fields. If provided, everything else is blocked. */
3
+ allowList?: string[];
4
+ /** Explicitly block these fields or tables (supports exact match or dot-notation prefixes like 'user.password'). */
5
+ blockList?: string[];
6
+ }
7
+ export declare class PrismaAIEngine {
8
+ private static matchesSecurityRule;
9
+ private static normalizeFilterValue;
10
+ static parseDynamicQuery(rawAiOutput: unknown, config?: SecurityConfig): {
11
+ targetTable?: string;
12
+ where: any;
13
+ };
14
+ }
package/dist/engine.js ADDED
@@ -0,0 +1,100 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.PrismaAIEngine = void 0;
4
+ const schema_1 = require("./schema");
5
+ class PrismaAIEngine {
6
+ static matchesSecurityRule(value, rule) {
7
+ const normalizedValue = value.trim();
8
+ const normalizedRule = rule.trim();
9
+ if (!normalizedValue || !normalizedRule) {
10
+ return false;
11
+ }
12
+ return normalizedValue === normalizedRule || normalizedValue.startsWith(`${normalizedRule}.`);
13
+ }
14
+ static normalizeFilterValue(filter) {
15
+ const { operator, value } = filter;
16
+ if (operator === 'in' || operator === 'notIn') {
17
+ if (Array.isArray(value)) {
18
+ return value;
19
+ }
20
+ if (typeof value === 'string') {
21
+ const trimmed = value.trim();
22
+ if (!trimmed) {
23
+ return [];
24
+ }
25
+ if (trimmed.startsWith('[') && trimmed.endsWith(']')) {
26
+ try {
27
+ const parsed = JSON.parse(trimmed);
28
+ return Array.isArray(parsed) ? parsed : [parsed];
29
+ }
30
+ catch {
31
+ // Fall back to comma-splitting below.
32
+ }
33
+ }
34
+ return trimmed.includes(',')
35
+ ? trimmed.split(',').map(part => part.trim()).filter(Boolean)
36
+ : [trimmed];
37
+ }
38
+ return [value];
39
+ }
40
+ if (typeof value === 'string') {
41
+ const trimmed = value.trim();
42
+ if (!trimmed) {
43
+ return value;
44
+ }
45
+ try {
46
+ if ((trimmed.startsWith('[') && trimmed.endsWith(']')) || (trimmed.startsWith('{') && trimmed.endsWith('}'))) {
47
+ const parsed = JSON.parse(trimmed);
48
+ return parsed;
49
+ }
50
+ }
51
+ catch {
52
+ // Keep the original string when the value is not valid JSON.
53
+ }
54
+ }
55
+ return value;
56
+ }
57
+ static parseDynamicQuery(rawAiOutput, config) {
58
+ let parsedInput = rawAiOutput;
59
+ if (typeof rawAiOutput === 'string') {
60
+ try {
61
+ parsedInput = JSON.parse(rawAiOutput);
62
+ }
63
+ catch (e) {
64
+ throw new Error("AI output was not valid JSON.");
65
+ }
66
+ }
67
+ const validatedData = schema_1.AIQueryResponseSchema.parse(parsedInput);
68
+ const targetTable = typeof validatedData.targetTable === 'string' ? validatedData.targetTable.trim() : '';
69
+ if (!targetTable) {
70
+ throw new Error('Security Exception: AI output did not include a valid targetTable.');
71
+ }
72
+ if (config?.blockList && config.blockList.some(rule => this.matchesSecurityRule(targetTable, rule))) {
73
+ throw new Error(`Security Exception: AI attempted to query a blocked table -> ${targetTable}`);
74
+ }
75
+ if (config?.allowList && !config.allowList.some(rule => this.matchesSecurityRule(targetTable, rule))) {
76
+ throw new Error(`Security Exception: AI attempted to query a non-allowlisted table -> ${targetTable}`);
77
+ }
78
+ const conditions = validatedData.filters.map(filter => {
79
+ const normalizedField = filter.field.trim();
80
+ if (config?.blockList && config.blockList.some(rule => this.matchesSecurityRule(normalizedField, rule))) {
81
+ throw new Error(`Security Exception: AI attempted to query a blocked table or field -> ${normalizedField}`);
82
+ }
83
+ if (config?.allowList && !config.allowList.some(rule => this.matchesSecurityRule(normalizedField, rule))) {
84
+ throw new Error(`Security Exception: AI attempted to query a non-allowlisted field -> ${normalizedField}`);
85
+ }
86
+ const normalizedValue = this.normalizeFilterValue(filter);
87
+ return {
88
+ [normalizedField]: {
89
+ [filter.operator]: normalizedValue
90
+ }
91
+ };
92
+ });
93
+ const whereClause = conditions.length === 0 ? {} : { [validatedData.logicalOperator]: conditions };
94
+ return {
95
+ targetTable,
96
+ where: whereClause
97
+ };
98
+ }
99
+ }
100
+ exports.PrismaAIEngine = PrismaAIEngine;
@@ -0,0 +1,3 @@
1
+ export { PrismaAIEngine, type SecurityConfig } from './engine';
2
+ export { PrismaGeminiAgent, type AgentConfig } from './agent';
3
+ export { AIQueryResponseSchema, PrismaOperatorSchema, type AIQueryResponse } from './schema';
package/dist/index.js ADDED
@@ -0,0 +1,10 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.PrismaOperatorSchema = exports.AIQueryResponseSchema = exports.PrismaGeminiAgent = exports.PrismaAIEngine = void 0;
4
+ var engine_1 = require("./engine");
5
+ Object.defineProperty(exports, "PrismaAIEngine", { enumerable: true, get: function () { return engine_1.PrismaAIEngine; } });
6
+ var agent_1 = require("./agent");
7
+ Object.defineProperty(exports, "PrismaGeminiAgent", { enumerable: true, get: function () { return agent_1.PrismaGeminiAgent; } });
8
+ var schema_1 = require("./schema");
9
+ Object.defineProperty(exports, "AIQueryResponseSchema", { enumerable: true, get: function () { return schema_1.AIQueryResponseSchema; } });
10
+ Object.defineProperty(exports, "PrismaOperatorSchema", { enumerable: true, get: function () { return schema_1.PrismaOperatorSchema; } });
@@ -0,0 +1,49 @@
1
+ import { z } from 'zod';
2
+ export declare const PrismaOperatorSchema: z.ZodEnum<["equals", "not", "in", "notIn", "lt", "lte", "gt", "gte", "contains", "startsWith", "endsWith"]>;
3
+ export declare const QueryConditionSchema: z.ZodObject<{
4
+ field: z.ZodString;
5
+ operator: z.ZodEnum<["equals", "not", "in", "notIn", "lt", "lte", "gt", "gte", "contains", "startsWith", "endsWith"]>;
6
+ value: z.ZodAny;
7
+ }, "strip", z.ZodTypeAny, {
8
+ field: string;
9
+ operator: "equals" | "not" | "in" | "notIn" | "lt" | "lte" | "gt" | "gte" | "contains" | "startsWith" | "endsWith";
10
+ value?: any;
11
+ }, {
12
+ field: string;
13
+ operator: "equals" | "not" | "in" | "notIn" | "lt" | "lte" | "gt" | "gte" | "contains" | "startsWith" | "endsWith";
14
+ value?: any;
15
+ }>;
16
+ export declare const AIQueryResponseSchema: z.ZodObject<{
17
+ targetTable: z.ZodOptional<z.ZodString>;
18
+ logicalOperator: z.ZodDefault<z.ZodEnum<["AND", "OR"]>>;
19
+ filters: z.ZodArray<z.ZodObject<{
20
+ field: z.ZodString;
21
+ operator: z.ZodEnum<["equals", "not", "in", "notIn", "lt", "lte", "gt", "gte", "contains", "startsWith", "endsWith"]>;
22
+ value: z.ZodAny;
23
+ }, "strip", z.ZodTypeAny, {
24
+ field: string;
25
+ operator: "equals" | "not" | "in" | "notIn" | "lt" | "lte" | "gt" | "gte" | "contains" | "startsWith" | "endsWith";
26
+ value?: any;
27
+ }, {
28
+ field: string;
29
+ operator: "equals" | "not" | "in" | "notIn" | "lt" | "lte" | "gt" | "gte" | "contains" | "startsWith" | "endsWith";
30
+ value?: any;
31
+ }>, "many">;
32
+ }, "strip", z.ZodTypeAny, {
33
+ logicalOperator: "AND" | "OR";
34
+ filters: {
35
+ field: string;
36
+ operator: "equals" | "not" | "in" | "notIn" | "lt" | "lte" | "gt" | "gte" | "contains" | "startsWith" | "endsWith";
37
+ value?: any;
38
+ }[];
39
+ targetTable?: string | undefined;
40
+ }, {
41
+ filters: {
42
+ field: string;
43
+ operator: "equals" | "not" | "in" | "notIn" | "lt" | "lte" | "gt" | "gte" | "contains" | "startsWith" | "endsWith";
44
+ value?: any;
45
+ }[];
46
+ targetTable?: string | undefined;
47
+ logicalOperator?: "AND" | "OR" | undefined;
48
+ }>;
49
+ export type AIQueryResponse = z.infer<typeof AIQueryResponseSchema>;
package/dist/schema.js ADDED
@@ -0,0 +1,18 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.AIQueryResponseSchema = exports.QueryConditionSchema = exports.PrismaOperatorSchema = void 0;
4
+ const zod_1 = require("zod");
5
+ exports.PrismaOperatorSchema = zod_1.z.enum([
6
+ 'equals', 'not', 'in', 'notIn', 'lt', 'lte',
7
+ 'gt', 'gte', 'contains', 'startsWith', 'endsWith'
8
+ ]);
9
+ exports.QueryConditionSchema = zod_1.z.object({
10
+ field: zod_1.z.string().describe("The database column name (can include dot notation for relations)"),
11
+ operator: exports.PrismaOperatorSchema,
12
+ value: zod_1.z.any().describe("The value to filter by"),
13
+ });
14
+ exports.AIQueryResponseSchema = zod_1.z.object({
15
+ targetTable: zod_1.z.string().optional().describe("The dynamic database table to query (e.g., 'user', 'product')"),
16
+ logicalOperator: zod_1.z.enum(['AND', 'OR']).default('AND'),
17
+ filters: zod_1.z.array(exports.QueryConditionSchema)
18
+ });
package/package.json CHANGED
@@ -1,6 +1,35 @@
1
1
  {
2
2
  "name": "prisma-safe-ai-query",
3
- "version": "0.0.0-stage",
4
- "stub": true,
5
- "description": "Temporary package placeholder for staged publishing"
6
- }
3
+ "version": "1.0.6",
4
+ "description": "An end-to-end AI ORM: securely translates natural language into Prisma queries using Google Gemini and Zod.",
5
+ "main": "dist/index.js",
6
+ "types": "dist/index.d.ts",
7
+ "scripts": {
8
+ "build": "tsc",
9
+ "prepublishOnly": "npm run build",
10
+ "test": "npm run build && node --test tests/engine.test.js"
11
+ },
12
+ "keywords": [
13
+ "prisma",
14
+ "ai",
15
+ "llm",
16
+ "gemini",
17
+ "zod",
18
+ "security",
19
+ "orm",
20
+ "dynamic-routing"
21
+ ],
22
+ "author": "Olugbenga Taiwo",
23
+ "license": "MIT",
24
+ "dependencies": {
25
+ "@google/genai": "^1.45.0"
26
+ },
27
+ "peerDependencies": {
28
+ "@prisma/client": ">=5.0.0",
29
+ "zod": "^3.22.0 || ^4.0.0"
30
+ },
31
+ "devDependencies": {
32
+ "typescript": "^5.7.3",
33
+ "@types/node": "^22.20.1"
34
+ }
35
+ }
@@ -0,0 +1,124 @@
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
+ *(Need an API key? Get one for free at [aistudio.google.com](https://aistudio.google.com/))*
23
+
24
+ ## 💬 The Chat Flow (Frontend to Backend)
25
+
26
+ 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.
27
+
28
+ ### Step 1: The Frontend (React/Vue/Vanilla)
29
+ 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.
30
+
31
+ ```javascript
32
+ // Example React submit handler
33
+ async function handleChatSubmit(userInput) {
34
+ const response = await fetch('/api/chat', {
35
+ method: 'POST',
36
+ headers: { 'Content-Type': 'application/json' },
37
+ body: JSON.stringify({ userPrompt: userInput })
38
+ });
39
+
40
+ const result = await response.json();
41
+
42
+ if (result.error) return alert(result.error);
43
+
44
+ console.log("Safe Database Results:", result.data);
45
+ // Render results in your chat UI...
46
+ }
47
+ ```
48
+
49
+ ### Step 2: The Backend API Route (Next.js / Express)
50
+ Your backend receives the prompt, safely converts it to a Prisma query, executes it dynamically across ANY table, and returns the data.
51
+
52
+ **Import your Prisma Client**
53
+ ```typescript
54
+ // Prisma v5 import
55
+ import { PrismaClient } from '@prisma/client';
56
+ const prisma = new PrismaClient();
57
+
58
+ // Prisma v6+ import from helper file
59
+ // const prisma = require("./helper/prisma");
60
+ ```
61
+ *(Check out my tutorial on [how to install Prisma v7.8.0 in your Express/PostgreSQL project](https://charisintelligence.com.ng/prisma-v7-express-postgresql/))*
62
+
63
+ **Execute the AI Agent**
64
+ ```typescript
65
+ import { PrismaGeminiAgent } from 'prisma-safe-ai-query';
66
+
67
+ // Automatically picks up GEMINI_API_KEY from process.env
68
+ const aiAgent = new PrismaGeminiAgent();
69
+
70
+ export async function POST(req: Request) {
71
+ const { userPrompt } = await req.json(); // e.g. "Show me all active users in London"
72
+
73
+ try {
74
+ // 1. One line handles Gemini generation, dynamic routing, and Zod security!
75
+ const { targetTable, where } = await aiAgent.generateSafeQuery(userPrompt, {
76
+ dbContext: "Available tables are user, product, and order.", // Helps Gemini route correctly
77
+ blockList: ['password', 'credit_card'] // Automatically blocks these columns
78
+ });
79
+
80
+ // 2. DYNAMIC ROUTING: Validate the table and execute
81
+ if (!targetTable || !(targetTable in prisma)) {
82
+ return new Response(JSON.stringify({ error: "Invalid table requested." }), { status: 400 });
83
+ }
84
+
85
+ // Executes equivalent of prisma.user.findMany(...)
86
+ const dbResults = await (prisma as any)[targetTable].findMany({
87
+ where: where,
88
+ take: 10
89
+ });
90
+
91
+ // 3. Return the data to the frontend chat UI
92
+ return new Response(JSON.stringify({ data: dbResults }), { status: 200 });
93
+
94
+ } catch (error) {
95
+ return new Response(JSON.stringify({ error: error.message }), { status: 400 });
96
+ }
97
+ }
98
+ ```
99
+
100
+ ## 🔒 Security Configuration
101
+
102
+ 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.
103
+
104
+ **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.
105
+
106
+ ```typescript
107
+ const { targetTable, where } = await aiAgent.generateSafeQuery(userPrompt, {
108
+
109
+ // OPTION 1: Describe your DB to Gemini so it doesn't hallucinate tables.
110
+ // A fast hack is to use an AI (like ChatGPT) to generate your dbContext by giving it your prisma schema.
111
+ dbContext: "We have tables: user, product. User has fields: email, role, status.",
112
+
113
+ // OPTION 2: Block specific sensitive fields across all tables
114
+ blockList: ['passwordHash', 'billingDetails'],
115
+
116
+ // OPTION 3: Strict Mode - allow ONLY these fields
117
+ // allowList: ['status', 'role', 'city']
118
+ });
119
+ ```
120
+
121
+ ## 👨‍💻 Author
122
+ Created by **Olugbenga Taiwo**
123
+ - [LinkedIn](https://www.linkedin.com/in/taiwo-olugbenga)
124
+ - [GitHub Profile](https://github.com/oluibk2020/)