@vurb/prisma-gen 3.1.31

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 ADDED
@@ -0,0 +1,83 @@
1
+ <p align="center">
2
+ <h1 align="center">@vurb/prisma-gen</h1>
3
+ <p align="center">
4
+ <strong>Prisma Schema → MCP Tools Generator</strong> — Compile-time CRUD generation with field-level security
5
+ </p>
6
+ </p>
7
+
8
+ <p align="center">
9
+ <a href="https://www.npmjs.com/package/@vurb/prisma-gen"><img src="https://img.shields.io/npm/v/@vurb/prisma-gen?color=blue" alt="npm" /></a>
10
+ <a href="https://github.com/vinkius-labs/vurb.ts/blob/main/LICENSE"><img src="https://img.shields.io/badge/license-Apache--2.0-green" alt="License" /></a>
11
+ <img src="https://img.shields.io/badge/node-%3E%3D18-brightgreen" alt="Node" />
12
+ </p>
13
+
14
+ ---
15
+
16
+ > A compile-time Prisma Generator that reads `schema.prisma` annotations and emits hardened Vurb.ts Presenters and ToolBuilders — with field-level security, tenant isolation, and OOM protection baked into the generated code.
17
+
18
+ ## Quick Start
19
+
20
+ ```prisma
21
+ generator mcp {
22
+ provider = "vurb-prisma-gen"
23
+ output = "../src/tools/database"
24
+ }
25
+
26
+ model User {
27
+ id String @id @default(uuid())
28
+ email String @unique
29
+ passwordHash String /// @vurb.hide
30
+ stripeToken String /// @vurb.hide
31
+ creditScore Int /// @vurb.describe("Score 0-1000. Above 700 is PREMIUM.")
32
+ tenantId String /// @vurb.tenantKey
33
+ }
34
+ ```
35
+
36
+ ```bash
37
+ npx prisma generate
38
+ # → src/tools/database/userPresenter.ts
39
+ # → src/tools/database/userTools.ts
40
+ # → src/tools/database/index.ts
41
+ ```
42
+
43
+ ## Features
44
+
45
+ | Feature | Description |
46
+ |---------|-------------|
47
+ | **Egress Firewall** | `@vurb.hide` physically excludes columns from the generated Zod response schema — SOC2 at compile time |
48
+ | **Semantic Descriptions** | `@vurb.describe("...")` injects domain semantics into generated Zod fields |
49
+ | **Tenant Isolation** | `@vurb.tenantKey` injects tenant filters into every query's WHERE clause |
50
+ | **OOM Guard** | Pagination enforced with `take` (capped at 50) and `skip` — unbounded queries are structurally impossible |
51
+ | **Inversion of Control** | Generates `ToolBuilder` + `Presenter` files, not a server. You wire them in |
52
+
53
+ ## Schema Annotations
54
+
55
+ | Annotation | Effect |
56
+ |---|---|
57
+ | `/// @vurb.hide` | Excludes the field from the generated Zod response schema |
58
+ | `/// @vurb.describe("...")` | Adds `.describe()` to the Zod field — LLM reads this as a business rule |
59
+ | `/// @vurb.tenantKey` | Injects the field into every query's `WHERE` clause from `ctx` |
60
+
61
+ ## Installation
62
+
63
+ ```bash
64
+ npm install @vurb/prisma-gen vurb zod
65
+ ```
66
+
67
+ ### Peer Dependencies
68
+
69
+ | Package | Version |
70
+ |---------|---------|
71
+ | `vurb` | `^2.0.0` |
72
+ | `zod` | `^3.25.1 \|\| ^4.0.0` |
73
+ | `prisma` | `^6.0.0` |
74
+
75
+ ## Requirements
76
+
77
+ - **Node.js** ≥ 18.0.0
78
+ - **Vurb.ts** ≥ 2.0.0 (peer dependency)
79
+ - **Prisma** ≥ 6.0.0
80
+
81
+ ## License
82
+
83
+ [Apache-2.0](https://github.com/vinkius-labs/vurb.ts/blob/main/LICENSE)
@@ -0,0 +1,27 @@
1
+ /**
2
+ * PresenterEmitter — Generates Presenter files (MVA View Layer)
3
+ *
4
+ * For each Prisma model, emits a `{model}Presenter.ts` file containing:
5
+ * 1. A strict Zod **ResponseSchema** — the Egress Firewall
6
+ * 2. A `createPresenter()` binding with systemRules
7
+ *
8
+ * Security rules:
9
+ * - Fields with `@vurb.hide` are physically absent from the schema
10
+ * - Fields with `kind !== 'scalar' && kind !== 'enum'` are filtered (flat-only MVA)
11
+ * - Fields with `@vurb.describe("...")` get `.describe()` calls
12
+ * - Schema uses `.strict()` to reject undeclared fields at runtime
13
+ *
14
+ * @module
15
+ */
16
+ import type { DMMFModel, ModelAnnotations } from '../parser/AnnotationParser.js';
17
+ import type { GeneratedFile } from '../types.js';
18
+ export type { GeneratedFile } from '../types.js';
19
+ /**
20
+ * Generate a Presenter file for a single Prisma model.
21
+ *
22
+ * @param model - Prisma DMMF model
23
+ * @param annotations - Parsed @vurb.* annotations
24
+ * @returns Generated file with path and content
25
+ */
26
+ export declare function emitPresenter(model: DMMFModel, annotations: ModelAnnotations): GeneratedFile;
27
+ //# sourceMappingURL=PresenterEmitter.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"PresenterEmitter.d.ts","sourceRoot":"","sources":["../../src/emitter/PresenterEmitter.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AACH,OAAO,KAAK,EAAE,SAAS,EAAa,gBAAgB,EAAE,MAAM,+BAA+B,CAAC;AAC5F,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;AAGjD,YAAY,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;AAmCjD;;;;;;GAMG;AACH,wBAAgB,aAAa,CAAC,KAAK,EAAE,SAAS,EAAE,WAAW,EAAE,gBAAgB,GAAG,aAAa,CAiE5F"}
@@ -0,0 +1,92 @@
1
+ import { toPascalCase } from '../helpers/NamingHelpers.js';
2
+ // ── Type Mapping ─────────────────────────────────────────
3
+ /**
4
+ * Map Prisma scalar type to Zod builder expression.
5
+ */
6
+ function prismaTypeToZod(field) {
7
+ const base = mapScalarType(field.type);
8
+ // Apply .describe() if annotation exists (done externally)
9
+ // Apply .optional() if not required
10
+ if (!field.isRequired) {
11
+ return `${base}.optional()`;
12
+ }
13
+ return base;
14
+ }
15
+ function mapScalarType(prismaType) {
16
+ switch (prismaType) {
17
+ case 'String': return 'z.string()';
18
+ case 'Int': return 'z.number().int()';
19
+ case 'Float': return 'z.number()';
20
+ case 'Decimal': return 'z.number()';
21
+ case 'Boolean': return 'z.boolean()';
22
+ case 'DateTime': return 'z.coerce.date()';
23
+ case 'BigInt': return 'z.bigint()';
24
+ case 'Json': return 'z.unknown()';
25
+ case 'Bytes': return 'z.instanceof(Buffer)';
26
+ default: return 'z.string()'; // Enums fall through to z.string()
27
+ }
28
+ }
29
+ // ── Public API ───────────────────────────────────────────
30
+ /**
31
+ * Generate a Presenter file for a single Prisma model.
32
+ *
33
+ * @param model - Prisma DMMF model
34
+ * @param annotations - Parsed @vurb.* annotations
35
+ * @returns Generated file with path and content
36
+ */
37
+ export function emitPresenter(model, annotations) {
38
+ const name = toPascalCase(model.name);
39
+ const lines = [];
40
+ // ── Imports ───────────────────────────────────────────
41
+ lines.push(`/**`);
42
+ lines.push(` * ${name}Presenter — Generated by vurb-prisma-gen`);
43
+ lines.push(` *`);
44
+ lines.push(` * Egress Firewall: fields marked @vurb.hide are physically absent.`);
45
+ lines.push(` * This file is auto-generated. Manual edits will be overwritten.`);
46
+ lines.push(` *`);
47
+ lines.push(` * @generated`);
48
+ lines.push(` */`);
49
+ lines.push(`import { z } from 'zod';`);
50
+ lines.push(`import { createPresenter } from 'vurb';`);
51
+ lines.push(``);
52
+ // ── Response Schema ──────────────────────────────────
53
+ lines.push(`// ── Response Schema (Egress Firewall) ────────────────────`);
54
+ lines.push(``);
55
+ lines.push(`export const ${name}ResponseSchema = z.object({`);
56
+ // Filter: scalar/enum only (flat-only MVA — no relations)
57
+ const scalarFields = model.fields.filter(f => f.kind === 'scalar' || f.kind === 'enum');
58
+ for (const field of scalarFields) {
59
+ const ann = annotations.fields.get(field.name);
60
+ // @vurb.hide — physically exclude from Response
61
+ if (ann?.hidden)
62
+ continue;
63
+ let zodExpr = prismaTypeToZod(field);
64
+ // @vurb.describe — inject .describe() for LLM semantics
65
+ if (ann?.description) {
66
+ const escaped = ann.description.replace(/'/g, "\\'");
67
+ // Insert .describe() before .optional() if present
68
+ if (zodExpr.endsWith('.optional()')) {
69
+ const base = zodExpr.slice(0, -'.optional()'.length);
70
+ zodExpr = `${base}.describe('${escaped}').optional()`;
71
+ }
72
+ else {
73
+ zodExpr = `${zodExpr}.describe('${escaped}')`;
74
+ }
75
+ }
76
+ lines.push(` ${field.name}: ${zodExpr},`);
77
+ }
78
+ lines.push(`}).strict();`);
79
+ lines.push(``);
80
+ // ── Presenter ────────────────────────────────────────
81
+ lines.push(`// ── Presenter ────────────────────────────────────────────`);
82
+ lines.push(``);
83
+ lines.push(`export const ${name}Presenter = createPresenter('${name}')`);
84
+ lines.push(` .schema(${name}ResponseSchema)`);
85
+ lines.push(` .systemRules(['Data originates from the database via Prisma ORM.']);`);
86
+ lines.push(``);
87
+ return {
88
+ path: `${model.name.charAt(0).toLowerCase() + model.name.slice(1)}Presenter.ts`,
89
+ content: lines.join('\n'),
90
+ };
91
+ }
92
+ //# sourceMappingURL=PresenterEmitter.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"PresenterEmitter.js","sourceRoot":"","sources":["../../src/emitter/PresenterEmitter.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,31 @@
1
+ /**
2
+ * ToolEmitter — Generates Tool files (MVA Agent Layer)
3
+ *
4
+ * For each Prisma model, emits a `{model}Tools.ts` file containing:
5
+ * 1. `PrismaVurbContext` type — shift-left TS safety for ctx.prisma + ctx.tenantId
6
+ * 2. `CreateSchema` — input params (no @id/@default, no @vurb.tenantKey, YES @vurb.hide)
7
+ * 3. `UpdateSchema` — all Create fields as .optional()
8
+ * 4. 5 CRUD actions: find_many, find_unique, create, update, delete
9
+ *
10
+ * Security rules:
11
+ * - Schema asymmetry: ResponseSchema ≠ CreateSchema ≠ UpdateSchema
12
+ * - @vurb.tenantKey → injected into WHERE/data of every query from ctx
13
+ * - @id @default fields → excluded from CreateSchema (server/DB generates)
14
+ * - find_many → forced pagination: take max 50, skip default 0
15
+ * - delete → destructive: true
16
+ * - find_many/find_unique → readOnly: true
17
+ * - Relations filtered (scalar/enum only)
18
+ *
19
+ * @module
20
+ */
21
+ import type { DMMFModel, ModelAnnotations } from '../parser/AnnotationParser.js';
22
+ import type { GeneratedFile } from '../types.js';
23
+ /**
24
+ * Generate a Tool file for a single Prisma model.
25
+ *
26
+ * @param model - Prisma DMMF model
27
+ * @param annotations - Parsed @vurb.* annotations
28
+ * @returns Generated file with path and content
29
+ */
30
+ export declare function emitTool(model: DMMFModel, annotations: ModelAnnotations): GeneratedFile;
31
+ //# sourceMappingURL=ToolEmitter.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ToolEmitter.d.ts","sourceRoot":"","sources":["../../src/emitter/ToolEmitter.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;GAmBG;AACH,OAAO,KAAK,EAAE,SAAS,EAAa,gBAAgB,EAAE,MAAM,+BAA+B,CAAC;AAC5F,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;AA6CjD;;;;;;GAMG;AACH,wBAAgB,QAAQ,CAAC,KAAK,EAAE,SAAS,EAAE,WAAW,EAAE,gBAAgB,GAAG,aAAa,CAqEvF"}
@@ -0,0 +1,266 @@
1
+ import { toPascalCase, toSnakeCase } from '../helpers/NamingHelpers.js';
2
+ // ── Type Mapping ─────────────────────────────────────────
3
+ function mapScalarType(prismaType) {
4
+ switch (prismaType) {
5
+ case 'String': return 'z.string()';
6
+ case 'Int': return 'z.number().int()';
7
+ case 'Float': return 'z.number()';
8
+ case 'Decimal': return 'z.number()';
9
+ case 'Boolean': return 'z.boolean()';
10
+ case 'DateTime': return 'z.coerce.date()';
11
+ case 'BigInt': return 'z.bigint()';
12
+ case 'Json': return 'z.unknown()';
13
+ case 'Bytes': return 'z.instanceof(Buffer)';
14
+ default: return 'z.string()';
15
+ }
16
+ }
17
+ // ── Field Classification ─────────────────────────────────
18
+ /**
19
+ * Determine if a field should be excluded from CreateSchema.
20
+ * Excludes: @id with @default, @vurb.tenantKey, auto-generated timestamps.
21
+ */
22
+ function isAutoGenerated(field, annotations) {
23
+ const ann = annotations.fields.get(field.name);
24
+ // @id with default value (uuid, autoincrement, cuid, etc.)
25
+ if (field.isId && field.hasDefaultValue)
26
+ return true;
27
+ // @vurb.tenantKey — injected from ctx, not from LLM
28
+ if (ann?.tenantKey)
29
+ return true;
30
+ // Common auto-generated timestamp fields
31
+ if (field.hasDefaultValue && (field.name === 'createdAt' || field.name === 'updatedAt')) {
32
+ return true;
33
+ }
34
+ return false;
35
+ }
36
+ // ── Public API ───────────────────────────────────────────
37
+ /**
38
+ * Generate a Tool file for a single Prisma model.
39
+ *
40
+ * @param model - Prisma DMMF model
41
+ * @param annotations - Parsed @vurb.* annotations
42
+ * @returns Generated file with path and content
43
+ */
44
+ export function emitTool(model, annotations) {
45
+ const name = toPascalCase(model.name);
46
+ const snakeName = toSnakeCase(model.name);
47
+ const prismaModel = model.name.charAt(0).toLowerCase() + model.name.slice(1);
48
+ const lines = [];
49
+ // ── Header ───────────────────────────────────────────
50
+ lines.push(`/**`);
51
+ lines.push(` * ${name}Tools — Generated by vurb-prisma-gen`);
52
+ lines.push(` *`);
53
+ lines.push(` * CRUD tool with tenant isolation and OOM protection.`);
54
+ lines.push(` * This file is auto-generated. Manual edits will be overwritten.`);
55
+ lines.push(` *`);
56
+ lines.push(` * @generated`);
57
+ lines.push(` */`);
58
+ lines.push(`import { z } from 'zod';`);
59
+ lines.push(`import { defineTool } from 'vurb';`);
60
+ lines.push(`import { ${name}Presenter } from './${prismaModel}Presenter.js';`);
61
+ lines.push(``);
62
+ // ── Context Type (Shift-Left Security) ───────────────
63
+ emitContextType(lines, annotations);
64
+ // ── Scalar fields ────────────────────────────────────
65
+ const scalarFields = model.fields.filter(f => f.kind === 'scalar' || f.kind === 'enum');
66
+ // Fields for CreateSchema: exclude auto-generated, include @vurb.hide
67
+ const createFields = scalarFields.filter(f => !isAutoGenerated(f, annotations));
68
+ // Find the @id field for find_unique/update/delete
69
+ const idField = scalarFields.find(f => f.isId);
70
+ const idName = idField?.name ?? 'id';
71
+ const idZod = idField ? mapScalarType(idField.type) : 'z.string()';
72
+ // Filterable fields for find_many (string fields, excluding hidden and tenant)
73
+ const filterableFields = scalarFields.filter(f => {
74
+ const ann = annotations.fields.get(f.name);
75
+ return f.type === 'String' && !ann?.hidden && !ann?.tenantKey && !f.isId;
76
+ });
77
+ // ── Tool Definition ──────────────────────────────────
78
+ lines.push(`export const ${prismaModel}Tools = defineTool<PrismaVurbContext>('db_${snakeName}', {`);
79
+ lines.push(` actions: {`);
80
+ // ── find_many ─────────────────────────────────────────
81
+ emitFindMany(lines, model, annotations, prismaModel, filterableFields);
82
+ // ── find_unique ───────────────────────────────────────
83
+ emitFindUnique(lines, annotations, prismaModel, idName, idZod);
84
+ // ── create ────────────────────────────────────────────
85
+ emitCreate(lines, annotations, prismaModel, createFields);
86
+ // ── update ────────────────────────────────────────────
87
+ emitUpdate(lines, annotations, prismaModel, idName, idZod, createFields);
88
+ // ── delete ────────────────────────────────────────────
89
+ emitDelete(lines, annotations, prismaModel, idName, idZod);
90
+ lines.push(` },`);
91
+ lines.push(`});`);
92
+ lines.push(``);
93
+ return {
94
+ path: `${prismaModel}Tools.ts`,
95
+ content: lines.join('\n'),
96
+ };
97
+ }
98
+ // ── Context Type Emitter ─────────────────────────────────
99
+ function emitContextType(lines, annotations) {
100
+ lines.push(`// ── Context Contract (Shift-Left Security) ──────────────`);
101
+ lines.push(`//`);
102
+ lines.push(`// If the developer forgets to provide these in contextFactory,`);
103
+ lines.push(`// TypeScript compilation fails — not runtime.`);
104
+ lines.push(``);
105
+ lines.push(`export interface PrismaVurbContext {`);
106
+ lines.push(` readonly prisma: { [model: string]: any };`);
107
+ if (annotations.tenantKeyField) {
108
+ lines.push(` /** Tenant isolation key — injected into every query WHERE clause */`);
109
+ lines.push(` readonly ${annotations.tenantKeyField}: string;`);
110
+ }
111
+ lines.push(`}`);
112
+ lines.push(``);
113
+ }
114
+ // ── Action Emitters ──────────────────────────────────────
115
+ function emitFindMany(lines, model, annotations, prismaModel, filterableFields) {
116
+ lines.push(` find_many: {`);
117
+ lines.push(` readOnly: true,`);
118
+ lines.push(` description: 'List ${model.name} records with pagination',`);
119
+ lines.push(` returns: ${toPascalCase(model.name)}Presenter,`);
120
+ lines.push(` params: z.object({`);
121
+ // Filterable string fields → contains filter
122
+ for (const f of filterableFields) {
123
+ const ann = annotations.fields.get(f.name);
124
+ const desc = ann?.description ? `.describe('Filter by ${f.name}')` : '';
125
+ lines.push(` ${f.name}_contains: z.string()${desc}.optional(),`);
126
+ }
127
+ // Pagination — OOM protection
128
+ lines.push(` take: z.number().int().min(1).max(50).default(20).describe('Max rows per page (capped at 50)'),`);
129
+ lines.push(` skip: z.number().int().min(0).default(0).describe('Offset for pagination'),`);
130
+ lines.push(` }),`);
131
+ lines.push(` handler: async (ctx, args) => {`);
132
+ // Build WHERE clause
133
+ lines.push(` const where: Record<string, unknown> = {};`);
134
+ // Tenant isolation
135
+ if (annotations.tenantKeyField) {
136
+ lines.push(` where['${annotations.tenantKeyField}'] = ctx.${annotations.tenantKeyField};`);
137
+ }
138
+ // Filters
139
+ for (const f of filterableFields) {
140
+ lines.push(` if (args.${f.name}_contains !== undefined) {`);
141
+ lines.push(` where['${f.name}'] = { contains: args.${f.name}_contains };`);
142
+ lines.push(` }`);
143
+ }
144
+ lines.push(` return await ctx.prisma.${prismaModel}.findMany({`);
145
+ lines.push(` where,`);
146
+ lines.push(` take: args.take,`);
147
+ lines.push(` skip: args.skip,`);
148
+ lines.push(` });`);
149
+ lines.push(` },`);
150
+ lines.push(` },`);
151
+ }
152
+ function emitFindUnique(lines, annotations, prismaModel, idName, idZod) {
153
+ lines.push(` find_unique: {`);
154
+ lines.push(` readOnly: true,`);
155
+ lines.push(` description: 'Get a single record by ID',`);
156
+ lines.push(` returns: ${toPascalCase(prismaModel)}Presenter,`);
157
+ lines.push(` params: z.object({`);
158
+ lines.push(` ${idName}: ${idZod},`);
159
+ lines.push(` }),`);
160
+ lines.push(` handler: async (ctx, args) => {`);
161
+ // WHERE with tenant isolation
162
+ if (annotations.tenantKeyField) {
163
+ lines.push(` const result = await ctx.prisma.${prismaModel}.findUnique({`);
164
+ lines.push(` where: { ${idName}: args.${idName}, ${annotations.tenantKeyField}: ctx.${annotations.tenantKeyField} },`);
165
+ lines.push(` });`);
166
+ }
167
+ else {
168
+ lines.push(` const result = await ctx.prisma.${prismaModel}.findUnique({`);
169
+ lines.push(` where: { ${idName}: args.${idName} },`);
170
+ lines.push(` });`);
171
+ }
172
+ lines.push(` if (!result) return { content: [{ type: 'text', text: '${toPascalCase(prismaModel)} not found' }], isError: true };`);
173
+ lines.push(` return result;`);
174
+ lines.push(` },`);
175
+ lines.push(` },`);
176
+ }
177
+ function emitCreate(lines, annotations, prismaModel, createFields) {
178
+ lines.push(` create: {`);
179
+ lines.push(` description: 'Create a new record',`);
180
+ lines.push(` returns: ${toPascalCase(prismaModel)}Presenter,`);
181
+ lines.push(` params: z.object({`);
182
+ for (const field of createFields) {
183
+ const ann = annotations.fields.get(field.name);
184
+ let zodExpr = mapScalarType(field.type);
185
+ if (ann?.description) {
186
+ zodExpr = `${zodExpr}.describe('${ann.description.replace(/'/g, "\\'")}')`;
187
+ }
188
+ // Optional fields stay optional in create
189
+ if (!field.isRequired || field.hasDefaultValue) {
190
+ zodExpr = `${zodExpr}.optional()`;
191
+ }
192
+ lines.push(` ${field.name}: ${zodExpr},`);
193
+ }
194
+ lines.push(` }),`);
195
+ lines.push(` handler: async (ctx, args) => {`);
196
+ if (annotations.tenantKeyField) {
197
+ lines.push(` return await ctx.prisma.${prismaModel}.create({`);
198
+ lines.push(` data: { ...args, ${annotations.tenantKeyField}: ctx.${annotations.tenantKeyField} },`);
199
+ lines.push(` });`);
200
+ }
201
+ else {
202
+ lines.push(` return await ctx.prisma.${prismaModel}.create({`);
203
+ lines.push(` data: args,`);
204
+ lines.push(` });`);
205
+ }
206
+ lines.push(` },`);
207
+ lines.push(` },`);
208
+ }
209
+ function emitUpdate(lines, annotations, prismaModel, idName, idZod, createFields) {
210
+ lines.push(` update: {`);
211
+ lines.push(` description: 'Update an existing record',`);
212
+ lines.push(` returns: ${toPascalCase(prismaModel)}Presenter,`);
213
+ lines.push(` params: z.object({`);
214
+ lines.push(` ${idName}: ${idZod},`);
215
+ // All create fields as .optional() for partial update
216
+ for (const field of createFields) {
217
+ if (field.name === idName)
218
+ continue; // Skip ID, already added
219
+ const ann = annotations.fields.get(field.name);
220
+ let zodExpr = mapScalarType(field.type);
221
+ if (ann?.description) {
222
+ zodExpr = `${zodExpr}.describe('${ann.description.replace(/'/g, "\\'")}')`;
223
+ }
224
+ lines.push(` ${field.name}: ${zodExpr}.optional(),`);
225
+ }
226
+ lines.push(` }),`);
227
+ lines.push(` handler: async (ctx, args) => {`);
228
+ lines.push(` const { ${idName}, ...data } = args;`);
229
+ if (annotations.tenantKeyField) {
230
+ lines.push(` return await ctx.prisma.${prismaModel}.update({`);
231
+ lines.push(` where: { ${idName}, ${annotations.tenantKeyField}: ctx.${annotations.tenantKeyField} },`);
232
+ lines.push(` data,`);
233
+ lines.push(` });`);
234
+ }
235
+ else {
236
+ lines.push(` return await ctx.prisma.${prismaModel}.update({`);
237
+ lines.push(` where: { ${idName} },`);
238
+ lines.push(` data,`);
239
+ lines.push(` });`);
240
+ }
241
+ lines.push(` },`);
242
+ lines.push(` },`);
243
+ }
244
+ function emitDelete(lines, annotations, prismaModel, idName, idZod) {
245
+ lines.push(` delete: {`);
246
+ lines.push(` destructive: true,`);
247
+ lines.push(` description: 'Delete a record by ID',`);
248
+ lines.push(` params: z.object({`);
249
+ lines.push(` ${idName}: ${idZod},`);
250
+ lines.push(` }),`);
251
+ lines.push(` handler: async (ctx, args) => {`);
252
+ if (annotations.tenantKeyField) {
253
+ lines.push(` await ctx.prisma.${prismaModel}.delete({`);
254
+ lines.push(` where: { ${idName}: args.${idName}, ${annotations.tenantKeyField}: ctx.${annotations.tenantKeyField} },`);
255
+ lines.push(` });`);
256
+ }
257
+ else {
258
+ lines.push(` await ctx.prisma.${prismaModel}.delete({`);
259
+ lines.push(` where: { ${idName}: args.${idName} },`);
260
+ lines.push(` });`);
261
+ }
262
+ lines.push(` return { deleted: true };`);
263
+ lines.push(` },`);
264
+ lines.push(` },`);
265
+ }
266
+ //# sourceMappingURL=ToolEmitter.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ToolEmitter.js","sourceRoot":"","sources":["../../src/emitter/ToolEmitter.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,3 @@
1
+ #!/usr/bin/env node
2
+ export {};
3
+ //# sourceMappingURL=generator.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"generator.d.ts","sourceRoot":"","sources":["../src/generator.ts"],"names":[],"mappings":""}
@@ -0,0 +1,81 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * Prisma Generator Entry Point — vurb-prisma-gen
4
+ *
5
+ * Intercepts `npx prisma generate` and emits Vurb Presenters
6
+ * and ToolBuilders with field-level security, tenant isolation, and
7
+ * OOM protection.
8
+ *
9
+ * Usage in schema.prisma:
10
+ * ```prisma
11
+ * generator mcp {
12
+ * provider = "vurb-prisma-gen"
13
+ * output = "../src/tools/database"
14
+ * }
15
+ * ```
16
+ *
17
+ * @module
18
+ */
19
+ import { generatorHandler } from '@prisma/generator-helper';
20
+ import { writeFileSync, mkdirSync } from 'node:fs';
21
+ import { join, resolve } from 'node:path';
22
+ import { parseAnnotations } from './parser/AnnotationParser.js';
23
+ import { emitPresenter } from './emitter/PresenterEmitter.js';
24
+ import { emitTool } from './emitter/ToolEmitter.js';
25
+ // ── Generator Handler ────────────────────────────────────
26
+ generatorHandler({
27
+ onManifest() {
28
+ return {
29
+ prettyName: 'Vurb Prisma Generator',
30
+ defaultOutput: './generated',
31
+ };
32
+ },
33
+ async onGenerate(options) {
34
+ const outputDir = resolve(options.generator.output?.value ?? './generated');
35
+ mkdirSync(outputDir, { recursive: true });
36
+ const models = options.dmmf.datamodel.models;
37
+ const generatedNames = [];
38
+ for (const model of models) {
39
+ const annotations = parseAnnotations(model);
40
+ // Emit Presenter (MVA View Layer — Egress Firewall)
41
+ const presenterFile = emitPresenter(model, annotations);
42
+ const presenterPath = join(outputDir, presenterFile.path);
43
+ writeFileSync(presenterPath, presenterFile.content, 'utf-8');
44
+ // Emit Tool (MVA Agent Layer — CRUD with tenant isolation)
45
+ const toolFile = emitTool(model, annotations);
46
+ const toolPath = join(outputDir, toolFile.path);
47
+ writeFileSync(toolPath, toolFile.content, 'utf-8');
48
+ generatedNames.push(model.name);
49
+ console.log(` 📄 ${presenterFile.path}`);
50
+ console.log(` 📄 ${toolFile.path}`);
51
+ }
52
+ // Emit barrel index.ts
53
+ const barrelFile = emitBarrel(generatedNames);
54
+ const barrelPath = join(outputDir, barrelFile.path);
55
+ writeFileSync(barrelPath, barrelFile.content, 'utf-8');
56
+ console.log(` 📄 ${barrelFile.path}`);
57
+ console.log(`\n🎉 Generated ${models.length * 2 + 1} files in ${outputDir}`);
58
+ },
59
+ });
60
+ // ── Barrel Emitter ───────────────────────────────────────
61
+ function emitBarrel(modelNames) {
62
+ const lines = [];
63
+ lines.push(`/**`);
64
+ lines.push(` * Generated barrel export — vurb-prisma-gen`);
65
+ lines.push(` * @generated`);
66
+ lines.push(` */`);
67
+ let isFirst = true;
68
+ for (const name of modelNames) {
69
+ const prismaModel = name.charAt(0).toLowerCase() + name.slice(1);
70
+ lines.push(`export { ${name}Presenter, ${name}ResponseSchema } from './${prismaModel}Presenter.js';`);
71
+ lines.push(`export { ${prismaModel}Tools } from './${prismaModel}Tools.js';`);
72
+ // Export PrismaVurbContext only from the first model to avoid duplicate identifiers
73
+ if (isFirst) {
74
+ lines.push(`export type { PrismaVurbContext } from './${prismaModel}Tools.js';`);
75
+ isFirst = false;
76
+ }
77
+ }
78
+ lines.push(``);
79
+ return { path: 'index.ts', content: lines.join('\n') };
80
+ }
81
+ //# sourceMappingURL=generator.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"generator.js","sourceRoot":"","sources":["../src/generator.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,24 @@
1
+ /**
2
+ * NamingHelpers — Case conversion and pluralization utilities
3
+ *
4
+ * Reuses patterns from openapi-gen's TemplateHelpers.
5
+ *
6
+ * @module
7
+ */
8
+ /**
9
+ * Convert PascalCase or camelCase to snake_case.
10
+ * @example toSnakeCase('UserProfile') → 'user_profile'
11
+ */
12
+ export declare function toSnakeCase(str: string): string;
13
+ /**
14
+ * Ensure PascalCase.
15
+ * @example toPascalCase('user_profile') → 'UserProfile'
16
+ */
17
+ export declare function toPascalCase(str: string): string;
18
+ /**
19
+ * Simple English pluralize (covers common cases).
20
+ * @example pluralize('User') → 'Users'
21
+ * @example pluralize('Company') → 'Companies'
22
+ */
23
+ export declare function pluralize(str: string): string;
24
+ //# sourceMappingURL=NamingHelpers.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"NamingHelpers.d.ts","sourceRoot":"","sources":["../../src/helpers/NamingHelpers.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAIH;;;GAGG;AACH,wBAAgB,WAAW,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,CAK/C;AAED;;;GAGG;AACH,wBAAgB,YAAY,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,CAGhD;AAED;;;;GAIG;AACH,wBAAgB,SAAS,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,CAS7C"}
@@ -0,0 +1,42 @@
1
+ /**
2
+ * NamingHelpers — Case conversion and pluralization utilities
3
+ *
4
+ * Reuses patterns from openapi-gen's TemplateHelpers.
5
+ *
6
+ * @module
7
+ */
8
+ // ── Case Conversion ──────────────────────────────────────
9
+ /**
10
+ * Convert PascalCase or camelCase to snake_case.
11
+ * @example toSnakeCase('UserProfile') → 'user_profile'
12
+ */
13
+ export function toSnakeCase(str) {
14
+ return str
15
+ .replace(/([A-Z]+)([A-Z][a-z])/g, '$1_$2')
16
+ .replace(/([a-z\d])([A-Z])/g, '$1_$2')
17
+ .toLowerCase();
18
+ }
19
+ /**
20
+ * Ensure PascalCase.
21
+ * @example toPascalCase('user_profile') → 'UserProfile'
22
+ */
23
+ export function toPascalCase(str) {
24
+ return str
25
+ .replace(/(^|_)([a-z])/g, (_m, _p, c) => c.toUpperCase());
26
+ }
27
+ /**
28
+ * Simple English pluralize (covers common cases).
29
+ * @example pluralize('User') → 'Users'
30
+ * @example pluralize('Company') → 'Companies'
31
+ */
32
+ export function pluralize(str) {
33
+ if (str.endsWith('s') || str.endsWith('x') || str.endsWith('z')
34
+ || str.endsWith('sh') || str.endsWith('ch')) {
35
+ return str + 'es';
36
+ }
37
+ if (str.endsWith('y') && !/[aeiou]y$/i.test(str)) {
38
+ return str.slice(0, -1) + 'ies';
39
+ }
40
+ return str + 's';
41
+ }
42
+ //# sourceMappingURL=NamingHelpers.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"NamingHelpers.js","sourceRoot":"","sources":["../../src/helpers/NamingHelpers.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,4DAA4D;AAE5D;;;GAGG;AACH,MAAM,UAAU,WAAW,CAAC,GAAW;IACnC,OAAO,GAAG;SACL,OAAO,CAAC,uBAAuB,EAAE,OAAO,CAAC;SACzC,OAAO,CAAC,mBAAmB,EAAE,OAAO,CAAC;SACrC,WAAW,EAAE,CAAC;AACvB,CAAC;AAED;;;GAGG;AACH,MAAM,UAAU,YAAY,CAAC,GAAW;IACpC,OAAO,GAAG;SACL,OAAO,CAAC,eAAe,EAAE,CAAC,EAAE,EAAE,EAAE,EAAE,CAAS,EAAE,EAAE,CAAC,CAAC,CAAC,WAAW,EAAE,CAAC,CAAC;AAC1E,CAAC;AAED;;;;GAIG;AACH,MAAM,UAAU,SAAS,CAAC,GAAW;IACjC,IAAI,GAAG,CAAC,QAAQ,CAAC,GAAG,CAAC,IAAI,GAAG,CAAC,QAAQ,CAAC,GAAG,CAAC,IAAI,GAAG,CAAC,QAAQ,CAAC,GAAG,CAAC;WACxD,GAAG,CAAC,QAAQ,CAAC,IAAI,CAAC,IAAI,GAAG,CAAC,QAAQ,CAAC,IAAI,CAAC,EAAE,CAAC;QAC9C,OAAO,GAAG,GAAG,IAAI,CAAC;IACtB,CAAC;IACD,IAAI,GAAG,CAAC,QAAQ,CAAC,GAAG,CAAC,IAAI,CAAC,YAAY,CAAC,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC;QAC/C,OAAO,GAAG,CAAC,KAAK,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,GAAG,KAAK,CAAC;IACpC,CAAC;IACD,OAAO,GAAG,GAAG,GAAG,CAAC;AACrB,CAAC"}
@@ -0,0 +1,19 @@
1
+ /**
2
+ * @vinkius-core/prisma-gen — Root Barrel Export
3
+ *
4
+ * Public API for programmatic usage.
5
+ *
6
+ * @example
7
+ * ```typescript
8
+ * import { parseAnnotations, emitPresenter, emitTool } from '@vurb/prisma-gen';
9
+ * ```
10
+ *
11
+ * @module
12
+ */
13
+ export { parseAnnotations } from './parser/AnnotationParser.js';
14
+ export type { FieldAnnotation, ModelAnnotations, DMMFField, DMMFModel, } from './parser/AnnotationParser.js';
15
+ export { emitPresenter } from './emitter/PresenterEmitter.js';
16
+ export { emitTool } from './emitter/ToolEmitter.js';
17
+ export type { GeneratedFile } from './types.js';
18
+ export { toSnakeCase, toPascalCase, pluralize } from './helpers/NamingHelpers.js';
19
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAGH,OAAO,EAAE,gBAAgB,EAAE,MAAM,8BAA8B,CAAC;AAChE,YAAY,EACR,eAAe,EAAE,gBAAgB,EACjC,SAAS,EAAE,SAAS,GACvB,MAAM,8BAA8B,CAAC;AAGtC,OAAO,EAAE,aAAa,EAAE,MAAM,+BAA+B,CAAC;AAC9D,OAAO,EAAE,QAAQ,EAAE,MAAM,0BAA0B,CAAC;AACpD,YAAY,EAAE,aAAa,EAAE,MAAM,YAAY,CAAC;AAGhD,OAAO,EAAE,WAAW,EAAE,YAAY,EAAE,SAAS,EAAE,MAAM,4BAA4B,CAAC"}
package/dist/index.js ADDED
@@ -0,0 +1,20 @@
1
+ /**
2
+ * @vinkius-core/prisma-gen — Root Barrel Export
3
+ *
4
+ * Public API for programmatic usage.
5
+ *
6
+ * @example
7
+ * ```typescript
8
+ * import { parseAnnotations, emitPresenter, emitTool } from '@vurb/prisma-gen';
9
+ * ```
10
+ *
11
+ * @module
12
+ */
13
+ // ── Parser ───────────────────────────────────────────────
14
+ export { parseAnnotations } from './parser/AnnotationParser.js';
15
+ // ── Emitters ─────────────────────────────────────────────
16
+ export { emitPresenter } from './emitter/PresenterEmitter.js';
17
+ export { emitTool } from './emitter/ToolEmitter.js';
18
+ // ── Helpers ──────────────────────────────────────────────
19
+ export { toSnakeCase, toPascalCase, pluralize } from './helpers/NamingHelpers.js';
20
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAEH,4DAA4D;AAC5D,OAAO,EAAE,gBAAgB,EAAE,MAAM,8BAA8B,CAAC;AAMhE,4DAA4D;AAC5D,OAAO,EAAE,aAAa,EAAE,MAAM,+BAA+B,CAAC;AAC9D,OAAO,EAAE,QAAQ,EAAE,MAAM,0BAA0B,CAAC;AAGpD,4DAA4D;AAC5D,OAAO,EAAE,WAAW,EAAE,YAAY,EAAE,SAAS,EAAE,MAAM,4BAA4B,CAAC"}
@@ -0,0 +1,53 @@
1
+ /**
2
+ * AnnotationParser — Extracts @vurb.* annotations from Prisma DMMF
3
+ *
4
+ * Reads the `documentation` field (/// triple-comments) from each Prisma
5
+ * model field and extracts Vurb security annotations.
6
+ *
7
+ * IMPORTANT: Prisma DMMF concatenates multi-line comments with \n.
8
+ * We use `includes()` for boolean flags (not anchored regex)
9
+ * to handle cases like "User's password.\n@vurb.hide".
10
+ *
11
+ * @module
12
+ */
13
+ /** Parsed annotations for a single field */
14
+ export interface FieldAnnotation {
15
+ /** Field is excluded from the Response Zod schema (Egress Firewall) */
16
+ readonly hidden: boolean;
17
+ /** LLM-facing description injected via .describe() */
18
+ readonly description?: string;
19
+ /** Field used for tenant isolation in WHERE clauses */
20
+ readonly tenantKey: boolean;
21
+ }
22
+ /** Parsed annotations for an entire model */
23
+ export interface ModelAnnotations {
24
+ /** Per-field annotation map (field name → annotations) */
25
+ readonly fields: Map<string, FieldAnnotation>;
26
+ /** The field name marked as @vurb.tenantKey (at most one per model) */
27
+ readonly tenantKeyField?: string;
28
+ }
29
+ /** Minimal Prisma DMMF field shape */
30
+ export interface DMMFField {
31
+ readonly name: string;
32
+ readonly kind: string;
33
+ readonly type: string;
34
+ readonly isList: boolean;
35
+ readonly isRequired: boolean;
36
+ readonly isId: boolean;
37
+ readonly hasDefaultValue: boolean;
38
+ readonly isUnique: boolean;
39
+ readonly documentation?: string;
40
+ }
41
+ /** Minimal Prisma DMMF model shape */
42
+ export interface DMMFModel {
43
+ readonly name: string;
44
+ readonly fields: readonly DMMFField[];
45
+ }
46
+ /**
47
+ * Parse all @vurb.* annotations from a Prisma DMMF model.
48
+ *
49
+ * @param model - Prisma DMMF model
50
+ * @returns Parsed annotations with field map and tenant key
51
+ */
52
+ export declare function parseAnnotations(model: DMMFModel): ModelAnnotations;
53
+ //# sourceMappingURL=AnnotationParser.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"AnnotationParser.d.ts","sourceRoot":"","sources":["../../src/parser/AnnotationParser.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAIH,4CAA4C;AAC5C,MAAM,WAAW,eAAe;IAC5B,uEAAuE;IACvE,QAAQ,CAAC,MAAM,EAAE,OAAO,CAAC;IACzB,sDAAsD;IACtD,QAAQ,CAAC,WAAW,CAAC,EAAE,MAAM,CAAC;IAC9B,uDAAuD;IACvD,QAAQ,CAAC,SAAS,EAAE,OAAO,CAAC;CAC/B;AAED,6CAA6C;AAC7C,MAAM,WAAW,gBAAgB;IAC7B,0DAA0D;IAC1D,QAAQ,CAAC,MAAM,EAAE,GAAG,CAAC,MAAM,EAAE,eAAe,CAAC,CAAC;IAC9C,uEAAuE;IACvE,QAAQ,CAAC,cAAc,CAAC,EAAE,MAAM,CAAC;CACpC;AAID,sCAAsC;AACtC,MAAM,WAAW,SAAS;IACtB,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,MAAM,EAAE,OAAO,CAAC;IACzB,QAAQ,CAAC,UAAU,EAAE,OAAO,CAAC;IAC7B,QAAQ,CAAC,IAAI,EAAE,OAAO,CAAC;IACvB,QAAQ,CAAC,eAAe,EAAE,OAAO,CAAC;IAClC,QAAQ,CAAC,QAAQ,EAAE,OAAO,CAAC;IAC3B,QAAQ,CAAC,aAAa,CAAC,EAAE,MAAM,CAAC;CACnC;AAED,sCAAsC;AACtC,MAAM,WAAW,SAAS;IACtB,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,MAAM,EAAE,SAAS,SAAS,EAAE,CAAC;CACzC;AAUD;;;;;GAKG;AACH,wBAAgB,gBAAgB,CAAC,KAAK,EAAE,SAAS,GAAG,gBAAgB,CA8BnE"}
@@ -0,0 +1,49 @@
1
+ /**
2
+ * AnnotationParser — Extracts @vurb.* annotations from Prisma DMMF
3
+ *
4
+ * Reads the `documentation` field (/// triple-comments) from each Prisma
5
+ * model field and extracts Vurb security annotations.
6
+ *
7
+ * IMPORTANT: Prisma DMMF concatenates multi-line comments with \n.
8
+ * We use `includes()` for boolean flags (not anchored regex)
9
+ * to handle cases like "User's password.\n@vurb.hide".
10
+ *
11
+ * @module
12
+ */
13
+ // ── Constants ────────────────────────────────────────────
14
+ const VURB_HIDE = '@vurb.hide';
15
+ const VURB_TENANT_KEY = '@vurb.tenantKey';
16
+ const VURB_DESCRIBE_REGEX = /@vurb\.describe\("([^"]+)"\)/;
17
+ // ── Public API ───────────────────────────────────────────
18
+ /**
19
+ * Parse all @vurb.* annotations from a Prisma DMMF model.
20
+ *
21
+ * @param model - Prisma DMMF model
22
+ * @returns Parsed annotations with field map and tenant key
23
+ */
24
+ export function parseAnnotations(model) {
25
+ const fields = new Map();
26
+ let tenantKeyField;
27
+ for (const field of model.fields) {
28
+ const doc = field.documentation ?? '';
29
+ // Boolean flags — simple includes(), no anchored regex
30
+ // Handles multi-line docs like "User's password.\n@vurb.hide"
31
+ const hidden = doc.includes(VURB_HIDE);
32
+ const tenantKey = doc.includes(VURB_TENANT_KEY);
33
+ // String extraction — non-anchored regex
34
+ const describeMatch = doc.match(VURB_DESCRIBE_REGEX);
35
+ const description = describeMatch?.[1];
36
+ if (tenantKey) {
37
+ tenantKeyField = field.name;
38
+ }
39
+ const annotation = description !== undefined
40
+ ? { hidden, description, tenantKey }
41
+ : { hidden, tenantKey };
42
+ fields.set(field.name, annotation);
43
+ }
44
+ const result = tenantKeyField !== undefined
45
+ ? { fields, tenantKeyField }
46
+ : { fields };
47
+ return result;
48
+ }
49
+ //# sourceMappingURL=AnnotationParser.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"AnnotationParser.js","sourceRoot":"","sources":["../../src/parser/AnnotationParser.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AA2CH,4DAA4D;AAE5D,MAAM,SAAS,GAAG,YAAY,CAAC;AAC/B,MAAM,eAAe,GAAG,iBAAiB,CAAC;AAC1C,MAAM,mBAAmB,GAAG,8BAA8B,CAAC;AAE3D,4DAA4D;AAE5D;;;;;GAKG;AACH,MAAM,UAAU,gBAAgB,CAAC,KAAgB;IAC7C,MAAM,MAAM,GAAG,IAAI,GAAG,EAA2B,CAAC;IAClD,IAAI,cAAkC,CAAC;IAEvC,KAAK,MAAM,KAAK,IAAI,KAAK,CAAC,MAAM,EAAE,CAAC;QAC/B,MAAM,GAAG,GAAG,KAAK,CAAC,aAAa,IAAI,EAAE,CAAC;QAEtC,uDAAuD;QACvD,8DAA8D;QAC9D,MAAM,MAAM,GAAG,GAAG,CAAC,QAAQ,CAAC,SAAS,CAAC,CAAC;QACvC,MAAM,SAAS,GAAG,GAAG,CAAC,QAAQ,CAAC,eAAe,CAAC,CAAC;QAEhD,yCAAyC;QACzC,MAAM,aAAa,GAAG,GAAG,CAAC,KAAK,CAAC,mBAAmB,CAAC,CAAC;QACrD,MAAM,WAAW,GAAG,aAAa,EAAE,CAAC,CAAC,CAAC,CAAC;QAEvC,IAAI,SAAS,EAAE,CAAC;YACZ,cAAc,GAAG,KAAK,CAAC,IAAI,CAAC;QAChC,CAAC;QAED,MAAM,UAAU,GAAoB,WAAW,KAAK,SAAS;YACzD,CAAC,CAAC,EAAE,MAAM,EAAE,WAAW,EAAE,SAAS,EAAE;YACpC,CAAC,CAAC,EAAE,MAAM,EAAE,SAAS,EAAE,CAAC;QAC5B,MAAM,CAAC,GAAG,CAAC,KAAK,CAAC,IAAI,EAAE,UAAU,CAAC,CAAC;IACvC,CAAC;IAED,MAAM,MAAM,GAAqB,cAAc,KAAK,SAAS;QACzD,CAAC,CAAC,EAAE,MAAM,EAAE,cAAc,EAAE;QAC5B,CAAC,CAAC,EAAE,MAAM,EAAE,CAAC;IACjB,OAAO,MAAM,CAAC;AAClB,CAAC"}
@@ -0,0 +1,10 @@
1
+ /**
2
+ * Shared types for the prisma-gen package.
3
+ * @module
4
+ */
5
+ /** A generated file with its path and content */
6
+ export interface GeneratedFile {
7
+ readonly path: string;
8
+ readonly content: string;
9
+ }
10
+ //# sourceMappingURL=types.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,iDAAiD;AACjD,MAAM,WAAW,aAAa;IAC1B,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;CAC5B"}
package/dist/types.js ADDED
@@ -0,0 +1,6 @@
1
+ /**
2
+ * Shared types for the prisma-gen package.
3
+ * @module
4
+ */
5
+ export {};
6
+ //# sourceMappingURL=types.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.js","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA;;;GAGG"}
package/package.json ADDED
@@ -0,0 +1,67 @@
1
+ {
2
+ "name": "@vurb/prisma-gen",
3
+ "version": "3.1.31",
4
+ "description": "Prisma Generator for Vurb. Reads schema annotations (@vurb.hide, @vurb.describe, @vurb.tenantKey) and emits hardened Presenters and ToolBuilders with field-level security, tenant isolation, and OOM protection.",
5
+ "type": "module",
6
+ "main": "dist/index.js",
7
+ "types": "dist/index.d.ts",
8
+ "bin": {
9
+ "vurb-prisma-gen": "./dist/generator.js"
10
+ },
11
+ "exports": {
12
+ ".": {
13
+ "import": "./dist/index.js",
14
+ "types": "./dist/index.d.ts"
15
+ }
16
+ },
17
+ "scripts": {
18
+ "build": "tsc",
19
+ "test": "vitest run",
20
+ "prepublishOnly": "npm run build"
21
+ },
22
+ "keywords": [
23
+ "mcp",
24
+ "prisma",
25
+ "generator",
26
+ "vurb",
27
+ "zod",
28
+ "mva",
29
+ "presenter",
30
+ "ai",
31
+ "llm",
32
+ "tenant-isolation",
33
+ "code-generator"
34
+ ],
35
+ "author": "Renato Marinho",
36
+ "repository": {
37
+ "type": "git",
38
+ "url": "git+https://github.com/vinkius-labs/vurb.ts.git",
39
+ "directory": "packages/prisma-gen"
40
+ },
41
+ "bugs": {
42
+ "url": "https://github.com/vinkius-labs/vurb.ts/issues"
43
+ },
44
+ "homepage": "https://vurb.vinkius.com/",
45
+ "files": [
46
+ "dist",
47
+ "README.md"
48
+ ],
49
+ "engines": {
50
+ "node": ">=18.0.0"
51
+ },
52
+ "publishConfig": {
53
+ "access": "public"
54
+ },
55
+ "dependencies": {
56
+ "@prisma/generator-helper": "^6.0.0"
57
+ },
58
+ "peerDependencies": {
59
+ "@vurb/core": "^3.0.0",
60
+ "zod": "^3.25.1 || ^4.0.0"
61
+ },
62
+ "devDependencies": {
63
+ "@types/node": "^25.3.0",
64
+ "@vurb/core": ">=3.0.0"
65
+ },
66
+ "license": "Apache-2.0"
67
+ }