suparisma 0.0.2 โ†’ 0.0.4

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.
@@ -6,7 +6,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
6
6
  exports.generateSupabaseClientFile = generateSupabaseClientFile;
7
7
  const fs_1 = __importDefault(require("fs"));
8
8
  const path_1 = __importDefault(require("path"));
9
- const config_1 = require("../config"); // Assuming OUTPUT_DIR is defined in config.ts
9
+ const config_1 = require("../config"); // Ensure this is UTILS_DIR
10
10
  function generateSupabaseClientFile() {
11
11
  const supabaseClientContent = `// THIS FILE IS AUTO-GENERATED - DO NOT EDIT DIRECTLY
12
12
 
@@ -19,10 +19,10 @@ export const supabase = createClient(
19
19
  process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY!
20
20
  );
21
21
  `;
22
- const outputPath = path_1.default.join(config_1.OUTPUT_DIR, 'supabase-client-generated.ts');
23
- // Ensure the output directory exists
24
- if (!fs_1.default.existsSync(config_1.OUTPUT_DIR)) {
25
- fs_1.default.mkdirSync(config_1.OUTPUT_DIR, { recursive: true });
22
+ // Output to the UTILS_DIR
23
+ const outputPath = path_1.default.join(config_1.UTILS_DIR, 'supabase-client.ts');
24
+ if (!fs_1.default.existsSync(config_1.UTILS_DIR)) {
25
+ fs_1.default.mkdirSync(config_1.UTILS_DIR, { recursive: true });
26
26
  }
27
27
  fs_1.default.writeFileSync(outputPath, supabaseClientContent);
28
28
  console.log(`๐Ÿš€ Generated Supabase client file at: ${outputPath}`);
@@ -38,6 +38,9 @@ function generateModelTypesFile(model) {
38
38
  });
39
39
  // Extract searchable fields from annotations
40
40
  const searchFields = model.searchFields?.map((field) => field.name) || [];
41
+ // Find the actual field names for createdAt and updatedAt
42
+ const createdAtField = model.fields.find(field => field.isCreatedAt)?.name || 'createdAt';
43
+ const updatedAtField = model.fields.find(field => field.isUpdatedAt)?.name || 'updatedAt';
41
44
  // Create a manual property list for WithRelations interface
42
45
  const withRelationsProps = model.fields
43
46
  .filter((field) => !relationObjectFields.includes(field.name) && !foreignKeyFields.includes(field.name))
@@ -90,7 +93,7 @@ function generateModelTypesFile(model) {
90
93
  // Edit the generator script instead
91
94
 
92
95
  import type { ${modelName} } from '@prisma/client';
93
- import type { ModelResult, SuparismaOptions, SearchQuery, SearchState } from './core';
96
+ import type { ModelResult, SuparismaOptions, SearchQuery, SearchState } from '../utils/core';
94
97
 
95
98
  /**
96
99
  * Extended ${modelName} type that includes relation fields.
@@ -288,6 +291,18 @@ export interface ${modelName}HookApi {
288
291
  */
289
292
  loading: boolean;
290
293
 
294
+ /**
295
+ * The current count of records matching the filter criteria.
296
+ * This automatically updates with the data, including realtime updates.
297
+ * It always reflects the current length of the data array, respecting any filters.
298
+ *
299
+ * @example
300
+ * // Display the count in the UI
301
+ * const { count } = ${modelName.toLowerCase()};
302
+ * return <div>Total records: {count}</div>;
303
+ */
304
+ count: number;
305
+
291
306
  ${searchFields.length > 0
292
307
  ? `/**
293
308
  * Search functionality for ${modelName} records.
@@ -507,26 +522,6 @@ ${createInputProps
507
522
  create: ${modelName}CreateInput;
508
523
  }) => ${modelName}SingleResult;
509
524
 
510
- /**
511
- * Count the number of ${modelName} records matching the filter criteria.
512
- *
513
- * @param params - Optional filter parameters
514
- * @returns A promise with the count of matching records
515
- *
516
- * @example
517
- * // Count all ${modelName.toLowerCase()} records
518
- * const count = await ${modelName.toLowerCase()}.count();
519
- *
520
- * @example
521
- * // Count active ${modelName.toLowerCase()} records
522
- * const activeCount = await ${modelName.toLowerCase()}.count({
523
- * where: { active: true }
524
- * });
525
- */
526
- count: (params?: {
527
- where?: ${modelName}WhereInput;
528
- }) => Promise<number>;
529
-
530
525
  /**
531
526
  * Manually refresh the data with current filter settings.
532
527
  * Useful after external operations or when realtime is disabled.
@@ -552,7 +547,10 @@ ${createInputProps
552
547
  skip?: number;
553
548
  }) => ${modelName}ManyResult;
554
549
  }`;
555
- const outputPath = path_1.default.join(config_1.OUTPUT_DIR, `${modelName}Types.ts`);
550
+ const outputPath = path_1.default.join(config_1.TYPES_DIR, `${modelName}Types.ts`);
551
+ if (!fs_1.default.existsSync(config_1.TYPES_DIR)) {
552
+ fs_1.default.mkdirSync(config_1.TYPES_DIR, { recursive: true });
553
+ }
556
554
  fs_1.default.writeFileSync(outputPath, typeContent);
557
555
  console.log(`Generated type definitions for ${modelName} at ${outputPath}`);
558
556
  return {
@@ -562,5 +560,7 @@ ${createInputProps
562
560
  hasUpdatedAt: model.fields.some((field) => field.isUpdatedAt),
563
561
  searchFields,
564
562
  defaultValues: Object.keys(defaultValues).length > 0 ? defaultValues : undefined,
563
+ createdAtField,
564
+ updatedAtField
565
565
  };
566
566
  }
package/dist/index.js CHANGED
@@ -39,12 +39,59 @@ const typeGenerator_1 = require("./generators/typeGenerator");
39
39
  const hookGenerator_1 = require("./generators/hookGenerator");
40
40
  const indexGenerator_1 = require("./generators/indexGenerator");
41
41
  const supabaseClientGenerator_1 = require("./generators/supabaseClientGenerator");
42
+ /**
43
+ * Prints the help message showing available commands
44
+ */
45
+ function printHelp() {
46
+ console.log(`
47
+ Suparisma - Typesafe React realtime CRUD hooks generator for Supabase, powered by Prisma.
48
+
49
+ Usage:
50
+ npx suparisma <command>
51
+
52
+ Commands:
53
+ generate Generate hooks based on your Prisma schema (runs in current directory)
54
+ help Show this help message
55
+
56
+ Example:
57
+ npx suparisma generate
58
+ `);
59
+ }
60
+ /**
61
+ * Checks for essential environment variables and throws an error if any are missing.
62
+ */
63
+ function checkEnvironmentVariables() {
64
+ const requiredEnvVars = [
65
+ 'DATABASE_URL',
66
+ 'NEXT_PUBLIC_SUPABASE_URL',
67
+ 'NEXT_PUBLIC_SUPABASE_ANON_KEY',
68
+ ];
69
+ const missingVars = requiredEnvVars.filter(varName => !process.env[varName]);
70
+ if (missingVars.length > 0) {
71
+ let errorMessage = 'Error: Missing required environment variables:\n';
72
+ missingVars.forEach(varName => {
73
+ errorMessage += `- ${varName}: This variable is essential for the generator to function correctly. `;
74
+ if (varName === 'DATABASE_URL') {
75
+ 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';
76
+ }
77
+ else if (varName === 'NEXT_PUBLIC_SUPABASE_URL') {
78
+ 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';
79
+ }
80
+ else if (varName === 'NEXT_PUBLIC_SUPABASE_ANON_KEY') {
81
+ 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';
82
+ }
83
+ });
84
+ errorMessage += '\nPlease add these variables to your .env file or ensure they are available in your environment and try again.';
85
+ throw new Error(errorMessage);
86
+ }
87
+ console.log('โœ… All required environment variables are set.');
88
+ }
42
89
  function analyzePrismaSchema(schemaPath) {
43
90
  try {
44
91
  const schemaContent = fs_1.default.readFileSync(schemaPath, 'utf8');
45
92
  const modelInfos = [];
46
93
  // Regular expression to match model definitions with comments
47
- const modelRegex = /(?:\/\/\s*@enableRealtime\s*)?\s*model\s+(\w+)\s*{([\s\S]*?)}/g;
94
+ const modelRegex = /(?:\/\/\s*@disableRealtime\s*)?\s*model\s+(\w+)\s*{([\s\S]*?)}/g;
48
95
  let modelMatch;
49
96
  while ((modelMatch = modelRegex.exec(schemaContent)) !== null) {
50
97
  const modelName = modelMatch[1];
@@ -56,8 +103,9 @@ function analyzePrismaSchema(schemaPath) {
56
103
  const tableName = modelMatch[0].includes('@map')
57
104
  ? modelMatch[0].match(/@map\s*\(\s*["'](.+?)["']\s*\)/)?.at(1) || modelName
58
105
  : modelName;
59
- // Check if model has @enableRealtime comment
60
- const enableRealtime = modelMatch[0].includes('// @enableRealtime');
106
+ // Check if model has @disableRealtime comment
107
+ // Default is to enable realtime unless explicitly disabled
108
+ const enableRealtime = !modelMatch[0].includes('// @disableRealtime');
61
109
  // Find fields with @enableSearch comment
62
110
  const searchFields = [];
63
111
  const fieldRegex = /(\w+)\s+(\w+)(?:\?.+?)?\s+(?:@.+?)?\s*(?:\/\/\s*@enableSearch)?/g;
@@ -86,7 +134,7 @@ function analyzePrismaSchema(schemaPath) {
86
134
  }
87
135
  /**
88
136
  * Configure database tables for proper realtime functionality and search
89
- * 1. Sets REPLICA IDENTITY FULL and enables realtime for models with @enableRealtime
137
+ * 1. Sets REPLICA IDENTITY FULL and enables realtime for all models (unless they have @disableRealtime)
90
138
  * 2. Creates search functions for fields with @enableSearch
91
139
  */
92
140
  async function configurePrismaTablesForSuparisma(schemaPath) {
@@ -98,8 +146,7 @@ async function configurePrismaTablesForSuparisma(schemaPath) {
98
146
  // Get direct PostgreSQL connection URL
99
147
  const directUrl = process.env.DIRECT_URL;
100
148
  if (!directUrl) {
101
- console.warn('โš ๏ธ DIRECT_URL environment variable not found. Skipping database configuration.');
102
- return;
149
+ throw new Error('โŒ Error: DIRECT_URL environment variable not found. This is required for database configuration (e.g., setting up realtime). Please define it in your .env file or as an environment variable and try again. This should be a direct PostgreSQL connection string.\n');
103
150
  }
104
151
  // Analyze Prisma schema for models, realtime and search annotations
105
152
  const modelInfos = analyzePrismaSchema(schemaPath);
@@ -129,6 +176,7 @@ async function configurePrismaTablesForSuparisma(schemaPath) {
129
176
  // Use the exact case of the table as it exists in the database
130
177
  const actualTableName = matchingTable.table_name;
131
178
  console.log(`๐Ÿ” Model ${model.name} -> Actual table: ${actualTableName}`);
179
+ console.log(`โ„น๏ธ Model ${model.name}: enableRealtime is ${model.enableRealtime}`);
132
180
  if (model.enableRealtime) {
133
181
  // Explicitly use double quotes for mixed case identifiers
134
182
  // try {
@@ -138,10 +186,10 @@ async function configurePrismaTablesForSuparisma(schemaPath) {
138
186
  // console.error(`โŒ Failed to set REPLICA IDENTITY on "${actualTableName}": ${err.message}`);
139
187
  // }
140
188
  // Directly add the table to Supabase Realtime publication
189
+ const alterPublicationQuery = `ALTER PUBLICATION supabase_realtime ADD TABLE "${actualTableName}";`;
190
+ console.log(`โ„น๏ธ Executing SQL: ${alterPublicationQuery}`);
141
191
  try {
142
- await pool.query(`
143
- ALTER PUBLICATION supabase_realtime ADD TABLE "${actualTableName}";
144
- `);
192
+ await pool.query(alterPublicationQuery);
145
193
  console.log(`โœ… Added "${actualTableName}" to supabase_realtime publication`);
146
194
  }
147
195
  catch (err) {
@@ -150,7 +198,7 @@ async function configurePrismaTablesForSuparisma(schemaPath) {
150
198
  console.log(`โ„น๏ธ Table "${actualTableName}" was already in supabase_realtime publication`);
151
199
  }
152
200
  else {
153
- console.error(`โŒ Failed to add "${actualTableName}" to supabase_realtime: ${err.message}`);
201
+ console.error(`โŒ Failed to add "${actualTableName}" to supabase_realtime. Full error:`, err);
154
202
  }
155
203
  }
156
204
  }
@@ -222,38 +270,68 @@ async function configurePrismaTablesForSuparisma(schemaPath) {
222
270
  }
223
271
  }
224
272
  /**
225
- * Main execution function
273
+ * Main execution function for hook generation
226
274
  */
227
- async function main() {
275
+ async function generateHooks() {
228
276
  try {
229
- console.log('Generating Supabase realtime hooks with improved architecture...');
230
- // Ensure output directory exists
231
- if (!fs_1.default.existsSync(config_1.OUTPUT_DIR)) {
232
- fs_1.default.mkdirSync(config_1.OUTPUT_DIR, { recursive: true });
277
+ console.log('๐Ÿš€ Starting Suparisma hook generation...');
278
+ checkEnvironmentVariables();
279
+ console.log(`Prisma schema path: ${config_1.PRISMA_SCHEMA_PATH}`);
280
+ console.log(`Output directory: ${config_1.OUTPUT_DIR}`);
281
+ // Delete the entire output directory if it exists to clean up any stale files
282
+ if (fs_1.default.existsSync(config_1.OUTPUT_DIR)) {
283
+ console.log(`๐Ÿงน Cleaning up previous generated files in ${config_1.OUTPUT_DIR}...`);
284
+ fs_1.default.rmSync(config_1.OUTPUT_DIR, { recursive: true, force: true });
285
+ console.log(`โœ… Removed previous generated directory`);
233
286
  }
234
- // Generate Supabase client file first
287
+ // Ensure all specific output directories exist, OUTPUT_DIR is the root and will be created if needed by sub-creations.
288
+ const dirsToEnsure = [config_1.TYPES_DIR, config_1.HOOKS_DIR, config_1.UTILS_DIR];
289
+ dirsToEnsure.forEach(dir => {
290
+ if (!fs_1.default.existsSync(dir)) {
291
+ fs_1.default.mkdirSync(dir, { recursive: true });
292
+ console.log(`Created directory: ${dir}`);
293
+ }
294
+ });
295
+ // Generate Supabase client file (goes to UTILS_DIR)
235
296
  (0, supabaseClientGenerator_1.generateSupabaseClientFile)();
236
- // First, generate the core hook factory
297
+ // Generate the core hook factory (goes to UTILS_DIR)
237
298
  (0, coreGenerator_1.generateCoreFile)();
238
- // Parse models from Prisma schema
239
299
  const models = (0, parser_1.parsePrismaSchema)(config_1.PRISMA_SCHEMA_PATH);
240
- // Configure database tables for real-time and search functionality
241
300
  await configurePrismaTablesForSuparisma(config_1.PRISMA_SCHEMA_PATH);
242
- // Generate type definitions and hooks for each model
243
301
  const modelInfos = [];
244
302
  for (const model of models) {
245
303
  const modelInfo = (0, typeGenerator_1.generateModelTypesFile)(model);
246
304
  (0, hookGenerator_1.generateModelHookFile)(modelInfo);
247
305
  modelInfos.push(modelInfo);
248
306
  }
249
- // Generate the main module file
250
307
  (0, indexGenerator_1.generateMainIndexFile)(modelInfos);
251
- console.log('โœ… Successfully generated all suparisma hooks!');
308
+ console.log(`โœ… Successfully generated all suparisma hooks and types in "${config_1.OUTPUT_DIR}"!`);
252
309
  }
253
310
  catch (error) {
254
311
  console.error('โŒ Error generating hooks:', error);
255
312
  process.exit(1);
256
313
  }
257
314
  }
258
- // Execute main function
259
- main();
315
+ /**
316
+ * Main CLI entry point
317
+ */
318
+ function run() {
319
+ const args = process.argv.slice(2);
320
+ const command = args[0];
321
+ switch (command) {
322
+ case 'generate':
323
+ generateHooks();
324
+ break;
325
+ case 'help':
326
+ case '--help':
327
+ case '-h':
328
+ printHelp();
329
+ break;
330
+ default:
331
+ console.log(`Unknown command: ${command}`);
332
+ printHelp();
333
+ process.exit(1);
334
+ }
335
+ }
336
+ // Execute the CLI command
337
+ run();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "suparisma",
3
- "version": "0.0.2",
3
+ "version": "0.0.4",
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
  "scripts": {
@@ -8,8 +8,21 @@
8
8
  "start": "node dist/index.js",
9
9
  "test": "jest",
10
10
  "generate-prisma-types": "prisma generate",
11
- "generate-hooks": "ts-node --transpile-only src/index.ts"
11
+ "generate-hooks-dev": "npx prisma generate && npx prisma db push && ts-node --transpile-only src/index.ts generate",
12
+ "run-next-example": "cd src/examples/with-next && npm run dev"
12
13
  },
14
+ "bin": {
15
+ "suparisma": "dist/index.js"
16
+ },
17
+ "keywords": [
18
+ "react",
19
+ "hooks",
20
+ "supabase",
21
+ "prisma",
22
+ "typescript",
23
+ "nextjs",
24
+ "realtime"
25
+ ],
13
26
  "dependencies": {
14
27
  "@supabase/supabase-js": "^2.49.4",
15
28
  "dotenv": "^16.5.0",
@@ -1,4 +1,4 @@
1
- // This is your Prisma schema file,
1
+ // This is a sample Prisma schema file,
2
2
  // learn more about it in the docs: https://pris.ly/d/prisma-schema
3
3
 
4
4
  // Looking for ways to speed up your queries, or scale easily with your serverless or edge functions?
@@ -13,10 +13,26 @@ datasource db {
13
13
  url = env("DATABASE_URL")
14
14
  }
15
15
 
16
- model User {
16
+ enum SomeEnum {
17
+ ONE
18
+ TWO
19
+ THREE
20
+ }
21
+
22
+ // Realtime is enabled by default for all models
23
+ model Thing {
17
24
  id String @id @default(uuid())
18
- email String @unique
19
25
  name String?
26
+ someEnum SomeEnum @default(ONE)
27
+ someNumber Int?
20
28
  createdAt DateTime @default(now())
21
29
  updatedAt DateTime @updatedAt
22
30
  }
31
+
32
+ // @disableRealtime
33
+ model AuditLog {
34
+ id String @id @default(uuid())
35
+ action String
36
+ details String?
37
+ createdAt DateTime @default(now())
38
+ }
package/tsconfig.json CHANGED
@@ -16,5 +16,5 @@
16
16
  "jsx": "preserve"
17
17
  },
18
18
  "include": ["src/**/*", "../src/app/**/*.ts"],
19
- "exclude": ["node_modules", "**/*.spec.ts"]
19
+ "exclude": ["node_modules", "**/*.spec.ts", "src/examples/**/*"]
20
20
  }
package/src/config.ts DELETED
@@ -1,7 +0,0 @@
1
- // Configuration
2
- export const PRISMA_SCHEMA_PATH = process.env.SUPARISMA_PRISMA_SCHEMA_PATH || 'prisma/schema.prisma';
3
- export const OUTPUT_DIR = process.env.SUPARISMA_OUTPUT_DIR || 'src/suparisma/generated';
4
- export const TYPES_DIR = `${OUTPUT_DIR}/types`;
5
- export const HOOKS_DIR = `${OUTPUT_DIR}/hooks`;
6
- export const UTILS_DIR = `${OUTPUT_DIR}/utils`;
7
- export const HOOK_NAME_PREFIX = 'useSuparisma';