suparisma 0.0.1
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/.gitattributes +2 -0
- package/LICENSE +21 -0
- package/README.md +4 -0
- package/dist/config.js +7 -0
- package/dist/generated/supabase-client-generated.js +7 -0
- package/dist/generators/coreGenerator.js +1430 -0
- package/dist/generators/hookGenerator.js +108 -0
- package/dist/generators/indexGenerator.js +100 -0
- package/dist/generators/supabaseClientGenerator.js +29 -0
- package/dist/generators/typeGenerator.js +566 -0
- package/dist/hooks/generated/UserTypes.js +2 -0
- package/dist/hooks/generated/core.js +1089 -0
- package/dist/hooks/generated/index.js +33 -0
- package/dist/hooks/generated/useSuparismaUser.js +60 -0
- package/dist/index.js +259 -0
- package/dist/parser.js +117 -0
- package/dist/types.js +2 -0
- package/package.json +28 -0
- package/prisma/schema.prisma +22 -0
- package/src/config.ts +7 -0
- package/src/generated/hooks/useSuparismaUser.ts +77 -0
- package/src/generated/index.ts +50 -0
- package/src/generated/types/UserTypes.ts +400 -0
- package/src/generated/utils/core.ts +1413 -0
- package/src/generated/utils/supabase-client.ts +7 -0
- package/src/generators/coreGenerator.ts +1426 -0
- package/src/generators/hookGenerator.ts +110 -0
- package/src/generators/indexGenerator.ts +117 -0
- package/src/generators/supabaseClientGenerator.ts +24 -0
- package/src/generators/typeGenerator.ts +587 -0
- package/src/index.ts +339 -0
- package/src/parser.ts +134 -0
- package/src/suparisma/generated/UserTypes.ts +400 -0
- package/src/suparisma/generated/core.ts +1413 -0
- package/src/suparisma/generated/hooks/useSuparismaUser.ts +77 -0
- package/src/suparisma/generated/index.ts +50 -0
- package/src/suparisma/generated/supabase-client-generated.ts +9 -0
- package/src/suparisma/generated/types/UserTypes.ts +400 -0
- package/src/suparisma/generated/useSuparismaUser.ts +77 -0
- package/src/suparisma/generated/utils/core.ts +1413 -0
- package/src/suparisma/generated/utils/supabase-client.ts +7 -0
- package/src/types.ts +57 -0
- package/tsconfig.json +20 -0
package/src/index.ts
ADDED
|
@@ -0,0 +1,339 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import fs from 'fs';
|
|
3
|
+
import path from 'path';
|
|
4
|
+
import * as dotenv from 'dotenv';
|
|
5
|
+
dotenv.config();
|
|
6
|
+
|
|
7
|
+
// Import configuration
|
|
8
|
+
import { PRISMA_SCHEMA_PATH, OUTPUT_DIR, TYPES_DIR, HOOKS_DIR, UTILS_DIR } from './config';
|
|
9
|
+
|
|
10
|
+
// Import type definitions
|
|
11
|
+
import { ProcessedModelInfo } from './types';
|
|
12
|
+
|
|
13
|
+
// Import parsers and generators
|
|
14
|
+
import { parsePrismaSchema } from './parser';
|
|
15
|
+
import { generateCoreFile } from './generators/coreGenerator';
|
|
16
|
+
import { generateModelTypesFile } from './generators/typeGenerator';
|
|
17
|
+
import { generateModelHookFile } from './generators/hookGenerator';
|
|
18
|
+
import { generateMainIndexFile } from './generators/indexGenerator';
|
|
19
|
+
import { generateSupabaseClientFile } from './generators/supabaseClientGenerator';
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Checks for essential environment variables and throws an error if any are missing.
|
|
23
|
+
*/
|
|
24
|
+
function checkEnvironmentVariables() {
|
|
25
|
+
const requiredEnvVars = [
|
|
26
|
+
'DATABASE_URL',
|
|
27
|
+
'NEXT_PUBLIC_SUPABASE_URL',
|
|
28
|
+
'NEXT_PUBLIC_SUPABASE_ANON_KEY',
|
|
29
|
+
];
|
|
30
|
+
const missingVars = requiredEnvVars.filter(varName => !process.env[varName]);
|
|
31
|
+
|
|
32
|
+
if (missingVars.length > 0) {
|
|
33
|
+
let errorMessage = 'Error: Missing required environment variables:\n';
|
|
34
|
+
missingVars.forEach(varName => {
|
|
35
|
+
errorMessage += `- ${varName}: This variable is essential for the generator to function correctly. `;
|
|
36
|
+
if (varName === 'DATABASE_URL') {
|
|
37
|
+
errorMessage += 'It is used by Prisma to connect to your database. Please ensure it is set in your .env file or as an environment variable (e.g., postgresql://user:password@host:port/database).\n';
|
|
38
|
+
} else if (varName === 'NEXT_PUBLIC_SUPABASE_URL') {
|
|
39
|
+
errorMessage += 'This is your Supabase project URL. It is required by the Supabase client. Please set it in your .env file or as an environment variable (e.g., https://your-project-id.supabase.co).\n';
|
|
40
|
+
} else if (varName === 'NEXT_PUBLIC_SUPABASE_ANON_KEY') {
|
|
41
|
+
errorMessage += 'This is your Supabase project public anonymous key. It is required by the Supabase client. Please set it in your .env file or as an environment variable.\n';
|
|
42
|
+
}
|
|
43
|
+
});
|
|
44
|
+
errorMessage += '\nPlease add these variables to your .env file or ensure they are available in your environment and try again.';
|
|
45
|
+
throw new Error(errorMessage);
|
|
46
|
+
}
|
|
47
|
+
console.log('✅ All required environment variables are set.');
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* Extracts comments from a Prisma schema
|
|
52
|
+
* Looks for // @enableRealtime and // @enableSearch
|
|
53
|
+
*/
|
|
54
|
+
interface ModelInfo {
|
|
55
|
+
name: string;
|
|
56
|
+
tableName: string;
|
|
57
|
+
enableRealtime: boolean;
|
|
58
|
+
searchFields: Array<{
|
|
59
|
+
name: string;
|
|
60
|
+
type: string;
|
|
61
|
+
}>;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
function analyzePrismaSchema(schemaPath: string): ModelInfo[] {
|
|
65
|
+
try {
|
|
66
|
+
const schemaContent = fs.readFileSync(schemaPath, 'utf8');
|
|
67
|
+
const modelInfos: ModelInfo[] = [];
|
|
68
|
+
|
|
69
|
+
// Regular expression to match model definitions with comments
|
|
70
|
+
const modelRegex = /(?:\/\/\s*@enableRealtime\s*)?\s*model\s+(\w+)\s*{([\s\S]*?)}/g;
|
|
71
|
+
let modelMatch;
|
|
72
|
+
|
|
73
|
+
while ((modelMatch = modelRegex.exec(schemaContent)) !== null) {
|
|
74
|
+
const modelName = modelMatch[1];
|
|
75
|
+
const modelBody = modelMatch[2];
|
|
76
|
+
if (!modelName || !modelBody) {
|
|
77
|
+
console.error('Model name or body not found');
|
|
78
|
+
continue;
|
|
79
|
+
}
|
|
80
|
+
const tableName = modelMatch[0].includes('@map')
|
|
81
|
+
? modelMatch[0].match(/@map\s*\(\s*["'](.+?)["']\s*\)/)?.at(1) || modelName
|
|
82
|
+
: modelName;
|
|
83
|
+
|
|
84
|
+
// Check if model has @enableRealtime comment
|
|
85
|
+
const enableRealtime = modelMatch[0].includes('// @enableRealtime');
|
|
86
|
+
|
|
87
|
+
// Find fields with @enableSearch comment
|
|
88
|
+
const searchFields: Array<{ name: string; type: string }> = [];
|
|
89
|
+
const fieldRegex = /(\w+)\s+(\w+)(?:\?.+?)?\s+(?:@.+?)?\s*(?:\/\/\s*@enableSearch)?/g;
|
|
90
|
+
let fieldMatch;
|
|
91
|
+
|
|
92
|
+
while ((fieldMatch = fieldRegex.exec(modelBody)) !== null) {
|
|
93
|
+
if (fieldMatch[0].includes('// @enableSearch')) {
|
|
94
|
+
searchFields.push({
|
|
95
|
+
name: fieldMatch[1] || '',
|
|
96
|
+
type: fieldMatch[2] || '',
|
|
97
|
+
});
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
modelInfos.push({
|
|
102
|
+
name: modelName,
|
|
103
|
+
tableName,
|
|
104
|
+
enableRealtime,
|
|
105
|
+
searchFields,
|
|
106
|
+
});
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
return modelInfos;
|
|
110
|
+
} catch (error) {
|
|
111
|
+
console.error('Error analyzing Prisma schema:', error);
|
|
112
|
+
return [];
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
/**
|
|
117
|
+
* Configure database tables for proper realtime functionality and search
|
|
118
|
+
* 1. Sets REPLICA IDENTITY FULL and enables realtime for models with @enableRealtime
|
|
119
|
+
* 2. Creates search functions for fields with @enableSearch
|
|
120
|
+
*/
|
|
121
|
+
async function configurePrismaTablesForSuparisma(schemaPath: string) {
|
|
122
|
+
try {
|
|
123
|
+
// COMPLETELY BYPASS NORMAL OPERATION FOR SIMPLICITY
|
|
124
|
+
console.log('🔧 Using direct SQL approach to avoid PostgreSQL case sensitivity issues...');
|
|
125
|
+
|
|
126
|
+
// Load environment variables
|
|
127
|
+
dotenv.config();
|
|
128
|
+
|
|
129
|
+
// Get direct PostgreSQL connection URL
|
|
130
|
+
const directUrl = process.env.DIRECT_URL;
|
|
131
|
+
if (!directUrl) {
|
|
132
|
+
console.warn(
|
|
133
|
+
'⚠️ DIRECT_URL environment variable not found. Skipping database configuration.'
|
|
134
|
+
);
|
|
135
|
+
return;
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
// Analyze Prisma schema for models, realtime and search annotations
|
|
139
|
+
const modelInfos = analyzePrismaSchema(schemaPath);
|
|
140
|
+
|
|
141
|
+
// Dynamically import pg package
|
|
142
|
+
const pg = await import('pg');
|
|
143
|
+
const { Pool } = pg.default || pg;
|
|
144
|
+
|
|
145
|
+
// Connect to PostgreSQL database
|
|
146
|
+
const pool = new Pool({ connectionString: directUrl });
|
|
147
|
+
|
|
148
|
+
console.log('🔌 Connected to PostgreSQL database');
|
|
149
|
+
|
|
150
|
+
// Get all tables from database directly
|
|
151
|
+
const { rows: allTables } = await pool.query(`
|
|
152
|
+
SELECT table_name
|
|
153
|
+
FROM information_schema.tables
|
|
154
|
+
WHERE table_schema = 'public'
|
|
155
|
+
`);
|
|
156
|
+
|
|
157
|
+
console.log(`📋 Found ${allTables.length} tables in the 'public' schema`);
|
|
158
|
+
allTables.forEach((t: any) => console.log(` - ${t.table_name}`));
|
|
159
|
+
|
|
160
|
+
// DIRECT APPROACH: Hardcode SQL for each known Prisma model type
|
|
161
|
+
for (const model of modelInfos) {
|
|
162
|
+
try {
|
|
163
|
+
// Find the matching table regardless of case
|
|
164
|
+
const matchingTable = allTables.find(
|
|
165
|
+
(t: any) => t.table_name.toLowerCase() === model.tableName.toLowerCase()
|
|
166
|
+
);
|
|
167
|
+
|
|
168
|
+
if (!matchingTable) {
|
|
169
|
+
console.warn(`⚠️ Could not find a table for model ${model.name}. Skipping.`);
|
|
170
|
+
continue;
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
// Use the exact case of the table as it exists in the database
|
|
174
|
+
const actualTableName = matchingTable.table_name;
|
|
175
|
+
console.log(`🔍 Model ${model.name} -> Actual table: ${actualTableName}`);
|
|
176
|
+
|
|
177
|
+
if (model.enableRealtime) {
|
|
178
|
+
// Explicitly use double quotes for mixed case identifiers
|
|
179
|
+
// try {
|
|
180
|
+
// await pool.query(`ALTER TABLE "${actualTableName}" REPLICA IDENTITY FULL;`);
|
|
181
|
+
// console.log(`✅ Set REPLICA IDENTITY FULL on "${actualTableName}"`);
|
|
182
|
+
// } catch (err: any ) {
|
|
183
|
+
// console.error(`❌ Failed to set REPLICA IDENTITY on "${actualTableName}": ${err.message}`);
|
|
184
|
+
// }
|
|
185
|
+
|
|
186
|
+
// Directly add the table to Supabase Realtime publication
|
|
187
|
+
try {
|
|
188
|
+
await pool.query(`
|
|
189
|
+
ALTER PUBLICATION supabase_realtime ADD TABLE "${actualTableName}";
|
|
190
|
+
`);
|
|
191
|
+
console.log(`✅ Added "${actualTableName}" to supabase_realtime publication`);
|
|
192
|
+
} catch (err: any) {
|
|
193
|
+
// If error contains "already exists", this is fine
|
|
194
|
+
if (err.message.includes('already member')) {
|
|
195
|
+
console.log(
|
|
196
|
+
`ℹ️ Table "${actualTableName}" was already in supabase_realtime publication`
|
|
197
|
+
);
|
|
198
|
+
} else {
|
|
199
|
+
console.error(
|
|
200
|
+
`❌ Failed to add "${actualTableName}" to supabase_realtime: ${err.message}`
|
|
201
|
+
);
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
// Handle search fields if any
|
|
207
|
+
if (model.searchFields.length > 0) {
|
|
208
|
+
// Get all columns for this table
|
|
209
|
+
const { rows: columns } = await pool.query(
|
|
210
|
+
`
|
|
211
|
+
SELECT column_name
|
|
212
|
+
FROM information_schema.columns
|
|
213
|
+
WHERE table_schema = 'public' AND table_name = $1
|
|
214
|
+
`,
|
|
215
|
+
[actualTableName]
|
|
216
|
+
);
|
|
217
|
+
|
|
218
|
+
for (const searchField of model.searchFields) {
|
|
219
|
+
// Find matching column regardless of case
|
|
220
|
+
const matchingColumn = columns.find(
|
|
221
|
+
(c) => c.column_name.toLowerCase() === searchField.name.toLowerCase()
|
|
222
|
+
);
|
|
223
|
+
|
|
224
|
+
if (!matchingColumn) {
|
|
225
|
+
console.warn(
|
|
226
|
+
`⚠️ Could not find column ${searchField.name} in table ${actualTableName}. Skipping search function.`
|
|
227
|
+
);
|
|
228
|
+
continue;
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
const actualColumnName = matchingColumn.column_name;
|
|
232
|
+
const functionName = `search_${actualTableName.toLowerCase()}_by_${actualColumnName.toLowerCase()}_prefix`;
|
|
233
|
+
const indexName = `idx_search_${actualTableName.toLowerCase()}_${actualColumnName.toLowerCase()}`;
|
|
234
|
+
|
|
235
|
+
try {
|
|
236
|
+
// Create search function with exact column case
|
|
237
|
+
await pool.query(`
|
|
238
|
+
CREATE OR REPLACE FUNCTION "public"."${functionName}"(prefix text)
|
|
239
|
+
RETURNS SETOF "public"."${actualTableName}" AS $$
|
|
240
|
+
BEGIN
|
|
241
|
+
RETURN QUERY
|
|
242
|
+
SELECT * FROM "public"."${actualTableName}"
|
|
243
|
+
WHERE to_tsvector('english', "${actualColumnName}") @@ to_tsquery('english', prefix || ':*');
|
|
244
|
+
END;
|
|
245
|
+
$$ LANGUAGE plpgsql;
|
|
246
|
+
`);
|
|
247
|
+
|
|
248
|
+
console.log(`✅ Created search function for ${actualTableName}.${actualColumnName}`);
|
|
249
|
+
|
|
250
|
+
// FIXED: Properly quote identifiers in the index creation query
|
|
251
|
+
await pool.query(`
|
|
252
|
+
DO $$
|
|
253
|
+
BEGIN
|
|
254
|
+
IF NOT EXISTS (
|
|
255
|
+
SELECT 1 FROM pg_indexes
|
|
256
|
+
WHERE schemaname = 'public'
|
|
257
|
+
AND tablename = '${actualTableName}'
|
|
258
|
+
AND indexname = '${indexName}'
|
|
259
|
+
) THEN
|
|
260
|
+
CREATE INDEX "${indexName}" ON "public"."${actualTableName}"
|
|
261
|
+
USING GIN (to_tsvector('english', "${actualColumnName}"));
|
|
262
|
+
END IF;
|
|
263
|
+
END;
|
|
264
|
+
$$;
|
|
265
|
+
`);
|
|
266
|
+
|
|
267
|
+
console.log(`✅ Created search index for ${actualTableName}.${actualColumnName}`);
|
|
268
|
+
} catch (err: any) {
|
|
269
|
+
console.error(
|
|
270
|
+
`❌ Failed to set up search for "${actualTableName}.${actualColumnName}": ${err.message}`
|
|
271
|
+
);
|
|
272
|
+
}
|
|
273
|
+
}
|
|
274
|
+
}
|
|
275
|
+
} catch (err: any) {
|
|
276
|
+
console.error(`❌ Error processing model ${model.name}: ${err.message}`);
|
|
277
|
+
}
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
await pool.end();
|
|
281
|
+
console.log('🎉 Database configuration complete');
|
|
282
|
+
} catch (err) {
|
|
283
|
+
console.error('❌ Error configuring database:', err);
|
|
284
|
+
console.log('⚠️ Continuing with hook generation anyway...');
|
|
285
|
+
}
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
/**
|
|
289
|
+
* Main execution function
|
|
290
|
+
*/
|
|
291
|
+
async function main() {
|
|
292
|
+
try {
|
|
293
|
+
console.log('🚀 Starting Suparisma hook generation...');
|
|
294
|
+
|
|
295
|
+
// Check for required environment variables first
|
|
296
|
+
checkEnvironmentVariables();
|
|
297
|
+
|
|
298
|
+
console.log(`Prisma schema path: ${PRISMA_SCHEMA_PATH}`);
|
|
299
|
+
console.log(`Output directory: ${OUTPUT_DIR}`);
|
|
300
|
+
|
|
301
|
+
// Ensure output directories exist
|
|
302
|
+
[OUTPUT_DIR, TYPES_DIR, HOOKS_DIR, UTILS_DIR].forEach(dir => {
|
|
303
|
+
if (!fs.existsSync(dir)) {
|
|
304
|
+
fs.mkdirSync(dir, { recursive: true });
|
|
305
|
+
}
|
|
306
|
+
});
|
|
307
|
+
|
|
308
|
+
// Generate Supabase client file first
|
|
309
|
+
generateSupabaseClientFile();
|
|
310
|
+
|
|
311
|
+
// First, generate the core hook factory
|
|
312
|
+
generateCoreFile();
|
|
313
|
+
|
|
314
|
+
// Parse models from Prisma schema
|
|
315
|
+
const models = parsePrismaSchema(PRISMA_SCHEMA_PATH);
|
|
316
|
+
|
|
317
|
+
// Configure database tables for real-time and search functionality
|
|
318
|
+
await configurePrismaTablesForSuparisma(PRISMA_SCHEMA_PATH);
|
|
319
|
+
|
|
320
|
+
// Generate type definitions and hooks for each model
|
|
321
|
+
const modelInfos: ProcessedModelInfo[] = [];
|
|
322
|
+
for (const model of models) {
|
|
323
|
+
const modelInfo = generateModelTypesFile(model);
|
|
324
|
+
generateModelHookFile(modelInfo);
|
|
325
|
+
modelInfos.push(modelInfo);
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
// Generate the main module file
|
|
329
|
+
generateMainIndexFile(modelInfos);
|
|
330
|
+
|
|
331
|
+
console.log(`✅ Successfully generated all suparisma hooks and types in "${OUTPUT_DIR}"!`);
|
|
332
|
+
} catch (error) {
|
|
333
|
+
console.error('❌ Error generating hooks:', error);
|
|
334
|
+
process.exit(1);
|
|
335
|
+
}
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
// Execute main function
|
|
339
|
+
main();
|
package/src/parser.ts
ADDED
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
import fs from 'fs';
|
|
2
|
+
import { ModelInfo, FieldInfo, SearchFieldInfo } from './types';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Parse Prisma schema to extract model information including search annotations
|
|
6
|
+
*/
|
|
7
|
+
export function parsePrismaSchema(schemaPath: string): ModelInfo[] {
|
|
8
|
+
const schema = fs.readFileSync(schemaPath, 'utf-8');
|
|
9
|
+
const modelRegex = /model\s+(\w+)\s+{([^}]*)}/gs;
|
|
10
|
+
const models: ModelInfo[] = [];
|
|
11
|
+
|
|
12
|
+
let match;
|
|
13
|
+
while ((match = modelRegex.exec(schema)) !== null) {
|
|
14
|
+
const modelName = match[1] || '';
|
|
15
|
+
const modelBody = match[2] || '';
|
|
16
|
+
|
|
17
|
+
// Extract custom table name if provided with @@map
|
|
18
|
+
const mapMatch = modelBody.match(/@@map\("([^"]+)"\)/);
|
|
19
|
+
const mappedName = mapMatch ? mapMatch[1] : modelName;
|
|
20
|
+
|
|
21
|
+
// Extract field info
|
|
22
|
+
const fields: FieldInfo[] = [];
|
|
23
|
+
// Track fields with @enableSearch annotation
|
|
24
|
+
const searchFields: SearchFieldInfo[] = [];
|
|
25
|
+
|
|
26
|
+
const lines = modelBody.split('\n');
|
|
27
|
+
let lastFieldName = '';
|
|
28
|
+
let lastFieldType = '';
|
|
29
|
+
|
|
30
|
+
for (let i = 0; i < lines.length; i++) {
|
|
31
|
+
const line = lines[i]?.trim();
|
|
32
|
+
|
|
33
|
+
// Skip blank lines and non-field lines
|
|
34
|
+
if (!line || line.startsWith('@@')) {
|
|
35
|
+
continue;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
// Check for standalone @enableSearch comment
|
|
39
|
+
if (line === '// @enableSearch' && lastFieldName) {
|
|
40
|
+
searchFields.push({
|
|
41
|
+
name: lastFieldName,
|
|
42
|
+
type: lastFieldType,
|
|
43
|
+
});
|
|
44
|
+
continue;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
// Check if line is a comment
|
|
48
|
+
if (line.startsWith('//')) {
|
|
49
|
+
continue;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
// Parse field definition
|
|
53
|
+
const fieldMatch = line.match(/\s*(\w+)\s+(\w+)(\?)?\s*(?:@[^)]+)?/);
|
|
54
|
+
if (fieldMatch) {
|
|
55
|
+
const fieldName = fieldMatch[1];
|
|
56
|
+
const fieldType = fieldMatch[2];
|
|
57
|
+
const isOptional = !!fieldMatch[3]; // ? makes it optional
|
|
58
|
+
|
|
59
|
+
// Store for potential standalone @enableSearch comment
|
|
60
|
+
lastFieldName = fieldName || '';
|
|
61
|
+
lastFieldType = fieldType || '';
|
|
62
|
+
|
|
63
|
+
// Detect special fields
|
|
64
|
+
const isId = line.includes('@id');
|
|
65
|
+
const isCreatedAt = fieldName === 'created_at' || fieldName === 'createdAt';
|
|
66
|
+
const isUpdatedAt = fieldName === 'updated_at' || fieldName === 'updatedAt';
|
|
67
|
+
const hasDefaultValue = line.includes('@default');
|
|
68
|
+
|
|
69
|
+
// Extract default value if present
|
|
70
|
+
let defaultValue;
|
|
71
|
+
if (hasDefaultValue) {
|
|
72
|
+
const defaultMatch = line.match(/@default\(\s*(.+?)\s*\)/);
|
|
73
|
+
if (defaultMatch) {
|
|
74
|
+
defaultValue = defaultMatch[1];
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
const isRelation =
|
|
79
|
+
line.includes('@relation') ||
|
|
80
|
+
(!!fieldName &&
|
|
81
|
+
(fieldName.endsWith('_id') || fieldName === 'userId' || fieldName === 'user_id'));
|
|
82
|
+
|
|
83
|
+
// Check for inline @enableSearch comment
|
|
84
|
+
if (line.includes('// @enableSearch')) {
|
|
85
|
+
searchFields.push({
|
|
86
|
+
name: fieldName || '',
|
|
87
|
+
type: fieldType || '',
|
|
88
|
+
});
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
if (fieldName && fieldType) {
|
|
92
|
+
fields.push({
|
|
93
|
+
name: fieldName,
|
|
94
|
+
type: fieldType,
|
|
95
|
+
isRequired: false,
|
|
96
|
+
isOptional,
|
|
97
|
+
isId,
|
|
98
|
+
isUnique: false,
|
|
99
|
+
isUpdatedAt,
|
|
100
|
+
isCreatedAt,
|
|
101
|
+
hasDefaultValue,
|
|
102
|
+
defaultValue, // Add the extracted default value
|
|
103
|
+
isRelation,
|
|
104
|
+
});
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
// Check for model-level @enableSearch before the model definition
|
|
110
|
+
if (schema.includes(`// @enableSearch\nmodel ${modelName}`)) {
|
|
111
|
+
// Add all string fields as searchable
|
|
112
|
+
fields.forEach((field) => {
|
|
113
|
+
if (
|
|
114
|
+
field.type.toLowerCase() === 'string' &&
|
|
115
|
+
!searchFields.some((sf) => sf.name === field.name)
|
|
116
|
+
) {
|
|
117
|
+
searchFields.push({
|
|
118
|
+
name: field.name,
|
|
119
|
+
type: field.type,
|
|
120
|
+
});
|
|
121
|
+
}
|
|
122
|
+
});
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
models.push({
|
|
126
|
+
name: modelName,
|
|
127
|
+
mappedName: mappedName || '',
|
|
128
|
+
fields,
|
|
129
|
+
searchFields: searchFields.length > 0 ? searchFields : undefined,
|
|
130
|
+
});
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
return models;
|
|
134
|
+
}
|