suparisma 1.0.5 → 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/dist/generators/typeGenerator.js +70 -48
- package/dist/parser.js +102 -1
- package/package.json +1 -1
- package/prisma/schema.prisma +4 -0
|
@@ -41,12 +41,25 @@ function generateModelTypesFile(model) {
|
|
|
41
41
|
// Find the actual field names for createdAt and updatedAt
|
|
42
42
|
const createdAtField = model.fields.find(field => field.isCreatedAt)?.name || 'createdAt';
|
|
43
43
|
const updatedAtField = model.fields.find(field => field.isUpdatedAt)?.name || 'updatedAt';
|
|
44
|
-
//
|
|
45
|
-
const
|
|
46
|
-
.filter((field) => !relationObjectFields.includes(field.name) && !foreignKeyFields.includes(field.name))
|
|
47
|
-
.map((field) => {
|
|
48
|
-
const isOptional = field.isOptional;
|
|
44
|
+
// Helper function to get TypeScript type for a field, considering zod directives
|
|
45
|
+
const getFieldType = (field) => {
|
|
49
46
|
let baseType;
|
|
47
|
+
// Check if field has a custom zod directive
|
|
48
|
+
if (field.zodDirective && field.type === 'Json') {
|
|
49
|
+
// For Json fields with zod directives, we need to infer the type from the zod schema
|
|
50
|
+
// This is a simplified approach - you might want to make this more sophisticated
|
|
51
|
+
if (field.zodDirective.includes('z.array(') && field.zodDirective.includes('LLMNodeSchema')) {
|
|
52
|
+
// Handle z.array(LLMNodeSchema).nullable() case
|
|
53
|
+
return field.zodDirective.includes('.nullable()') ? 'LLMNode[] | null' : 'LLMNode[]';
|
|
54
|
+
}
|
|
55
|
+
else if (field.zodDirective.includes('LLMNodeSchema')) {
|
|
56
|
+
// Handle single LLMNodeSchema case
|
|
57
|
+
return field.zodDirective.includes('.nullable()') ? 'LLMNode | null' : 'LLMNode';
|
|
58
|
+
}
|
|
59
|
+
// For other custom zod directives on Json fields, default to any for now
|
|
60
|
+
return 'any';
|
|
61
|
+
}
|
|
62
|
+
// Standard type mapping
|
|
50
63
|
switch (field.type) {
|
|
51
64
|
case 'Int':
|
|
52
65
|
case 'Float':
|
|
@@ -65,7 +78,14 @@ function generateModelTypesFile(model) {
|
|
|
65
78
|
// Covers String, Enum names (e.g., "SomeEnum"), Bytes, Decimal, etc.
|
|
66
79
|
baseType = 'string';
|
|
67
80
|
}
|
|
68
|
-
|
|
81
|
+
return field.isList ? `${baseType}[]` : baseType;
|
|
82
|
+
};
|
|
83
|
+
// Create a manual property list for WithRelations interface
|
|
84
|
+
const withRelationsProps = model.fields
|
|
85
|
+
.filter((field) => !relationObjectFields.includes(field.name) && !foreignKeyFields.includes(field.name))
|
|
86
|
+
.map((field) => {
|
|
87
|
+
const isOptional = field.isOptional;
|
|
88
|
+
const finalType = getFieldType(field);
|
|
69
89
|
return ` ${field.name}${isOptional ? '?' : ''}: ${finalType};`;
|
|
70
90
|
});
|
|
71
91
|
// Add foreign key fields
|
|
@@ -83,26 +103,7 @@ function generateModelTypesFile(model) {
|
|
|
83
103
|
.map((field) => {
|
|
84
104
|
// Make fields with default values optional in CreateInput
|
|
85
105
|
const isOptional = field.isOptional || defaultValueFields.includes(field.name);
|
|
86
|
-
|
|
87
|
-
switch (field.type) {
|
|
88
|
-
case 'Int':
|
|
89
|
-
case 'Float':
|
|
90
|
-
baseType = 'number';
|
|
91
|
-
break;
|
|
92
|
-
case 'Boolean':
|
|
93
|
-
baseType = 'boolean';
|
|
94
|
-
break;
|
|
95
|
-
case 'DateTime':
|
|
96
|
-
baseType = 'string'; // ISO date string
|
|
97
|
-
break;
|
|
98
|
-
case 'Json':
|
|
99
|
-
baseType = 'any'; // Or a more specific structured type if available
|
|
100
|
-
break;
|
|
101
|
-
default:
|
|
102
|
-
// Covers String, Enum names (e.g., "SomeEnum"), Bytes, Decimal, etc.
|
|
103
|
-
baseType = 'string';
|
|
104
|
-
}
|
|
105
|
-
const finalType = field.isList ? `${baseType}[]` : baseType;
|
|
106
|
+
const finalType = getFieldType(field);
|
|
106
107
|
return ` ${field.name}${isOptional ? '?' : ''}: ${finalType};`;
|
|
107
108
|
});
|
|
108
109
|
// Add foreign key fields to CreateInput
|
|
@@ -114,11 +115,50 @@ function generateModelTypesFile(model) {
|
|
|
114
115
|
createInputProps.push(` ${field}${isOptional ? '?' : ''}: ${fieldInfo.type === 'Int' ? 'number' : 'string'};`);
|
|
115
116
|
}
|
|
116
117
|
});
|
|
118
|
+
// Generate imports section for zod custom types
|
|
119
|
+
let customImports = '';
|
|
120
|
+
if (model.zodImports && model.zodImports.length > 0) {
|
|
121
|
+
// Add zod import for type inference
|
|
122
|
+
customImports = 'import { z } from \'zod\';\n';
|
|
123
|
+
// Get the zod schemas file path from environment variable
|
|
124
|
+
const zodSchemasPath = process.env.ZOD_SCHEMAS_FILE_PATH || '../commonTypes';
|
|
125
|
+
// Add custom imports with environment variable path
|
|
126
|
+
customImports += model.zodImports
|
|
127
|
+
.map(zodImport => {
|
|
128
|
+
// Extract the types from the original import statement
|
|
129
|
+
const typeMatch = zodImport.importStatement.match(/import\s+{\s*([^}]+)\s*}\s+from/);
|
|
130
|
+
if (typeMatch) {
|
|
131
|
+
const types = typeMatch[1].trim();
|
|
132
|
+
return `import { ${types} } from '${zodSchemasPath}'`;
|
|
133
|
+
}
|
|
134
|
+
// Fallback to original import if parsing fails
|
|
135
|
+
return zodImport.importStatement;
|
|
136
|
+
})
|
|
137
|
+
.join('\n') + '\n\n';
|
|
138
|
+
// Add type definitions for imported zod schemas if needed
|
|
139
|
+
// This is a simplified approach - you might want to make this more sophisticated
|
|
140
|
+
const customTypeDefinitions = model.zodImports
|
|
141
|
+
.flatMap(zodImport => zodImport.types)
|
|
142
|
+
.filter((type, index, array) => array.indexOf(type) === index) // Remove duplicates
|
|
143
|
+
.map(type => {
|
|
144
|
+
// If it ends with 'Schema', create a corresponding type
|
|
145
|
+
if (type.endsWith('Schema')) {
|
|
146
|
+
const typeName = type.replace('Schema', '');
|
|
147
|
+
return `export type ${typeName} = z.infer<typeof ${type}>;`;
|
|
148
|
+
}
|
|
149
|
+
return '';
|
|
150
|
+
})
|
|
151
|
+
.filter(Boolean)
|
|
152
|
+
.join('\n');
|
|
153
|
+
if (customTypeDefinitions) {
|
|
154
|
+
customImports += customTypeDefinitions + '\n\n';
|
|
155
|
+
}
|
|
156
|
+
}
|
|
117
157
|
// Generate the type content with TSDoc comments
|
|
118
158
|
const typeContent = `// THIS FILE IS AUTO-GENERATED - DO NOT EDIT DIRECTLY
|
|
119
159
|
// Edit the generator script instead
|
|
120
160
|
|
|
121
|
-
import type { ${modelName} } from '@prisma/client';
|
|
161
|
+
${customImports}import type { ${modelName} } from '@prisma/client';
|
|
122
162
|
import type { ModelResult, SuparismaOptions, SearchQuery, SearchState, FilterOperators } from '../utils/core';
|
|
123
163
|
|
|
124
164
|
/**
|
|
@@ -230,26 +270,7 @@ ${model.fields
|
|
|
230
270
|
.filter((field) => !relationObjectFields.includes(field.name) && !foreignKeyFields.includes(field.name))
|
|
231
271
|
.map((field) => {
|
|
232
272
|
const isOptional = true; // All where fields are optional
|
|
233
|
-
|
|
234
|
-
switch (field.type) {
|
|
235
|
-
case 'Int':
|
|
236
|
-
case 'Float':
|
|
237
|
-
baseType = 'number';
|
|
238
|
-
break;
|
|
239
|
-
case 'Boolean':
|
|
240
|
-
baseType = 'boolean';
|
|
241
|
-
break;
|
|
242
|
-
case 'DateTime':
|
|
243
|
-
baseType = 'string'; // ISO date string
|
|
244
|
-
break;
|
|
245
|
-
case 'Json':
|
|
246
|
-
baseType = 'any'; // Or a more specific structured type if available
|
|
247
|
-
break;
|
|
248
|
-
default:
|
|
249
|
-
// Covers String, Enum names (e.g., "SomeEnum"), Bytes, Decimal, etc.
|
|
250
|
-
baseType = 'string';
|
|
251
|
-
}
|
|
252
|
-
const finalType = field.isList ? `${baseType}[]` : baseType;
|
|
273
|
+
const finalType = getFieldType(field);
|
|
253
274
|
const filterType = `${finalType} | FilterOperators<${finalType}>`;
|
|
254
275
|
return ` ${field.name}${isOptional ? '?' : ''}: ${filterType};`;
|
|
255
276
|
})
|
|
@@ -643,6 +664,7 @@ ${createInputProps
|
|
|
643
664
|
searchFields,
|
|
644
665
|
defaultValues: Object.keys(defaultValues).length > 0 ? defaultValues : undefined,
|
|
645
666
|
createdAtField,
|
|
646
|
-
updatedAtField
|
|
667
|
+
updatedAtField,
|
|
668
|
+
zodImports: model.zodImports // Pass through zod imports
|
|
647
669
|
};
|
|
648
670
|
}
|
package/dist/parser.js
CHANGED
|
@@ -6,7 +6,67 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
6
6
|
exports.parsePrismaSchema = parsePrismaSchema;
|
|
7
7
|
const fs_1 = __importDefault(require("fs"));
|
|
8
8
|
/**
|
|
9
|
-
* Parse
|
|
9
|
+
* Parse zod directive from comment
|
|
10
|
+
* Supports formats like:
|
|
11
|
+
* /// @zod.custom.use(z.string().refine(...))
|
|
12
|
+
* /// @zod.string.min(3).max(10)
|
|
13
|
+
*/
|
|
14
|
+
function parseZodDirective(comment) {
|
|
15
|
+
// Remove leading /// and whitespace
|
|
16
|
+
const cleanComment = comment.replace(/^\/\/\/?\s*/, '').trim();
|
|
17
|
+
// Look for @zod.custom.use() format
|
|
18
|
+
const customUseMatch = cleanComment.match(/@zod\.custom\.use\((.+)\)$/);
|
|
19
|
+
if (customUseMatch) {
|
|
20
|
+
return customUseMatch[1].trim();
|
|
21
|
+
}
|
|
22
|
+
// Look for other @zod patterns like @zod.string.min(3)
|
|
23
|
+
const zodMatch = cleanComment.match(/@zod\.(.+)$/);
|
|
24
|
+
if (zodMatch) {
|
|
25
|
+
const zodChain = zodMatch[1].trim();
|
|
26
|
+
// Convert to actual zod syntax - this is a basic implementation
|
|
27
|
+
// For more complex cases, you might want to build a more sophisticated parser
|
|
28
|
+
return `z.${zodChain}`;
|
|
29
|
+
}
|
|
30
|
+
return undefined;
|
|
31
|
+
}
|
|
32
|
+
/**
|
|
33
|
+
* Parse zod import from comment
|
|
34
|
+
* Supports format like:
|
|
35
|
+
* /// @zod.import(["import { LLMNodeSchema } from '../commonTypes'"])
|
|
36
|
+
*/
|
|
37
|
+
function parseZodImport(comment) {
|
|
38
|
+
const cleanComment = comment.replace(/^\/\/\/?\s*/, '').trim();
|
|
39
|
+
// Look for @zod.import([...]) format
|
|
40
|
+
const importMatch = cleanComment.match(/@zod\.import\(\[(.*)\]\)/);
|
|
41
|
+
if (!importMatch) {
|
|
42
|
+
return [];
|
|
43
|
+
}
|
|
44
|
+
const importsString = importMatch[1];
|
|
45
|
+
const imports = [];
|
|
46
|
+
// Parse individual import statements within the array
|
|
47
|
+
// Handle nested quotes properly - look for quoted strings that start with "import"
|
|
48
|
+
const importRegex = /"(import\s+[^"]+)"|'(import\s+[^']+)'/g;
|
|
49
|
+
let match;
|
|
50
|
+
while ((match = importRegex.exec(importsString)) !== null) {
|
|
51
|
+
// Get the import statement from either the double-quoted or single-quoted group
|
|
52
|
+
const importStatement = match[1] || match[2];
|
|
53
|
+
// Extract types from import statement
|
|
54
|
+
// e.g., "import { LLMNodeSchema, AnotherType } from '../commonTypes'"
|
|
55
|
+
const typeMatch = importStatement.match(/import\s+{\s*([^}]+)\s*}\s+from/);
|
|
56
|
+
const types = [];
|
|
57
|
+
if (typeMatch) {
|
|
58
|
+
// Split by comma and clean up whitespace
|
|
59
|
+
types.push(...typeMatch[1].split(',').map(t => t.trim()));
|
|
60
|
+
}
|
|
61
|
+
imports.push({
|
|
62
|
+
importStatement,
|
|
63
|
+
types
|
|
64
|
+
});
|
|
65
|
+
}
|
|
66
|
+
return imports;
|
|
67
|
+
}
|
|
68
|
+
/**
|
|
69
|
+
* Parse Prisma schema to extract model information including search annotations and zod directives
|
|
10
70
|
*/
|
|
11
71
|
function parsePrismaSchema(schemaPath) {
|
|
12
72
|
const schema = fs_1.default.readFileSync(schemaPath, 'utf-8');
|
|
@@ -33,9 +93,12 @@ function parsePrismaSchema(schemaPath) {
|
|
|
33
93
|
const fields = [];
|
|
34
94
|
// Track fields with @enableSearch annotation
|
|
35
95
|
const searchFields = [];
|
|
96
|
+
// Track zod imports at model level
|
|
97
|
+
const zodImports = [];
|
|
36
98
|
const lines = modelBody.split('\n');
|
|
37
99
|
let lastFieldName = '';
|
|
38
100
|
let lastFieldType = '';
|
|
101
|
+
let pendingZodDirective;
|
|
39
102
|
for (let i = 0; i < lines.length; i++) {
|
|
40
103
|
const line = lines[i]?.trim();
|
|
41
104
|
// Skip blank lines and non-field lines
|
|
@@ -52,6 +115,14 @@ function parsePrismaSchema(schemaPath) {
|
|
|
52
115
|
}
|
|
53
116
|
// Check if line is a comment
|
|
54
117
|
if (line.startsWith('//')) {
|
|
118
|
+
// Parse zod directives from comments
|
|
119
|
+
const zodDirective = parseZodDirective(line);
|
|
120
|
+
if (zodDirective) {
|
|
121
|
+
pendingZodDirective = zodDirective;
|
|
122
|
+
}
|
|
123
|
+
// Parse zod imports from comments
|
|
124
|
+
const zodImportInfos = parseZodImport(line);
|
|
125
|
+
zodImports.push(...zodImportInfos);
|
|
55
126
|
continue;
|
|
56
127
|
}
|
|
57
128
|
// Parse field definition - Updated to handle array types
|
|
@@ -91,6 +162,14 @@ function parsePrismaSchema(schemaPath) {
|
|
|
91
162
|
type: baseFieldType || '',
|
|
92
163
|
});
|
|
93
164
|
}
|
|
165
|
+
// Check for inline zod directive
|
|
166
|
+
let fieldZodDirective = pendingZodDirective;
|
|
167
|
+
if (line.includes('/// @zod.')) {
|
|
168
|
+
const inlineZodDirective = parseZodDirective(line);
|
|
169
|
+
if (inlineZodDirective) {
|
|
170
|
+
fieldZodDirective = inlineZodDirective;
|
|
171
|
+
}
|
|
172
|
+
}
|
|
94
173
|
if (fieldName && baseFieldType) {
|
|
95
174
|
fields.push({
|
|
96
175
|
name: fieldName,
|
|
@@ -105,8 +184,11 @@ function parsePrismaSchema(schemaPath) {
|
|
|
105
184
|
defaultValue, // Add the extracted default value
|
|
106
185
|
isRelation,
|
|
107
186
|
isList: isArray, // Add the isList property
|
|
187
|
+
zodDirective: fieldZodDirective, // Add zod directive
|
|
108
188
|
});
|
|
109
189
|
}
|
|
190
|
+
// Clear pending zod directive after using it
|
|
191
|
+
pendingZodDirective = undefined;
|
|
110
192
|
}
|
|
111
193
|
}
|
|
112
194
|
// Check for model-level @enableSearch before the model definition
|
|
@@ -122,11 +204,30 @@ function parsePrismaSchema(schemaPath) {
|
|
|
122
204
|
}
|
|
123
205
|
});
|
|
124
206
|
}
|
|
207
|
+
// Also check for model-level zod imports before the model definition
|
|
208
|
+
const modelStartIndex = schema.indexOf(`model ${modelName}`);
|
|
209
|
+
if (modelStartIndex !== -1) {
|
|
210
|
+
// Look backwards for any /// @zod.import directives before this model
|
|
211
|
+
const beforeModel = schema.substring(0, modelStartIndex);
|
|
212
|
+
const lines = beforeModel.split('\n').reverse(); // Start from model and go backwards
|
|
213
|
+
for (const line of lines) {
|
|
214
|
+
const trimmedLine = line.trim();
|
|
215
|
+
if (trimmedLine.startsWith('///') && trimmedLine.includes('@zod.import')) {
|
|
216
|
+
const modelLevelImports = parseZodImport(trimmedLine);
|
|
217
|
+
zodImports.push(...modelLevelImports);
|
|
218
|
+
}
|
|
219
|
+
else if (trimmedLine && !trimmedLine.startsWith('///') && !trimmedLine.startsWith('//')) {
|
|
220
|
+
// Stop if we hit a non-comment line (another model or other content)
|
|
221
|
+
break;
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
}
|
|
125
225
|
models.push({
|
|
126
226
|
name: modelName,
|
|
127
227
|
mappedName: mappedName || '',
|
|
128
228
|
fields,
|
|
129
229
|
searchFields: searchFields.length > 0 ? searchFields : undefined,
|
|
230
|
+
zodImports: zodImports.length > 0 ? zodImports : undefined,
|
|
130
231
|
});
|
|
131
232
|
}
|
|
132
233
|
return models;
|
package/package.json
CHANGED
package/prisma/schema.prisma
CHANGED
|
@@ -28,6 +28,7 @@ model User {
|
|
|
28
28
|
updatedAt DateTime @updatedAt
|
|
29
29
|
}
|
|
30
30
|
|
|
31
|
+
/// @zod.import(["import { LLMNodeSchema } from '../commonTypes'"])
|
|
31
32
|
model Thing {
|
|
32
33
|
id String @id @default(uuid())
|
|
33
34
|
// @enableSearch
|
|
@@ -35,6 +36,9 @@ model Thing {
|
|
|
35
36
|
stringArray String[]
|
|
36
37
|
someEnum SomeEnum @default(ONE)
|
|
37
38
|
someNumber Int?
|
|
39
|
+
|
|
40
|
+
/// [Json]
|
|
41
|
+
someJson Json? /// @zod.custom.use(z.array(LLMNodeSchema).nullable())
|
|
38
42
|
userId String?
|
|
39
43
|
user User? @relation(fields: [userId], references: [id])
|
|
40
44
|
createdAt DateTime @default(now())
|