suparisma 0.0.2 → 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.
package/README.md CHANGED
@@ -1,6 +1,157 @@
1
- DO NOT INSTALL STILL UNDER CONSTRUCTION.
1
+ # Suparisma
2
2
 
3
- This package combines supabase and prisma to easily generate realtime CRUD hooks for all your tables to be used seamlessly in react.
3
+ A React hook generator for Supabase that is driven by your Prisma schema, giving you type-safe, real-time enabled hooks to interact with your Supabase database.
4
4
 
5
- To install first do:
6
- pnpm install suparisma
5
+ ## Why?
6
+ CRUD typesafetey with Supabase should be easy, currently it is not with a lot of issues that can rise easily espeically if you're not using Prisma.
7
+
8
+ Prisma solved supabase typesafely issue on the server, and TRPC helped making the client even more type safe but realtime capabilites from the DB to the browser was still lacking and that lead to a lot of unnecessary GET, POST requests if you just simply need to have realtime support.
9
+
10
+ Supabase's rules are also powerful and if you're using TRPC or any server solution you're easily missing out on them.
11
+
12
+ This package solves all this focusing, it lets you:
13
+ - Create typesafe CRUD hooks for all your supabase tables.
14
+ - Enables you to easily paginate, search and query in each table.
15
+ - Uses Prisma and Supabase official SDKs.
16
+ - Respects Supabase's auth rules enabling an easy way to secure your DB.
17
+ - Works with any React env. like NextJS/Remix/Tanstack Start/Router/etc..
18
+
19
+ ## Features
20
+
21
+ - 🚀 **Auto-generated React hooks** based on your Prisma schema
22
+ - 🔄 **Real-time updates by default** for all tables (opt-out available)
23
+ - 🔒 **Type-safe interfaces** for all database operations
24
+ - 🔍 **Full-text search** capabilities with optional annotations
25
+ - 🔄 **Prisma-like API** that feels familiar if you use Prisma
26
+
27
+ ## Installation
28
+
29
+ ```bash
30
+ npm install suparisma
31
+ # or
32
+ yarn add suparisma
33
+ # or
34
+ pnpm install suparisma
35
+ ```
36
+
37
+ ## Quick Start
38
+
39
+ 1. **Add a Prisma schema**: Make sure you have a valid `prisma/schema.prisma` file in your project.
40
+
41
+ 2. **Set up required environment variables** in a `.env` file:
42
+
43
+ ```
44
+ # Required for Prisma and Supabase
45
+ DATABASE_URL="postgresql://user:password@host:port/database"
46
+ DIRECT_URL="postgresql://user:password@host:port/database" # Direct Postgres connection
47
+ NEXT_PUBLIC_SUPABASE_URL="https://your-project.supabase.co"
48
+ NEXT_PUBLIC_SUPABASE_ANON_KEY="your-anon-key"
49
+ ```
50
+
51
+ 3. **Generate hooks** with a single command:
52
+
53
+ ```bash
54
+ npx suparisma generate
55
+ ```
56
+
57
+ This will:
58
+ - Read your Prisma schema from the current directory
59
+ - Configure your database for realtime functionality and search
60
+ - Generate type-safe React hooks in `src/suparisma/generated` (configurable)
61
+
62
+ 4. **Use the hooks** in your React components:
63
+
64
+ ```tsx
65
+ import useSuparisma from './src/suparisma/generated';
66
+
67
+ function UserList() {
68
+ const users = useSuparisma.user();
69
+
70
+ if (users.loading) return <div>Loading...</div>;
71
+ if (users.error) return <div>Error: {users.error.message}</div>;
72
+
73
+ return (
74
+ <div>
75
+ <h1>Users</h1>
76
+ <ul>
77
+ {users.data?.map(user => (
78
+ <li key={user.id}>{user.name}</li>
79
+ ))}
80
+ </ul>
81
+
82
+ <button onClick={() => users.create({ name: "New User" })}>
83
+ Add User
84
+ </button>
85
+ </div>
86
+ );
87
+ }
88
+ ```
89
+
90
+ ## Annotations in Your Prisma Schema
91
+
92
+ Add annotations directly in your Prisma schema as comments:
93
+
94
+ ```prisma
95
+ // Realtime is enabled by default for this model
96
+ model User {
97
+ id String @id @default(uuid())
98
+ email String @unique
99
+ name String? // @enableSearch
100
+ createdAt DateTime @default(now())
101
+ updatedAt DateTime @updatedAt
102
+ }
103
+
104
+ // @disableRealtime - Opt out of realtime for this model
105
+ model AuditLog {
106
+ id String @id @default(uuid())
107
+ action String
108
+ details String?
109
+ createdAt DateTime @default(now())
110
+ }
111
+ ```
112
+
113
+ ## Stale Models Cleanup
114
+
115
+ When you delete a model from your Prisma schema and run the generation command, Suparisma automatically:
116
+ - Detects changes to your schema
117
+ - Deletes the entire generated directory
118
+ - Regenerates all hooks and types based on your current schema
119
+
120
+ This ensures you don't have stale files lingering around for models that no longer exist in your schema.
121
+
122
+ ## CLI Commands
123
+
124
+ Suparisma provides a simple CLI with the following commands:
125
+
126
+ ```bash
127
+ # Generate hooks based on your Prisma schema
128
+ npx suparisma generate
129
+
130
+ # Show help information
131
+ npx suparisma help
132
+ ```
133
+
134
+ ## Configuration
135
+
136
+ You can customize the behavior using environment variables:
137
+
138
+ ```
139
+ # Optional: Customize output directory
140
+ SUPARISMA_OUTPUT_DIR="src/hooks/generated"
141
+
142
+ # Optional: Specify custom schema path
143
+ SUPARISMA_PRISMA_SCHEMA_PATH="path/to/schema.prisma"
144
+ ```
145
+
146
+ ## Environment Variables
147
+
148
+ The following environment variables are required:
149
+
150
+ - `DATABASE_URL` - Your Postgres database URL (used by Prisma)
151
+ - `DIRECT_URL` - Direct URL to your Postgres database (for setting up realtime)
152
+ - `NEXT_PUBLIC_SUPABASE_URL` - Your Supabase project URL
153
+ - `NEXT_PUBLIC_SUPABASE_ANON_KEY` - Your Supabase anonymous key
154
+
155
+ ## License
156
+
157
+ MIT
package/dist/config.js CHANGED
@@ -1,7 +1,16 @@
1
1
  "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
