suparisma 0.0.1 → 0.0.3

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.
Files changed (38) hide show
  1. package/README.md +156 -3
  2. package/dist/config.js +12 -3
  3. package/dist/generators/coreGenerator.js +122 -40
  4. package/dist/generators/hookGenerator.js +16 -7
  5. package/dist/generators/indexGenerator.js +12 -7
  6. package/dist/generators/supabaseClientGenerator.js +5 -5
  7. package/dist/generators/typeGenerator.js +22 -22
  8. package/dist/index.js +103 -25
  9. package/{src/suparisma/generated/useSuparismaUser.ts → dist/suparisma/generated/hooks/useSuparismaUser.js} +19 -35
  10. package/dist/suparisma/generated/index.js +33 -0
  11. package/dist/suparisma/generated/types/UserTypes.js +4 -0
  12. package/dist/suparisma/generated/utils/core.js +1090 -0
  13. package/dist/suparisma/generated/utils/supabase-client.js +8 -0
  14. package/package.json +12 -2
  15. package/prisma/schema.prisma +19 -3
  16. package/tsconfig.json +1 -1
  17. package/src/config.ts +0 -7
  18. package/src/generated/hooks/useSuparismaUser.ts +0 -77
  19. package/src/generated/index.ts +0 -50
  20. package/src/generated/types/UserTypes.ts +0 -400
  21. package/src/generated/utils/core.ts +0 -1413
  22. package/src/generated/utils/supabase-client.ts +0 -7
  23. package/src/generators/coreGenerator.ts +0 -1426
  24. package/src/generators/hookGenerator.ts +0 -110
  25. package/src/generators/indexGenerator.ts +0 -117
  26. package/src/generators/supabaseClientGenerator.ts +0 -24
  27. package/src/generators/typeGenerator.ts +0 -587
  28. package/src/index.ts +0 -339
  29. package/src/parser.ts +0 -134
  30. package/src/suparisma/generated/UserTypes.ts +0 -400
  31. package/src/suparisma/generated/core.ts +0 -1413
  32. package/src/suparisma/generated/hooks/useSuparismaUser.ts +0 -77
  33. package/src/suparisma/generated/index.ts +0 -50
  34. package/src/suparisma/generated/supabase-client-generated.ts +0 -9
  35. package/src/suparisma/generated/types/UserTypes.ts +0 -400
  36. package/src/suparisma/generated/utils/core.ts +0 -1413
  37. package/src/suparisma/generated/utils/supabase-client.ts +0 -7
  38. package/src/types.ts +0 -57
package/src/index.ts DELETED
@@ -1,339 +0,0 @@
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 DELETED
@@ -1,134 +0,0 @@
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
- }