suparisma 1.2.3 → 1.2.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.
- package/dist/generators/coreGenerator.js +97 -37
- package/dist/generators/hookGenerator.js +6 -2
- package/dist/generators/typeGenerator.js +43 -31
- package/dist/index.js +6 -1
- package/dist/suparisma/generated/utils/core.js +1835 -0
- package/package.json +1 -1
- package/tmp/generated-test/hooks/useSuparismaAsset.ts +94 -0
- package/tmp/generated-test/hooks/useSuparismaChapter.ts +96 -0
- package/tmp/generated-test/hooks/useSuparismaCourse.ts +96 -0
- package/tmp/generated-test/hooks/useSuparismaDeviceSession.ts +94 -0
- package/tmp/generated-test/hooks/useSuparismaEnrollment.ts +92 -0
- package/tmp/generated-test/hooks/useSuparismaLesson.ts +96 -0
- package/tmp/generated-test/hooks/useSuparismaLessonPurchase.ts +92 -0
- package/tmp/generated-test/hooks/useSuparismaLessonQuestion.ts +96 -0
- package/tmp/generated-test/hooks/useSuparismaPayoutMethod.ts +96 -0
- package/tmp/generated-test/hooks/useSuparismaPayoutRequest.ts +96 -0
- package/tmp/generated-test/hooks/useSuparismaQuestionOption.ts +92 -0
- package/tmp/generated-test/hooks/useSuparismaSavedPaymentMethod.ts +96 -0
- package/tmp/generated-test/hooks/useSuparismaTeacherPayoutInfo.ts +96 -0
- package/tmp/generated-test/hooks/useSuparismaThing.ts +96 -0
- package/tmp/generated-test/hooks/useSuparismaUser.ts +96 -0
- package/tmp/generated-test/hooks/useSuparismaVideoNote.ts +96 -0
- package/tmp/generated-test/hooks/useSuparismaWallet.ts +96 -0
- package/tmp/generated-test/hooks/useSuparismaWalletTransaction.ts +96 -0
- package/tmp/generated-test/hooks/useSuparismaWatchProgress.ts +96 -0
- package/tmp/generated-test/index.ts +140 -0
- package/tmp/generated-test/types/AssetTypes.ts +485 -0
- package/tmp/generated-test/types/ChapterTypes.ts +488 -0
- package/tmp/generated-test/types/CourseTypes.ts +519 -0
- package/tmp/generated-test/types/DeviceSessionTypes.ts +489 -0
- package/tmp/generated-test/types/EnrollmentTypes.ts +495 -0
- package/tmp/generated-test/types/LessonPurchaseTypes.ts +490 -0
- package/tmp/generated-test/types/LessonQuestionTypes.ts +496 -0
- package/tmp/generated-test/types/LessonTypes.ts +517 -0
- package/tmp/generated-test/types/PayoutMethodTypes.ts +517 -0
- package/tmp/generated-test/types/PayoutRequestTypes.ts +528 -0
- package/tmp/generated-test/types/QuestionOptionTypes.ts +479 -0
- package/tmp/generated-test/types/SavedPaymentMethodTypes.ts +497 -0
- package/tmp/generated-test/types/TeacherPayoutInfoTypes.ts +480 -0
- package/tmp/generated-test/types/ThingTypes.ts +482 -0
- package/tmp/generated-test/types/UserTypes.ts +487 -0
- package/tmp/generated-test/types/VideoNoteTypes.ts +489 -0
- package/tmp/generated-test/types/WalletTransactionTypes.ts +505 -0
- package/tmp/generated-test/types/WalletTypes.ts +480 -0
- package/tmp/generated-test/types/WatchProgressTypes.ts +493 -0
- package/tmp/generated-test/utils/core.ts +2306 -0
- package/tmp/generated-test/utils/supabase-client.ts +17 -0
- package/tmp/generated-test2/hooks/useSuparismaAsset.ts +94 -0
- package/tmp/generated-test2/hooks/useSuparismaChapter.ts +96 -0
- package/tmp/generated-test2/hooks/useSuparismaCourse.ts +96 -0
- package/tmp/generated-test2/hooks/useSuparismaDeviceSession.ts +94 -0
- package/tmp/generated-test2/hooks/useSuparismaEnrollment.ts +92 -0
- package/tmp/generated-test2/hooks/useSuparismaLesson.ts +96 -0
- package/tmp/generated-test2/hooks/useSuparismaLessonPurchase.ts +92 -0
- package/tmp/generated-test2/hooks/useSuparismaLessonQuestion.ts +96 -0
- package/tmp/generated-test2/hooks/useSuparismaPayoutMethod.ts +96 -0
- package/tmp/generated-test2/hooks/useSuparismaPayoutRequest.ts +96 -0
- package/tmp/generated-test2/hooks/useSuparismaQuestionOption.ts +92 -0
- package/tmp/generated-test2/hooks/useSuparismaSavedPaymentMethod.ts +96 -0
- package/tmp/generated-test2/hooks/useSuparismaTeacherPayoutInfo.ts +96 -0
- package/tmp/generated-test2/hooks/useSuparismaThing.ts +96 -0
- package/tmp/generated-test2/hooks/useSuparismaUser.ts +96 -0
- package/tmp/generated-test2/hooks/useSuparismaVideoNote.ts +96 -0
- package/tmp/generated-test2/hooks/useSuparismaWallet.ts +96 -0
- package/tmp/generated-test2/hooks/useSuparismaWalletTransaction.ts +96 -0
- package/tmp/generated-test2/hooks/useSuparismaWatchProgress.ts +96 -0
- package/tmp/generated-test2/index.ts +140 -0
- package/tmp/generated-test2/types/AssetTypes.ts +485 -0
- package/tmp/generated-test2/types/ChapterTypes.ts +488 -0
- package/tmp/generated-test2/types/CourseTypes.ts +522 -0
- package/tmp/generated-test2/types/DeviceSessionTypes.ts +489 -0
- package/tmp/generated-test2/types/EnrollmentTypes.ts +495 -0
- package/tmp/generated-test2/types/LessonPurchaseTypes.ts +490 -0
- package/tmp/generated-test2/types/LessonQuestionTypes.ts +496 -0
- package/tmp/generated-test2/types/LessonTypes.ts +517 -0
- package/tmp/generated-test2/types/PayoutMethodTypes.ts +517 -0
- package/tmp/generated-test2/types/PayoutRequestTypes.ts +528 -0
- package/tmp/generated-test2/types/QuestionOptionTypes.ts +479 -0
- package/tmp/generated-test2/types/SavedPaymentMethodTypes.ts +497 -0
- package/tmp/generated-test2/types/TeacherPayoutInfoTypes.ts +480 -0
- package/tmp/generated-test2/types/ThingTypes.ts +482 -0
- package/tmp/generated-test2/types/UserTypes.ts +490 -0
- package/tmp/generated-test2/types/VideoNoteTypes.ts +489 -0
- package/tmp/generated-test2/types/WalletTransactionTypes.ts +505 -0
- package/tmp/generated-test2/types/WalletTypes.ts +480 -0
- package/tmp/generated-test2/types/WatchProgressTypes.ts +493 -0
- package/tmp/generated-test2/utils/core.ts +2306 -0
- package/tmp/generated-test2/utils/supabase-client.ts +17 -0
- package/tmp/generated-test3/hooks/useSuparismaAsset.ts +94 -0
- package/tmp/generated-test3/hooks/useSuparismaChapter.ts +98 -0
- package/tmp/generated-test3/hooks/useSuparismaCourse.ts +98 -0
- package/tmp/generated-test3/hooks/useSuparismaDeviceSession.ts +94 -0
- package/tmp/generated-test3/hooks/useSuparismaEnrollment.ts +94 -0
- package/tmp/generated-test3/hooks/useSuparismaLesson.ts +98 -0
- package/tmp/generated-test3/hooks/useSuparismaLessonPurchase.ts +94 -0
- package/tmp/generated-test3/hooks/useSuparismaLessonQuestion.ts +98 -0
- package/tmp/generated-test3/hooks/useSuparismaPayoutMethod.ts +96 -0
- package/tmp/generated-test3/hooks/useSuparismaPayoutRequest.ts +96 -0
- package/tmp/generated-test3/hooks/useSuparismaQuestionOption.ts +94 -0
- package/tmp/generated-test3/hooks/useSuparismaSavedPaymentMethod.ts +96 -0
- package/tmp/generated-test3/hooks/useSuparismaTeacherPayoutInfo.ts +98 -0
- package/tmp/generated-test3/hooks/useSuparismaThing.ts +96 -0
- package/tmp/generated-test3/hooks/useSuparismaUser.ts +98 -0
- package/tmp/generated-test3/hooks/useSuparismaVideoNote.ts +98 -0
- package/tmp/generated-test3/hooks/useSuparismaWallet.ts +98 -0
- package/tmp/generated-test3/hooks/useSuparismaWalletTransaction.ts +98 -0
- package/tmp/generated-test3/hooks/useSuparismaWatchProgress.ts +98 -0
- package/tmp/generated-test3/index.ts +140 -0
- package/tmp/generated-test3/types/AssetTypes.ts +485 -0
- package/tmp/generated-test3/types/ChapterTypes.ts +488 -0
- package/tmp/generated-test3/types/CourseTypes.ts +522 -0
- package/tmp/generated-test3/types/DeviceSessionTypes.ts +489 -0
- package/tmp/generated-test3/types/EnrollmentTypes.ts +495 -0
- package/tmp/generated-test3/types/LessonPurchaseTypes.ts +490 -0
- package/tmp/generated-test3/types/LessonQuestionTypes.ts +496 -0
- package/tmp/generated-test3/types/LessonTypes.ts +517 -0
- package/tmp/generated-test3/types/PayoutMethodTypes.ts +517 -0
- package/tmp/generated-test3/types/PayoutRequestTypes.ts +528 -0
- package/tmp/generated-test3/types/QuestionOptionTypes.ts +479 -0
- package/tmp/generated-test3/types/SavedPaymentMethodTypes.ts +497 -0
- package/tmp/generated-test3/types/TeacherPayoutInfoTypes.ts +480 -0
- package/tmp/generated-test3/types/ThingTypes.ts +482 -0
- package/tmp/generated-test3/types/UserTypes.ts +490 -0
- package/tmp/generated-test3/types/VideoNoteTypes.ts +489 -0
- package/tmp/generated-test3/types/WalletTransactionTypes.ts +505 -0
- package/tmp/generated-test3/types/WalletTypes.ts +480 -0
- package/tmp/generated-test3/types/WatchProgressTypes.ts +493 -0
- package/tmp/generated-test3/utils/core.ts +2316 -0
- package/tmp/generated-test3/utils/supabase-client.ts +17 -0
- package/tmp/prisma-test-schema-2.prisma +339 -0
- package/tmp/prisma-test-schema.prisma +317 -0
- package/tsconfig.json +1 -1
|
@@ -194,11 +194,14 @@ export type SelectInput<T> = {
|
|
|
194
194
|
* // Include a relation with specific fields
|
|
195
195
|
* { posts: { select: { id: true, title: true } } }
|
|
196
196
|
*/
|
|
197
|
-
export type
|
|
198
|
-
[key: string]: boolean | { select?: Record<string, boolean> };
|
|
199
|
-
};
|
|
197
|
+
export type IncludeValue = boolean | { select?: Record<string, boolean> };
|
|
200
198
|
|
|
201
|
-
export type SuparismaOptions<
|
|
199
|
+
export type SuparismaOptions<
|
|
200
|
+
TWhereInput,
|
|
201
|
+
TOrderByInput,
|
|
202
|
+
TSelectInput = Record<string, boolean>,
|
|
203
|
+
TIncludeInput = Record<never, never>
|
|
204
|
+
> = {
|
|
202
205
|
/** Whether to enable realtime updates (default: true) */
|
|
203
206
|
realtime?: boolean;
|
|
204
207
|
/** Custom channel name for realtime subscription */
|
|
@@ -222,7 +225,14 @@ export type SuparismaOptions<TWhereInput, TOrderByInput, TSelectInput = Record<s
|
|
|
222
225
|
* Include related records (foreign key relations).
|
|
223
226
|
* @example { posts: true } or { posts: { select: { id: true, title: true } } }
|
|
224
227
|
*/
|
|
225
|
-
include?:
|
|
228
|
+
include?: TIncludeInput;
|
|
229
|
+
/**
|
|
230
|
+
* Whether to enable the hook (default: true).
|
|
231
|
+
* When false, the hook will not fetch data or set up realtime subscriptions.
|
|
232
|
+
* Useful for conditional fetching, e.g., waiting for auth/user data to be ready.
|
|
233
|
+
* @example enabled: !!user?.id
|
|
234
|
+
*/
|
|
235
|
+
enabled?: boolean;
|
|
226
236
|
};
|
|
227
237
|
|
|
228
238
|
/**
|
|
@@ -794,7 +804,8 @@ function matchesFilter<T>(record: any, filter: T): boolean {
|
|
|
794
804
|
*/
|
|
795
805
|
export function buildSelectString<TSelect, TInclude>(
|
|
796
806
|
select?: TSelect,
|
|
797
|
-
include?: TInclude
|
|
807
|
+
include?: TInclude,
|
|
808
|
+
relationMappings?: Record<string, string>
|
|
798
809
|
): string {
|
|
799
810
|
const parts: string[] = [];
|
|
800
811
|
|
|
@@ -812,9 +823,16 @@ export function buildSelectString<TSelect, TInclude>(
|
|
|
812
823
|
// Handle include - add related records
|
|
813
824
|
if (include && typeof include === 'object') {
|
|
814
825
|
for (const [relationName, relationValue] of Object.entries(include)) {
|
|
826
|
+
const relatedTableName = relationMappings?.[relationName] || relationName;
|
|
827
|
+
// If mapping exists, use PostgREST alias syntax: alias:foreignTable(...)
|
|
828
|
+
const embedName =
|
|
829
|
+
relationMappings?.[relationName] && relatedTableName !== relationName
|
|
830
|
+
? \`\${relationName}:\${relatedTableName}\`
|
|
831
|
+
: relationName;
|
|
832
|
+
|
|
815
833
|
if (relationValue === true) {
|
|
816
834
|
// Include all fields from the relation
|
|
817
|
-
parts.push(\`\${
|
|
835
|
+
parts.push(\`\${embedName}(*)\`);
|
|
818
836
|
} else if (typeof relationValue === 'object' && relationValue !== null) {
|
|
819
837
|
// Include specific fields from the relation
|
|
820
838
|
const relationOptions = relationValue as { select?: Record<string, boolean> };
|
|
@@ -824,12 +842,12 @@ export function buildSelectString<TSelect, TInclude>(
|
|
|
824
842
|
.map(([key]) => key);
|
|
825
843
|
|
|
826
844
|
if (relationFields.length > 0) {
|
|
827
|
-
parts.push(\`\${
|
|
845
|
+
parts.push(\`\${embedName}(\${relationFields.join(',')})\`);
|
|
828
846
|
} else {
|
|
829
|
-
parts.push(\`\${
|
|
847
|
+
parts.push(\`\${embedName}(*)\`);
|
|
830
848
|
}
|
|
831
849
|
} else {
|
|
832
|
-
parts.push(\`\${
|
|
850
|
+
parts.push(\`\${embedName}(*)\`);
|
|
833
851
|
}
|
|
834
852
|
}
|
|
835
853
|
}
|
|
@@ -904,6 +922,7 @@ export function createSuparismaHook<
|
|
|
904
922
|
defaultValues?: Record<string, string>;
|
|
905
923
|
createdAtField?: string;
|
|
906
924
|
updatedAtField?: string;
|
|
925
|
+
relationMappings?: Record<string, string>;
|
|
907
926
|
}) {
|
|
908
927
|
const {
|
|
909
928
|
tableName,
|
|
@@ -912,7 +931,8 @@ export function createSuparismaHook<
|
|
|
912
931
|
searchFields = [],
|
|
913
932
|
defaultValues = {},
|
|
914
933
|
createdAtField = 'createdAt',
|
|
915
|
-
updatedAtField = 'updatedAt'
|
|
934
|
+
updatedAtField = 'updatedAt',
|
|
935
|
+
relationMappings = {}
|
|
916
936
|
} = config;
|
|
917
937
|
|
|
918
938
|
/**
|
|
@@ -945,10 +965,11 @@ export function createSuparismaHook<
|
|
|
945
965
|
offset,
|
|
946
966
|
select,
|
|
947
967
|
include,
|
|
968
|
+
enabled = true,
|
|
948
969
|
} = options;
|
|
949
970
|
|
|
950
971
|
// Build the select string once for reuse
|
|
951
|
-
const selectString = buildSelectString(select, include);
|
|
972
|
+
const selectString = buildSelectString(select, include, relationMappings);
|
|
952
973
|
|
|
953
974
|
// Refs to store the latest options for realtime handlers
|
|
954
975
|
const whereRef = useRef(where);
|
|
@@ -956,6 +977,7 @@ export function createSuparismaHook<
|
|
|
956
977
|
const limitRef = useRef(limit);
|
|
957
978
|
const offsetRef = useRef(offset);
|
|
958
979
|
const selectStringRef = useRef(selectString);
|
|
980
|
+
const enabledRef = useRef(enabled);
|
|
959
981
|
|
|
960
982
|
// Update refs whenever options change
|
|
961
983
|
useEffect(() => {
|
|
@@ -978,6 +1000,10 @@ export function createSuparismaHook<
|
|
|
978
1000
|
selectStringRef.current = selectString;
|
|
979
1001
|
}, [selectString]);
|
|
980
1002
|
|
|
1003
|
+
useEffect(() => {
|
|
1004
|
+
enabledRef.current = enabled;
|
|
1005
|
+
}, [enabled]);
|
|
1006
|
+
|
|
981
1007
|
// Single data collection for holding results
|
|
982
1008
|
const [data, setData] = useState<TWithRelations[]>([]);
|
|
983
1009
|
const [error, setError] = useState<Error | null>(null);
|
|
@@ -995,33 +1021,34 @@ export function createSuparismaHook<
|
|
|
995
1021
|
const searchTimeoutRef = useRef<NodeJS.Timeout | null>(null);
|
|
996
1022
|
const isSearchingRef = useRef<boolean>(false);
|
|
997
1023
|
|
|
998
|
-
// Function to fetch the total count from Supabase with current filters
|
|
1024
|
+
// Function to fetch the total count from Supabase with current filters.
|
|
1025
|
+
// IMPORTANT: do NOT capture unstable objects (where/orderBy/etc) in deps.
|
|
1026
|
+
// Read the latest values from refs to avoid effect→setState→rerender loops.
|
|
999
1027
|
const fetchTotalCount = useCallback(async () => {
|
|
1000
1028
|
try {
|
|
1001
|
-
// Skip count updates during search
|
|
1029
|
+
// Skip count updates when disabled or during search
|
|
1030
|
+
if (!enabledRef.current) return;
|
|
1002
1031
|
if (isSearchingRef.current) return;
|
|
1003
1032
|
|
|
1004
1033
|
let countQuery = supabase.from(tableName).select('*', { count: 'exact', head: true });
|
|
1005
1034
|
|
|
1006
|
-
// Apply where conditions
|
|
1007
|
-
|
|
1008
|
-
|
|
1035
|
+
// Apply current where conditions via ref (NOT the captured 'where')
|
|
1036
|
+
const currentWhere = whereRef.current;
|
|
1037
|
+
if (currentWhere) {
|
|
1038
|
+
countQuery = applyFilter(countQuery, currentWhere);
|
|
1009
1039
|
}
|
|
1010
1040
|
|
|
1011
1041
|
const { count: totalCount, error: countError } = await countQuery;
|
|
1012
1042
|
|
|
1013
1043
|
if (!countError) {
|
|
1014
|
-
|
|
1044
|
+
const nextCount = totalCount || 0;
|
|
1045
|
+
// Cheap guard to reduce churn
|
|
1046
|
+
setCount((prev) => (prev === nextCount ? prev : nextCount));
|
|
1015
1047
|
}
|
|
1016
1048
|
} catch (err) {
|
|
1017
1049
|
console.error(\`Error fetching count for \${tableName}:\`, err);
|
|
1018
1050
|
}
|
|
1019
|
-
}, [
|
|
1020
|
-
|
|
1021
|
-
// Update total count whenever where filter changes
|
|
1022
|
-
useEffect(() => {
|
|
1023
|
-
fetchTotalCount();
|
|
1024
|
-
}, [fetchTotalCount]);
|
|
1051
|
+
}, [tableName]);
|
|
1025
1052
|
|
|
1026
1053
|
// Create the search state object with all required methods
|
|
1027
1054
|
const search: SearchState = {
|
|
@@ -1428,16 +1455,17 @@ export function createSuparismaHook<
|
|
|
1428
1455
|
}, []);
|
|
1429
1456
|
|
|
1430
1457
|
// Set up realtime subscription for the list - ONCE and listen to ALL events
|
|
1458
|
+
const channelIdRef = useRef<string | null>(null);
|
|
1459
|
+
|
|
1431
1460
|
useEffect(() => {
|
|
1432
|
-
if
|
|
1461
|
+
// Skip subscription if not enabled or realtime is off
|
|
1462
|
+
if (!enabled || !realtime) return;
|
|
1433
1463
|
|
|
1434
|
-
//
|
|
1435
|
-
|
|
1436
|
-
|
|
1437
|
-
|
|
1438
|
-
|
|
1439
|
-
|
|
1440
|
-
const channelId = channelName || \`changes_to_\${tableName}_\${Math.random().toString(36).substring(2, 15)}\`;
|
|
1464
|
+
// Stable channel id per hook instance (unless user explicitly provides channelName)
|
|
1465
|
+
const channelId =
|
|
1466
|
+
channelName ??
|
|
1467
|
+
channelIdRef.current ??
|
|
1468
|
+
(channelIdRef.current = \`changes_to_\${tableName}_\${generateUUID()}\`);
|
|
1441
1469
|
|
|
1442
1470
|
// ALWAYS listen to ALL events and filter client-side for maximum reliability
|
|
1443
1471
|
let subscriptionConfig: any = {
|
|
@@ -1716,13 +1744,15 @@ export function createSuparismaHook<
|
|
|
1716
1744
|
console.log(\`Subscription status for \${tableName}\`, status);
|
|
1717
1745
|
});
|
|
1718
1746
|
|
|
1719
|
-
// Store the channel ref
|
|
1747
|
+
// Store the channel ref (for optional introspection)
|
|
1720
1748
|
channelRef.current = channel;
|
|
1721
1749
|
|
|
1722
1750
|
return () => {
|
|
1723
1751
|
console.log(\`Unsubscribing from \${channelId}\`);
|
|
1724
|
-
|
|
1725
|
-
|
|
1752
|
+
// Always remove the exact channel created by this effect instance
|
|
1753
|
+
supabase.removeChannel(channel);
|
|
1754
|
+
// Only clear the ref if it still matches (avoid races if effect re-runs)
|
|
1755
|
+
if (channelRef.current === channel) {
|
|
1726
1756
|
channelRef.current = null;
|
|
1727
1757
|
}
|
|
1728
1758
|
|
|
@@ -1731,7 +1761,7 @@ export function createSuparismaHook<
|
|
|
1731
1761
|
searchTimeoutRef.current = null;
|
|
1732
1762
|
}
|
|
1733
1763
|
};
|
|
1734
|
-
}, [realtime, channelName, tableName]); // NEVER include 'where' - subscription should persist
|
|
1764
|
+
}, [realtime, channelName, tableName, enabled]); // NEVER include 'where' - subscription should persist
|
|
1735
1765
|
|
|
1736
1766
|
// Create a memoized options object to prevent unnecessary re-renders
|
|
1737
1767
|
const optionsRef = useRef({ where, orderBy, limit, offset, selectString });
|
|
@@ -1763,6 +1793,12 @@ export function createSuparismaHook<
|
|
|
1763
1793
|
|
|
1764
1794
|
// Load initial data and refetch when options change (BUT NEVER TOUCH SUBSCRIPTION)
|
|
1765
1795
|
useEffect(() => {
|
|
1796
|
+
// Skip fetching if not enabled
|
|
1797
|
+
if (!enabled) {
|
|
1798
|
+
setLoading(false); // Don't show loading spinner when disabled
|
|
1799
|
+
return;
|
|
1800
|
+
}
|
|
1801
|
+
|
|
1766
1802
|
// Skip if search is active
|
|
1767
1803
|
if (isSearchingRef.current) return;
|
|
1768
1804
|
|
|
@@ -1795,7 +1831,31 @@ export function createSuparismaHook<
|
|
|
1795
1831
|
|
|
1796
1832
|
// Initial count fetch
|
|
1797
1833
|
fetchTotalCount();
|
|
1798
|
-
}, [findMany, where, orderBy, limit, offset, optionsChanged, fetchTotalCount]);
|
|
1834
|
+
}, [findMany, where, orderBy, limit, offset, optionsChanged, fetchTotalCount, enabled]);
|
|
1835
|
+
|
|
1836
|
+
// Track previous enabled state to detect changes from false to true
|
|
1837
|
+
const prevEnabledRef = useRef(enabled);
|
|
1838
|
+
|
|
1839
|
+
// Fetch when enabled changes from false to true
|
|
1840
|
+
useEffect(() => {
|
|
1841
|
+
const wasDisabled = !prevEnabledRef.current;
|
|
1842
|
+
const isNowEnabled = enabled;
|
|
1843
|
+
|
|
1844
|
+
// Update the previous value
|
|
1845
|
+
prevEnabledRef.current = enabled;
|
|
1846
|
+
|
|
1847
|
+
// If we just became enabled and have already done initial load, refetch
|
|
1848
|
+
if (wasDisabled && isNowEnabled && initialLoadRef.current) {
|
|
1849
|
+
console.log(\`Hook enabled for \${tableName}, fetching data\`);
|
|
1850
|
+
findMany({
|
|
1851
|
+
where,
|
|
1852
|
+
orderBy,
|
|
1853
|
+
take: limit,
|
|
1854
|
+
skip: offset
|
|
1855
|
+
});
|
|
1856
|
+
fetchTotalCount();
|
|
1857
|
+
}
|
|
1858
|
+
}, [enabled, findMany, where, orderBy, limit, offset, fetchTotalCount]);
|
|
1799
1859
|
|
|
1800
1860
|
/**
|
|
1801
1861
|
* Create a new record with the provided data.
|
|
@@ -16,7 +16,7 @@ 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, createdAtField = 'createdAt', // Default to camelCase but use actual field name if provided
|
|
19
|
+
const { modelName, tableName, hasCreatedAt, hasUpdatedAt, searchFields, defaultValues, relationMappings, createdAtField = 'createdAt', // Default to camelCase but use actual field name if provided
|
|
20
20
|
updatedAtField = 'updatedAt' // Default to camelCase but use actual field name if provided
|
|
21
21
|
} = modelInfo;
|
|
22
22
|
// Configure search fields if available
|
|
@@ -29,6 +29,10 @@ function generateModelHookFile(modelInfo) {
|
|
|
29
29
|
: '';
|
|
30
30
|
// Add createdAt/updatedAt field name config
|
|
31
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}"` : ''}`;
|
|
32
|
+
// Relation mappings config (Prisma relation field -> related table name for PostgREST embedding)
|
|
33
|
+
const relationMappingsConfig = relationMappings && Object.keys(relationMappings).length > 0
|
|
34
|
+
? `,\n // Relation mappings for PostgREST embedding\n relationMappings: ${JSON.stringify(relationMappings)}`
|
|
35
|
+
: '';
|
|
32
36
|
// Generate the hook content
|
|
33
37
|
const hookContent = `// THIS FILE IS AUTO-GENERATED - DO NOT EDIT DIRECTLY
|
|
34
38
|
// Edit the generator script instead: scripts/generate-realtime-hooks.ts
|
|
@@ -118,7 +122,7 @@ export const ${config_1.HOOK_NAME_PREFIX}${modelName} = createSuparismaHook<
|
|
|
118
122
|
>({
|
|
119
123
|
tableName: '${tableName}',
|
|
120
124
|
hasCreatedAt: ${hasCreatedAt},
|
|
121
|
-
hasUpdatedAt: ${hasUpdatedAt}${searchConfig}${defaultValuesConfig}${fieldNamesConfig}
|
|
125
|
+
hasUpdatedAt: ${hasUpdatedAt}${searchConfig}${defaultValuesConfig}${fieldNamesConfig}${relationMappingsConfig}
|
|
122
126
|
}) as unknown as (options?: Use${modelName}Options) => ${modelName}HookApi;
|
|
123
127
|
`;
|
|
124
128
|
// Output to the HOOKS_DIR
|
|
@@ -10,7 +10,7 @@ const config_1 = require("../config");
|
|
|
10
10
|
/**
|
|
11
11
|
* Generate model-specific types for a model
|
|
12
12
|
*/
|
|
13
|
-
function generateModelTypesFile(model) {
|
|
13
|
+
function generateModelTypesFile(model, modelNameToTableName = {}) {
|
|
14
14
|
const modelName = model.name || '';
|
|
15
15
|
const tableName = model.mappedName || modelName;
|
|
16
16
|
// Identify foreign key fields (these end with _id)
|
|
@@ -21,6 +21,13 @@ function generateModelTypesFile(model) {
|
|
|
21
21
|
const relationObjectFields = model.fields
|
|
22
22
|
.filter((field) => field.isRelation && !foreignKeyFields.includes(field.name))
|
|
23
23
|
.map((field) => field.name);
|
|
24
|
+
// Collect relation fields with their target model types (for include + WithRelations typing)
|
|
25
|
+
const relationFields = model.fields.filter((field) => relationObjectFields.includes(field.name) && field.type !== modelName);
|
|
26
|
+
// Map relation field name -> actual related table name (for PostgREST embedding)
|
|
27
|
+
const relationMappings = {};
|
|
28
|
+
for (const rel of relationFields) {
|
|
29
|
+
relationMappings[rel.name] = modelNameToTableName[rel.type] || rel.type;
|
|
30
|
+
}
|
|
24
31
|
// Fields that have default values (should be optional in CreateInput)
|
|
25
32
|
const defaultValueFields = model.fields
|
|
26
33
|
.filter((field) => field.hasDefaultValue)
|
|
@@ -82,7 +89,7 @@ function generateModelTypesFile(model) {
|
|
|
82
89
|
};
|
|
83
90
|
// Create a manual property list for WithRelations interface
|
|
84
91
|
const withRelationsProps = model.fields
|
|
85
|
-
.filter((field) => !relationObjectFields.includes(field.name)
|
|
92
|
+
.filter((field) => !relationObjectFields.includes(field.name))
|
|
86
93
|
.map((field) => {
|
|
87
94
|
const isOptional = field.isOptional;
|
|
88
95
|
const finalType = getFieldType(field);
|
|
@@ -95,6 +102,20 @@ function generateModelTypesFile(model) {
|
|
|
95
102
|
withRelationsProps.push(` ${field}${fieldInfo.isOptional ? '?' : ''}: ${fieldInfo.type === 'Int' ? 'number' : 'string'};`);
|
|
96
103
|
}
|
|
97
104
|
});
|
|
105
|
+
// Add relation object fields as OPTIONAL (only present when included via include/select)
|
|
106
|
+
relationFields.forEach((field) => {
|
|
107
|
+
const relatedModel = field.type;
|
|
108
|
+
const relatedType = field.isList
|
|
109
|
+
? `${relatedModel}WithRelations[]`
|
|
110
|
+
: `${relatedModel}WithRelations${field.isOptional ? ' | null' : ''}`;
|
|
111
|
+
withRelationsProps.push(` ${field.name}?: ${relatedType};`);
|
|
112
|
+
});
|
|
113
|
+
// Build type-only imports for related model types (to type relations and include.select)
|
|
114
|
+
const relatedModelImports = Array.from(new Set(relationFields.map((f) => f.type).filter((t) => t && t !== modelName)));
|
|
115
|
+
// Note: We can't import multiple files in one statement. We'll generate one per model below.
|
|
116
|
+
const relationTypeImportStatements = relatedModelImports
|
|
117
|
+
.map((m) => `import type { ${m}WithRelations, ${m}SelectInput } from './${m}Types';`)
|
|
118
|
+
.join('\n');
|
|
98
119
|
// Create a manual property list for CreateInput
|
|
99
120
|
const createInputProps = model.fields
|
|
100
121
|
.filter((field) => !relationObjectFields.includes(field.name) &&
|
|
@@ -142,7 +163,8 @@ function generateModelTypesFile(model) {
|
|
|
142
163
|
// Edit the generator script instead
|
|
143
164
|
|
|
144
165
|
${customImports}import type { ${modelName} } from '@prisma/client';
|
|
145
|
-
import type { ModelResult, SuparismaOptions, SearchQuery, SearchState, FilterOperators } from '../utils/core';
|
|
166
|
+
import type { ModelResult, SuparismaOptions, SearchQuery, SearchState, FilterOperators, IncludeValue } from '../utils/core';
|
|
167
|
+
${relationTypeImportStatements}
|
|
146
168
|
|
|
147
169
|
/**
|
|
148
170
|
* Extended ${modelName} type that includes relation fields.
|
|
@@ -349,41 +371,28 @@ export type ${modelName}WhereUniqueInput = {
|
|
|
349
371
|
* });
|
|
350
372
|
*/
|
|
351
373
|
export type ${modelName}OrderByInput = {
|
|
352
|
-
[key in keyof ${modelName}
|
|
374
|
+
[key in keyof ${modelName}SelectInput]?: 'asc' | 'desc';
|
|
353
375
|
};
|
|
354
376
|
|
|
355
377
|
/**
|
|
356
|
-
* Select specific fields to return from ${modelName} queries.
|
|
357
|
-
*
|
|
358
|
-
*
|
|
359
|
-
* @example
|
|
360
|
-
* // Only return id and name
|
|
361
|
-
* ${modelName.toLowerCase()}.findMany({
|
|
362
|
-
* select: { id: true, name: true }
|
|
363
|
-
* });
|
|
378
|
+
* Select specific scalar fields to return from ${modelName} queries.
|
|
379
|
+
* Relation fields are intentionally excluded; use \`include\` for relations.
|
|
364
380
|
*/
|
|
365
381
|
export type ${modelName}SelectInput = {
|
|
366
|
-
|
|
382
|
+
${model.fields
|
|
383
|
+
.filter((f) => !relationObjectFields.includes(f.name))
|
|
384
|
+
.map((f) => ` ${f.name}?: boolean;`)
|
|
385
|
+
.join('\n')}
|
|
367
386
|
};
|
|
368
387
|
|
|
369
388
|
/**
|
|
370
389
|
* Include related records when querying ${modelName}.
|
|
371
|
-
*
|
|
372
|
-
*
|
|
373
|
-
* @example
|
|
374
|
-
* // Include all fields from a relation
|
|
375
|
-
* ${modelName.toLowerCase()}.findMany({
|
|
376
|
-
* include: { relatedModel: true }
|
|
377
|
-
* });
|
|
378
|
-
*
|
|
379
|
-
* @example
|
|
380
|
-
* // Include specific fields from a relation
|
|
381
|
-
* ${modelName.toLowerCase()}.findMany({
|
|
382
|
-
* include: { relatedModel: { select: { id: true, name: true } } }
|
|
383
|
-
* });
|
|
390
|
+
* Only real Prisma relation fields are allowed here.
|
|
384
391
|
*/
|
|
385
392
|
export type ${modelName}IncludeInput = {
|
|
386
|
-
|
|
393
|
+
${relationFields
|
|
394
|
+
.map((f) => ` ${f.name}?: IncludeValue | { select?: ${f.type}SelectInput };`)
|
|
395
|
+
.join('\n')}
|
|
387
396
|
};
|
|
388
397
|
|
|
389
398
|
/**
|
|
@@ -400,10 +409,12 @@ export type ${modelName}ManyResult = ModelResult<${modelName}WithRelations[]>;
|
|
|
400
409
|
* Configuration options for the ${modelName} hook.
|
|
401
410
|
* Includes where filters, ordering, pagination, and field selection.
|
|
402
411
|
*/
|
|
403
|
-
export type Use${modelName}Options = SuparismaOptions
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
}
|
|
412
|
+
export type Use${modelName}Options = SuparismaOptions<
|
|
413
|
+
${modelName}WhereInput,
|
|
414
|
+
${modelName}OrderByInput,
|
|
415
|
+
${modelName}SelectInput,
|
|
416
|
+
${modelName}IncludeInput
|
|
417
|
+
>;
|
|
407
418
|
|
|
408
419
|
/**
|
|
409
420
|
* The complete API for interacting with ${modelName} records.
|
|
@@ -726,6 +737,7 @@ ${createInputProps
|
|
|
726
737
|
defaultValues: Object.keys(defaultValues).length > 0 ? defaultValues : undefined,
|
|
727
738
|
createdAtField,
|
|
728
739
|
updatedAtField,
|
|
740
|
+
relationMappings: Object.keys(relationMappings).length > 0 ? relationMappings : undefined,
|
|
729
741
|
zodImports: model.zodImports // Pass through zod imports
|
|
730
742
|
};
|
|
731
743
|
}
|
package/dist/index.js
CHANGED
|
@@ -484,9 +484,14 @@ async function generateHooks() {
|
|
|
484
484
|
(0, coreGenerator_1.generateCoreFile)();
|
|
485
485
|
const models = (0, parser_1.parsePrismaSchema)(config_1.PRISMA_SCHEMA_PATH);
|
|
486
486
|
await configurePrismaTablesForSuparisma(config_1.PRISMA_SCHEMA_PATH);
|
|
487
|
+
// Map Prisma model name -> actual table name (respects @@map)
|
|
488
|
+
const modelNameToTableName = {};
|
|
489
|
+
for (const m of models) {
|
|
490
|
+
modelNameToTableName[m.name] = m.mappedName || m.name;
|
|
491
|
+
}
|
|
487
492
|
const modelInfos = [];
|
|
488
493
|
for (const model of models) {
|
|
489
|
-
const modelInfo = (0, typeGenerator_1.generateModelTypesFile)(model);
|
|
494
|
+
const modelInfo = (0, typeGenerator_1.generateModelTypesFile)(model, modelNameToTableName);
|
|
490
495
|
(0, hookGenerator_1.generateModelHookFile)(modelInfo);
|
|
491
496
|
modelInfos.push(modelInfo);
|
|
492
497
|
}
|