2
5
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.HOOK_NAME_PREFIX = exports.OUTPUT_DIR = exports.PRISMA_SCHEMA_PATH = void 0;
6
+ exports.HOOK_NAME_PREFIX = exports.UTILS_DIR = exports.HOOKS_DIR = exports.TYPES_DIR = exports.OUTPUT_DIR = exports.PRISMA_SCHEMA_PATH = void 0;
4
7
  // Configuration
5
- exports.PRISMA_SCHEMA_PATH = 'prisma/schema.prisma';
6
- exports.OUTPUT_DIR = 'src/hooks/generated';
8
+ const path_1 = __importDefault(require("path"));
9
+ // Use current working directory for all paths
10
+ const CWD = process.cwd();
11
+ exports.PRISMA_SCHEMA_PATH = process.env.SUPARISMA_PRISMA_SCHEMA_PATH || path_1.default.join(CWD, 'prisma/schema.prisma');
12
+ exports.OUTPUT_DIR = process.env.SUPARISMA_OUTPUT_DIR || path_1.default.join(CWD, 'src/suparisma/generated');
13
+ exports.TYPES_DIR = `${exports.OUTPUT_DIR}/types`;
14
+ exports.HOOKS_DIR = `${exports.OUTPUT_DIR}/hooks`;
15
+ exports.UTILS_DIR = `${exports.OUTPUT_DIR}/utils`;
7
16
  exports.HOOK_NAME_PREFIX = 'useSuparisma';
