suparisma 1.0.5 → 1.0.7

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.
@@ -532,7 +532,7 @@ export function createSuparismaHook<
532
532
  // Single data collection for holding results
533
533
  const [data, setData] = useState<TWithRelations[]>([]);
534
534
  const [error, setError] = useState<Error | null>(null);
535
- const [loading, setLoading] = useState<boolean>(false);
535
+ const [loading, setLoading] = useState<boolean>(true);
536
536
 
537
537
  // This is the total count, unaffected by pagination limits
538
538
  const [count, setCount] = useState<number>(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
- // Create a manual property list for WithRelations interface
45
- const withRelationsProps = model.fields
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
- const finalType = field.isList ? `${baseType}[]` : baseType;
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
- let baseType;
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
- let baseType;
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 Prisma schema to extract model information including search annotations
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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "suparisma",
3
- "version": "1.0.5",
3
+ "version": "1.0.7",
4
4
  "description": "Opinionated typesafe React realtime CRUD hooks generator for all your Supabase tables, powered by Prisma.",
5
5
  "main": "dist/index.js",
6
6
  "repository": {
@@ -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())