@@ -0,0 +1,7 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.supabase = void 0;
4
+ const supabase_js_1 = require("@supabase/supabase-js");
5
+ console.log(`NEXT_PUBLIC_SUPABASE_URL: ${process.env.NEXT_PUBLIC_SUPABASE_URL}`);
6
+ console.log(`NEXT_PUBLIC_SUPABASE_ANON_KEY: ${process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY}`);
7
+ exports.supabase = (0, supabase_js_1.createClient)(process.env.NEXT_PUBLIC_SUPABASE_URL, process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY);
@@ -15,7 +15,8 @@ function generateCoreFile() {
15
15
  // Edit the generator script instead: scripts/generate-realtime-hooks.ts
16
16
 
17
17
  import { useEffect, useState, useCallback, useRef } from 'react';
18
- import { supabase } from '../supabase-client-generated';
18
+ // This import should be relative to its new location in utils/
19
+ import { supabase } from './supabase-client';
19
20
 
20
21
  /**
21
22
  * Represents a single search query against a field
@@ -301,13 +302,14 @@ export function applyFilter<T>(
301
302
  export function applyOrderBy<T>(
302
303
  query: SupabaseQueryBuilder,
303
304
  orderBy?: T,
304
- hasCreatedAt?: boolean
305
+ hasCreatedAt?: boolean,
306
+ createdAtField: string = 'createdAt'
305
307
  ): SupabaseQueryBuilder {
306
308
  if (!orderBy) {
307
- // By default, sort by created_at if available
309
+ // By default, sort by createdAt if available, using the actual field name from Prisma
308
310
  if (hasCreatedAt) {
309
311
  // @ts-ignore: Supabase typing issue
310
- return query.order('created_at', { ascending: false });
312
+ return query.order(createdAtField, { ascending: false });
311
313
  }
312
314
  return query;
313
315
  }
@@ -341,9 +343,19 @@ export function createSuparismaHook<
341
343
  hasCreatedAt: boolean;
342
344
  hasUpdatedAt: boolean;
343
345
  searchFields?: string[];
344
- defaultValues?: Record<string, string>; // Added parameter for default values
346
+ defaultValues?: Record<string, string>;
347
+ createdAtField?: string;
348
+ updatedAtField?: string;
345
349
  }) {
346
- const { tableName, hasCreatedAt, hasUpdatedAt, searchFields = [], defaultValues = {} } = config;
350
+ const {
351
+ tableName,
352
+ hasCreatedAt,
353
+ hasUpdatedAt,
354
+ searchFields = [],
355
+ defaultValues = {},
356
+ createdAtField = 'createdAt',
357
+ updatedAtField = 'updatedAt'
358
+ } = config;
347
359
 
348
360
  /**
349
361
  * The main hook function that provides all data access methods for a model.
@@ -383,6 +395,9 @@ export function createSuparismaHook<
383
395
  const [error, setError] = useState<Error | null>(null);
384
396
  const [loading, setLoading] = useState<boolean>(false);
385
397
 
398
+ // This is the total count, unaffected by pagination limits
399
+ const [count, setCount] = useState<number>(0);
400
+
386
401
  // Search state
387
402
  const [searchQueries, setSearchQueries] = useState<SearchQuery[]>([]);
388
403
  const [searchLoading, setSearchLoading] = useState<boolean>(false);
@@ -391,6 +406,34 @@ export function createSuparismaHook<
391
406
  const channelRef = useRef<ReturnType<typeof supabase.channel> | null>(null);
392
407
  const searchTimeoutRef = useRef<NodeJS.Timeout | null>(null);
393
408
  const isSearchingRef = useRef<boolean>(false);
409
+
410
+ // Function to fetch the total count from Supabase with current filters
411
+ const fetchTotalCount = useCallback(async () => {
412
+ try {
413
+ // Skip count updates during search
414
+ if (isSearchingRef.current) return;
415
+
416
+ let countQuery = supabase.from(tableName).select('*', { count: 'exact', head: true });
417
+
418
+ // Apply where conditions if provided
419
+ if (where) {
420
+ countQuery = applyFilter(countQuery, where);
421
+ }
422
+
423
+ const { count: totalCount, error: countError } = await countQuery;
424
+
425
+ if (!countError) {
426
+ setCount(totalCount || 0);
427
+ }
428
+ } catch (err) {
429
+ console.error(\`Error fetching count for \${tableName}:\`, err);
430
+ }
431
+ }, [where, tableName]);
432
+
433
+ // Update total count whenever where filter changes
434
+ useEffect(() => {
435
+ fetchTotalCount();
436
+ }, [fetchTotalCount]);
394
437
 
395
438
  // Create the search state object with all required methods
396
439
  const search: SearchState = {
@@ -537,6 +580,9 @@ export function createSuparismaHook<
537
580
  });
538
581
  }
539
582
 
583
+ // Set count directly for search results
584
+ setCount(results.length);
585
+
540
586
  // Apply ordering if needed
541
587
  if (orderBy) {
542
588
  const orderEntries = Object.entries(orderBy);
@@ -556,16 +602,17 @@ export function createSuparismaHook<
556
602
  }
557
603
 
558
604
  // Apply pagination if needed
605
+ let paginatedResults = results;
559
606
  if (limit && limit > 0) {
560
- results = results.slice(0, limit);
607
+ paginatedResults = results.slice(0, limit);
561
608
  }
562
609
 
563
610
  if (offset && offset > 0) {
564
- results = results.slice(offset);
611
+ paginatedResults = paginatedResults.slice(offset);
565
612
  }
566
613
 
567
614
  // Update data with search results
568
- setData(results);
615
+ setData(paginatedResults);
569
616
  } catch (err) {
570
617
  console.error('Search error:', err);
571
618
  setError(err as Error);
@@ -620,11 +667,11 @@ export function createSuparismaHook<
620
667
 
621
668
  // Apply order by if provided
622
669
  if (params?.orderBy) {
623
- query = applyOrderBy(query, params.orderBy, hasCreatedAt);
670
+ query = applyOrderBy(query, params.orderBy, hasCreatedAt, createdAtField);
624
671
  } else if (hasCreatedAt) {
625
- // Default ordering if available
672
+ // Use the actual createdAt field name from Prisma
626
673
  // @ts-ignore: Supabase typing issue
627
- query = query.order('created_at', { ascending: false });
674
+ query = query.order(createdAtField, { ascending: false });
628
675
  }
629
676
 
630
677
  // Apply limit if provided
@@ -646,6 +693,12 @@ export function createSuparismaHook<
646
693
  // Only update data if not currently searching
647
694
  if (!isSearchingRef.current) {
648
695
  setData(typedData);
696
+
697
+ // If the where filter changed, update the total count
698
+ if (JSON.stringify(params?.where) !== JSON.stringify(where)) {
699
+ // Use our standard count fetching function instead of duplicating logic
700
+ setTimeout(() => fetchTotalCount(), 0);
701
+ }
649
702
  }
650
703
 
651
704
  return { data: typedData, error: null };
@@ -656,7 +709,7 @@ export function createSuparismaHook<
656
709
  } finally {
657
710
  setLoading(false);
658
711
  }
659
- }, []);
712
+ }, [fetchTotalCount, where, tableName, hasCreatedAt, createdAtField]);
660
713
 
661
714
  /**
662
715
  * Find a single record by its unique identifier (usually ID).
@@ -809,6 +862,9 @@ export function createSuparismaHook<
809
862
  newData = newData.slice(0, currentLimit);
810
863
  }
811
864
 
865
+ // Fetch the updated count after the data changes
866
+ setTimeout(() => fetchTotalCount(), 0);
867
+
812
868
  return newData;
813
869
  } catch (error) {
814
870
  console.error('Error processing INSERT event:', error);
@@ -816,19 +872,25 @@ export function createSuparismaHook<
816
872
  }
817
873
  });
818
874
  } else if (payload.eventType === 'UPDATE') {
819
- // Process update event
875
+ // Process update event
820
876
  setData((prev) => {
821
877
  // Skip if search is active
822
878
  if (isSearchingRef.current) {
823
879
  return prev;
824
880
  }
825
881
 
826
- return prev.map((item) =>
882
+ const newData = prev.map((item) =>
827
883
  // @ts-ignore: Supabase typing issue
828
884
  'id' in item && 'id' in payload.new && item.id === payload.new.id
829
885
  ? (payload.new as TWithRelations)
830
886
  : item
831
887
  );
888
+
889
+ // Fetch the updated count after the data changes
890
+ // For updates, the count might not change but we fetch anyway to be consistent
891
+ setTimeout(() => fetchTotalCount(), 0);
892
+
893
+ return newData;
832
894
  });
833
895
  } else if (payload.eventType === 'DELETE') {
834
896
  // Process delete event
@@ -847,6 +909,9 @@ export function createSuparismaHook<
847
909
  return !('id' in item && 'id' in payload.old && item.id === payload.old.id);
848
910
  });
849
911
 
912
+ // Fetch the updated count after the data changes
913
+ setTimeout(() => fetchTotalCount(), 0);
914
+
850
915
  // If we need to maintain the size with a limit
851
916
  if (currentLimit && currentLimit > 0 && filteredData.length < currentSize && currentSize === currentLimit) {
852
917
  console.log(\`Record deleted with limit \${currentLimit}, will fetch additional record to maintain size\`);
@@ -931,6 +996,9 @@ export function createSuparismaHook<
931
996
  take: limit,
932
997
  skip: offset
933
998
  });
999
+
1000
+ // Also update the total count
1001
+ fetchTotalCount();
934
1002
  }
935
1003
  return;
936
1004
  }
@@ -943,7 +1011,10 @@ export function createSuparismaHook<
943
1011
  take: limit,
944
1012
  skip: offset
945
1013
  });
946
- }, [findMany, where, orderBy, limit, offset, optionsChanged]);
1014
+
1015
+ // Initial count fetch
1016
+ fetchTotalCount();
1017
+ }, [findMany, where, orderBy, limit, offset, optionsChanged, fetchTotalCount]);
947
1018
 
948
1019
  /**
949
1020
  * Create a new record with the provided data.
@@ -1008,8 +1079,9 @@ export function createSuparismaHook<
1008
1079
  const itemWithDefaults = {
1009
1080
  ...appliedDefaults, // Apply schema defaults first
1010
1081
  ...data, // Then user data (overrides defaults)
1011
- ...(hasCreatedAt ? { created_at: now } : {}), // Always set timestamps
1012
- ...(hasUpdatedAt ? { updated_at: now } : {})
1082
+ // Use the actual field names from Prisma
1083
+ ...(hasCreatedAt ? { [createdAtField]: now } : {}),
1084
+ ...(hasUpdatedAt ? { [updatedAtField]: now } : {})
1013
1085
  };
1014
1086
 
1015
1087
  const { data: result, error } = await supabase
@@ -1019,6 +1091,9 @@ export function createSuparismaHook<
1019
1091
 
1020
1092
  if (error) throw error;
1021
1093
 
1094
+ // Update the total count after a successful creation
1095
+ setTimeout(() => fetchTotalCount(), 0);
1096
+
1022
1097
  // Return created record
1023
1098
  return { data: result?.[0] as TWithRelations, error: null };
1024
1099
  } catch (err: any) {
@@ -1028,7 +1103,7 @@ export function createSuparismaHook<
1028
1103
  } finally {
1029
1104
  setLoading(false);
1030
1105
  }
1031
- }, []);
1106
+ }, [fetchTotalCount]);
1032
1107
 
1033
1108
  /**
1034
1109
  * Update an existing record identified by a unique identifier.
@@ -1081,7 +1156,8 @@ export function createSuparismaHook<
1081
1156
 
1082
1157
  const itemWithDefaults = {
1083
1158
  ...params.data,
1084
- ...(hasUpdatedAt ? { updated_at: now } : {})
1159
+ // Use the actual updatedAt field name from Prisma
1160
+ ...(hasUpdatedAt ? { [updatedAtField]: now } : {})
1085
1161
  };
1086
1162
 
1087
1163
  const { data, error } = await supabase
@@ -1092,6 +1168,11 @@ export function createSuparismaHook<
1092
1168
 
1093
1169
  if (error) throw error;
1094
1170
 
1171
+ // Update the total count after a successful update
1172
+ // This is for consistency with other operations, and because
1173
+ // updates can sometimes affect filtering based on updated values
1174
+ setTimeout(() => fetchTotalCount(), 0);
1175
+
1095
1176
  // Return updated record
1096
1177
  return { data: data?.[0] as TWithRelations, error: null };
1097
1178
  } catch (err: any) {
@@ -1101,7 +1182,7 @@ export function createSuparismaHook<
1101
1182
  } finally {
1102
1183
  setLoading(false);
1103
1184
  }
1104
- }, []);
1185
+ }, [fetchTotalCount]);
1105
1186
 
1106
1187
  /**
1107
1188
  * Delete a record by its unique identifier.
@@ -1154,6 +1235,9 @@ export function createSuparismaHook<
1154
1235
 
1155
1236
  if (error) throw error;
1156
1237
 
1238
+ // Update the total count after a successful deletion
1239
+ setTimeout(() => fetchTotalCount(), 0);
1240
+
1157
1241
  // Return the deleted record
1158
1242
  return { data: recordToDelete as TWithRelations, error: null };
1159
1243
  } catch (err: any) {
@@ -1163,7 +1247,7 @@ export function createSuparismaHook<
1163
1247
  } finally {
1164
1248
  setLoading(false);
1165
1249
  }
1166
- }, []);
1250
+ }, [fetchTotalCount]);
1167
1251
 
1168
1252
  /**
1169
1253
  * Delete multiple records matching the filter criteria.
@@ -1220,6 +1304,9 @@ export function createSuparismaHook<
1220
1304
 
1221
1305
  if (deleteError) throw deleteError;
1222
1306
 
1307
+ // Update the total count after a successful bulk deletion
1308
+ setTimeout(() => fetchTotalCount(), 0);
1309
+
1223
1310
  // Return the count of deleted records
1224
1311
  return { count: recordsToDelete.length, error: null };
1225
1312
  } catch (err: any) {
@@ -1229,7 +1316,7 @@ export function createSuparismaHook<
1229
1316
  } finally {
1230
1317
  setLoading(false);
1231
1318
  }
1232
- }, []);
1319
+ }, [fetchTotalCount]);
1233
1320
 
1234
1321
  /**
1235
1322
  * Find the first record matching the filter criteria.
@@ -1312,21 +1399,13 @@ export function createSuparismaHook<
1312
1399
 
1313
1400
  /**
1314
1401
  * Count the number of records matching the filter criteria.
1402
+ * This is a manual method to get the count with a different filter
1403
+ * than the main hook's filter.
1315
1404
  *
1316
1405
  * @param params - Query parameters for filtering
1317
1406
  * @returns A promise with the count of matching records
1318
- *
1319
- * @example
1320
- * // Count all users
1321
- * const count = await users.count();
1322
- *
1323
- * @example
1324
- * // Count active users
1325
- * const activeCount = await users.count({
1326
- * where: { active: true }
1327
- * });
1328
1407
  */
1329
- const count = useCallback(async (params?: {
1408
+ const countFn = useCallback(async (params?: {
1330
1409
  where?: TWhereInput;
1331
1410
  }): Promise<number> => {
1332
1411
  try {
@@ -1395,6 +1474,7 @@ export function createSuparismaHook<
1395
1474
  data,
1396
1475
  error,
1397
1476
  loading,
1477
+ count, // Now including count as a reactive state value
1398
1478
 
1399
1479
  // Finder methods
1400
1480
  findUnique,
@@ -1408,9 +1488,6 @@ export function createSuparismaHook<
1408
1488
  deleteMany,
1409
1489
  upsert,
1410
1490
 
1411
- // Utilities
1412
- count,
1413
-
1414
1491
  // Manual refresh
1415
1492
  refresh
1416
1493
  };
@@ -1423,8 +1500,13 @@ export function createSuparismaHook<
1423
1500
  }
1424
1501
  : api;
1425
1502
  };
1426
- }`;
1427
- const outputPath = path_1.default.join(config_1.OUTPUT_DIR, 'core.ts');
1503
+ }
1504
+ `; // Ensure template literal is closed
1505
+ // Output to the UTILS_DIR
1506
+ const outputPath = path_1.default.join(config_1.UTILS_DIR, 'core.ts');
1507
+ if (!fs_1.default.existsSync(config_1.UTILS_DIR)) {
1508
+ fs_1.default.mkdirSync(config_1.UTILS_DIR, { recursive: true });
1509
+ }
1428
1510
  fs_1.default.writeFileSync(outputPath, coreContent);
1429
- console.log(`Updated core hook factory with improved refresh and count functions at ${outputPath}`);
1511
+ console.log(`Generated core utility file at ${outputPath}`);
1430
1512
  }
@@ -16,7 +16,9 @@ const config_1 = require("../config");
16
16
  * @param modelInfo - Processed model information with metadata
17
17
  */
18
18
  function generateModelHookFile(modelInfo) {
19
- const { modelName, tableName, hasCreatedAt, hasUpdatedAt, searchFields, defaultValues } = modelInfo;
19
+ const { modelName, tableName, hasCreatedAt, hasUpdatedAt, searchFields, defaultValues, createdAtField = 'createdAt', // Default to camelCase but use actual field name if provided
20
+ updatedAtField = 'updatedAt' // Default to camelCase but use actual field name if provided
21
+ } = modelInfo;
20
22
  // Configure search fields if available
21
23
  const searchConfig = searchFields && searchFields.length > 0
22
24
  ? `,\n // Configure search for fields with @enableSearch annotation\n searchFields: ${JSON.stringify(searchFields)}`
@@ -25,11 +27,14 @@ function generateModelHookFile(modelInfo) {
25
27
  const defaultValuesConfig = defaultValues
26
28
  ? `,\n // Default values from schema\n defaultValues: ${JSON.stringify(defaultValues)}`
27
29
  : '';
30
+ // Add createdAt/updatedAt field name config
31
+ const fieldNamesConfig = `${hasCreatedAt ? `,\n // Field name for createdAt from Prisma schema\n createdAtField: "${createdAtField}"` : ''}${hasUpdatedAt ? `,\n // Field name for updatedAt from Prisma schema\n updatedAtField: "${updatedAtField}"` : ''}`;
28
32
  // Generate the hook content
29
33
  const hookContent = `// THIS FILE IS AUTO-GENERATED - DO NOT EDIT DIRECTLY
30
34
  // Edit the generator script instead: scripts/generate-realtime-hooks.ts
31
35
 
32
- import { createSuparismaHook } from './core';
36
+ // Corrected import for core hook factory
37
+ import { createSuparismaHook } from '../utils/core';
33
38
  import type {
34
39
  ${modelName}WithRelations,
35
40
  ${modelName}CreateInput,
@@ -39,7 +44,7 @@ import type {
39
44
  ${modelName}OrderByInput,
40
45
  ${modelName}HookApi,
41
46
  Use${modelName}Options
42
- } from './${modelName}Types';
47
+ } from '../types/${modelName}Types';
43
48
 
44
49
  /**
45
50
  * A Prisma-like hook for interacting with ${modelName} records with real-time capabilities.
@@ -58,7 +63,7 @@ import type {
58
63
  * // With filtering and ordering
59
64
  * const ${modelName.toLowerCase()} = ${config_1.HOOK_NAME_PREFIX}${modelName}({
60
65
  * where: { active: true },
61
- * orderBy: { created_at: 'desc' },
66
+ * orderBy: { createdAt: 'desc' }, // Note: Using actual Prisma field name
62
67
  * limit: 10
63
68
  * });
64
69
  *
@@ -99,10 +104,14 @@ export const ${config_1.HOOK_NAME_PREFIX}${modelName} = createSuparismaHook<
99
104
  >({
100
105
  tableName: '${tableName}',
101
106
  hasCreatedAt: ${hasCreatedAt},
102
- hasUpdatedAt: ${hasUpdatedAt}${searchConfig}${defaultValuesConfig}
103
- }) as (options?: Use${modelName}Options) => ${modelName}HookApi;
107
+ hasUpdatedAt: ${hasUpdatedAt}${searchConfig}${defaultValuesConfig}${fieldNamesConfig}
108
+ }) as unknown as (options?: Use${modelName}Options) => ${modelName}HookApi;
104
109
  `;
105
- const outputPath = path_1.default.join(config_1.OUTPUT_DIR, `${config_1.HOOK_NAME_PREFIX}${modelName}.ts`);
110
+ // Output to the HOOKS_DIR
111
+ const outputPath = path_1.default.join(config_1.HOOKS_DIR, `${config_1.HOOK_NAME_PREFIX}${modelName}.ts`);
112
+ if (!fs_1.default.existsSync(config_1.HOOKS_DIR)) {
113
+ fs_1.default.mkdirSync(config_1.HOOKS_DIR, { recursive: true });
114
+ }
106
115
  fs_1.default.writeFileSync(outputPath, hookContent);
107
116
  console.log(`Generated hook for ${modelName} at ${outputPath}`);
108
117
  }