suparisma 1.2.2 → 1.2.6

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 (131) hide show
  1. package/README.md +51 -2
  2. package/dist/generators/coreGenerator.js +200 -15
  3. package/dist/generators/hookGenerator.js +20 -2
  4. package/dist/generators/typeGenerator.js +55 -5
  5. package/dist/index.js +6 -1
  6. package/package.json +1 -1
  7. package/tmp/generated-test/hooks/useSuparismaAsset.ts +94 -0
  8. package/tmp/generated-test/hooks/useSuparismaChapter.ts +96 -0
  9. package/tmp/generated-test/hooks/useSuparismaCourse.ts +96 -0
  10. package/tmp/generated-test/hooks/useSuparismaDeviceSession.ts +94 -0
  11. package/tmp/generated-test/hooks/useSuparismaEnrollment.ts +92 -0
  12. package/tmp/generated-test/hooks/useSuparismaLesson.ts +96 -0
  13. package/tmp/generated-test/hooks/useSuparismaLessonPurchase.ts +92 -0
  14. package/tmp/generated-test/hooks/useSuparismaLessonQuestion.ts +96 -0
  15. package/tmp/generated-test/hooks/useSuparismaPayoutMethod.ts +96 -0
  16. package/tmp/generated-test/hooks/useSuparismaPayoutRequest.ts +96 -0
  17. package/tmp/generated-test/hooks/useSuparismaQuestionOption.ts +92 -0
  18. package/tmp/generated-test/hooks/useSuparismaSavedPaymentMethod.ts +96 -0
  19. package/tmp/generated-test/hooks/useSuparismaTeacherPayoutInfo.ts +96 -0
  20. package/tmp/generated-test/hooks/useSuparismaThing.ts +96 -0
  21. package/tmp/generated-test/hooks/useSuparismaUser.ts +96 -0
  22. package/tmp/generated-test/hooks/useSuparismaVideoNote.ts +96 -0
  23. package/tmp/generated-test/hooks/useSuparismaWallet.ts +96 -0
  24. package/tmp/generated-test/hooks/useSuparismaWalletTransaction.ts +96 -0
  25. package/tmp/generated-test/hooks/useSuparismaWatchProgress.ts +96 -0
  26. package/tmp/generated-test/index.ts +140 -0
  27. package/tmp/generated-test/types/AssetTypes.ts +485 -0
  28. package/tmp/generated-test/types/ChapterTypes.ts +488 -0
  29. package/tmp/generated-test/types/CourseTypes.ts +519 -0
  30. package/tmp/generated-test/types/DeviceSessionTypes.ts +489 -0
  31. package/tmp/generated-test/types/EnrollmentTypes.ts +495 -0
  32. package/tmp/generated-test/types/LessonPurchaseTypes.ts +490 -0
  33. package/tmp/generated-test/types/LessonQuestionTypes.ts +496 -0
  34. package/tmp/generated-test/types/LessonTypes.ts +517 -0
  35. package/tmp/generated-test/types/PayoutMethodTypes.ts +517 -0
  36. package/tmp/generated-test/types/PayoutRequestTypes.ts +528 -0
  37. package/tmp/generated-test/types/QuestionOptionTypes.ts +479 -0
  38. package/tmp/generated-test/types/SavedPaymentMethodTypes.ts +497 -0
  39. package/tmp/generated-test/types/TeacherPayoutInfoTypes.ts +480 -0
  40. package/tmp/generated-test/types/ThingTypes.ts +482 -0
  41. package/tmp/generated-test/types/UserTypes.ts +487 -0
  42. package/tmp/generated-test/types/VideoNoteTypes.ts +489 -0
  43. package/tmp/generated-test/types/WalletTransactionTypes.ts +505 -0
  44. package/tmp/generated-test/types/WalletTypes.ts +480 -0
  45. package/tmp/generated-test/types/WatchProgressTypes.ts +493 -0
  46. package/tmp/generated-test/utils/core.ts +2306 -0
  47. package/tmp/generated-test/utils/supabase-client.ts +17 -0
  48. package/tmp/generated-test2/hooks/useSuparismaAsset.ts +94 -0
  49. package/tmp/generated-test2/hooks/useSuparismaChapter.ts +96 -0
  50. package/tmp/generated-test2/hooks/useSuparismaCourse.ts +96 -0
  51. package/tmp/generated-test2/hooks/useSuparismaDeviceSession.ts +94 -0
  52. package/tmp/generated-test2/hooks/useSuparismaEnrollment.ts +92 -0
  53. package/tmp/generated-test2/hooks/useSuparismaLesson.ts +96 -0
  54. package/tmp/generated-test2/hooks/useSuparismaLessonPurchase.ts +92 -0
  55. package/tmp/generated-test2/hooks/useSuparismaLessonQuestion.ts +96 -0
  56. package/tmp/generated-test2/hooks/useSuparismaPayoutMethod.ts +96 -0
  57. package/tmp/generated-test2/hooks/useSuparismaPayoutRequest.ts +96 -0
  58. package/tmp/generated-test2/hooks/useSuparismaQuestionOption.ts +92 -0
  59. package/tmp/generated-test2/hooks/useSuparismaSavedPaymentMethod.ts +96 -0
  60. package/tmp/generated-test2/hooks/useSuparismaTeacherPayoutInfo.ts +96 -0
  61. package/tmp/generated-test2/hooks/useSuparismaThing.ts +96 -0
  62. package/tmp/generated-test2/hooks/useSuparismaUser.ts +96 -0
  63. package/tmp/generated-test2/hooks/useSuparismaVideoNote.ts +96 -0
  64. package/tmp/generated-test2/hooks/useSuparismaWallet.ts +96 -0
  65. package/tmp/generated-test2/hooks/useSuparismaWalletTransaction.ts +96 -0
  66. package/tmp/generated-test2/hooks/useSuparismaWatchProgress.ts +96 -0
  67. package/tmp/generated-test2/index.ts +140 -0
  68. package/tmp/generated-test2/types/AssetTypes.ts +485 -0
  69. package/tmp/generated-test2/types/ChapterTypes.ts +488 -0
  70. package/tmp/generated-test2/types/CourseTypes.ts +522 -0
  71. package/tmp/generated-test2/types/DeviceSessionTypes.ts +489 -0
  72. package/tmp/generated-test2/types/EnrollmentTypes.ts +495 -0
  73. package/tmp/generated-test2/types/LessonPurchaseTypes.ts +490 -0
  74. package/tmp/generated-test2/types/LessonQuestionTypes.ts +496 -0
  75. package/tmp/generated-test2/types/LessonTypes.ts +517 -0
  76. package/tmp/generated-test2/types/PayoutMethodTypes.ts +517 -0
  77. package/tmp/generated-test2/types/PayoutRequestTypes.ts +528 -0
  78. package/tmp/generated-test2/types/QuestionOptionTypes.ts +479 -0
  79. package/tmp/generated-test2/types/SavedPaymentMethodTypes.ts +497 -0
  80. package/tmp/generated-test2/types/TeacherPayoutInfoTypes.ts +480 -0
  81. package/tmp/generated-test2/types/ThingTypes.ts +482 -0
  82. package/tmp/generated-test2/types/UserTypes.ts +490 -0
  83. package/tmp/generated-test2/types/VideoNoteTypes.ts +489 -0
  84. package/tmp/generated-test2/types/WalletTransactionTypes.ts +505 -0
  85. package/tmp/generated-test2/types/WalletTypes.ts +480 -0
  86. package/tmp/generated-test2/types/WatchProgressTypes.ts +493 -0
  87. package/tmp/generated-test2/utils/core.ts +2306 -0
  88. package/tmp/generated-test2/utils/supabase-client.ts +17 -0
  89. package/tmp/generated-test3/hooks/useSuparismaAsset.ts +94 -0
  90. package/tmp/generated-test3/hooks/useSuparismaChapter.ts +98 -0
  91. package/tmp/generated-test3/hooks/useSuparismaCourse.ts +98 -0
  92. package/tmp/generated-test3/hooks/useSuparismaDeviceSession.ts +94 -0
  93. package/tmp/generated-test3/hooks/useSuparismaEnrollment.ts +94 -0
  94. package/tmp/generated-test3/hooks/useSuparismaLesson.ts +98 -0
  95. package/tmp/generated-test3/hooks/useSuparismaLessonPurchase.ts +94 -0
  96. package/tmp/generated-test3/hooks/useSuparismaLessonQuestion.ts +98 -0
  97. package/tmp/generated-test3/hooks/useSuparismaPayoutMethod.ts +96 -0
  98. package/tmp/generated-test3/hooks/useSuparismaPayoutRequest.ts +96 -0
  99. package/tmp/generated-test3/hooks/useSuparismaQuestionOption.ts +94 -0
  100. package/tmp/generated-test3/hooks/useSuparismaSavedPaymentMethod.ts +96 -0
  101. package/tmp/generated-test3/hooks/useSuparismaTeacherPayoutInfo.ts +98 -0
  102. package/tmp/generated-test3/hooks/useSuparismaThing.ts +96 -0
  103. package/tmp/generated-test3/hooks/useSuparismaUser.ts +98 -0
  104. package/tmp/generated-test3/hooks/useSuparismaVideoNote.ts +98 -0
  105. package/tmp/generated-test3/hooks/useSuparismaWallet.ts +98 -0
  106. package/tmp/generated-test3/hooks/useSuparismaWalletTransaction.ts +98 -0
  107. package/tmp/generated-test3/hooks/useSuparismaWatchProgress.ts +98 -0
  108. package/tmp/generated-test3/index.ts +140 -0
  109. package/tmp/generated-test3/types/AssetTypes.ts +485 -0
  110. package/tmp/generated-test3/types/ChapterTypes.ts +488 -0
  111. package/tmp/generated-test3/types/CourseTypes.ts +522 -0
  112. package/tmp/generated-test3/types/DeviceSessionTypes.ts +489 -0
  113. package/tmp/generated-test3/types/EnrollmentTypes.ts +495 -0
  114. package/tmp/generated-test3/types/LessonPurchaseTypes.ts +490 -0
  115. package/tmp/generated-test3/types/LessonQuestionTypes.ts +496 -0
  116. package/tmp/generated-test3/types/LessonTypes.ts +517 -0
  117. package/tmp/generated-test3/types/PayoutMethodTypes.ts +517 -0
  118. package/tmp/generated-test3/types/PayoutRequestTypes.ts +528 -0
  119. package/tmp/generated-test3/types/QuestionOptionTypes.ts +479 -0
  120. package/tmp/generated-test3/types/SavedPaymentMethodTypes.ts +497 -0
  121. package/tmp/generated-test3/types/TeacherPayoutInfoTypes.ts +480 -0
  122. package/tmp/generated-test3/types/ThingTypes.ts +482 -0
  123. package/tmp/generated-test3/types/UserTypes.ts +490 -0
  124. package/tmp/generated-test3/types/VideoNoteTypes.ts +489 -0
  125. package/tmp/generated-test3/types/WalletTransactionTypes.ts +505 -0
  126. package/tmp/generated-test3/types/WalletTypes.ts +480 -0
  127. package/tmp/generated-test3/types/WatchProgressTypes.ts +493 -0
  128. package/tmp/generated-test3/utils/core.ts +2316 -0
  129. package/tmp/generated-test3/utils/supabase-client.ts +17 -0
  130. package/tmp/prisma-test-schema-2.prisma +339 -0
  131. package/tmp/prisma-test-schema.prisma +317 -0
package/README.md CHANGED
@@ -26,6 +26,8 @@ A powerful, typesafe React hook generator for Supabase, driven by your Prisma sc
26
26
  - [Array Filtering](#array-filtering)
27
27
  - [Sorting Data](#sorting-data)
28
28
  - [Pagination](#pagination)
29
+ - [Field Selection (select)](#field-selection-select)
30
+ - [Including Relations (include)](#including-relations-include)
29
31
  - [Search Functionality](#search-functionality)
30
32
  - [Enabling Search](#enabling-search)
31
33
  - [Search Methods](#search-methods)
@@ -733,6 +735,53 @@ const { data: page2 } = useSuparisma.thing({
733
735
  const { data, count } = useSuparisma.thing();
734
736
  ```
735
737
 
738
+ ### Field Selection (select)
739
+
740
+ Use the `select` option to only return specific fields, reducing payload size:
741
+
742
+ ```tsx
743
+ // Only get id and name fields
744
+ const { data: things } = useSuparisma.thing({
745
+ select: { id: true, name: true }
746
+ });
747
+ // Returns: [{ id: "123", name: "Thing 1" }, ...]
748
+
749
+ // Combine with filtering
750
+ const { data: activeThings } = useSuparisma.thing({
751
+ where: { someEnum: "ONE" },
752
+ select: { id: true, name: true, someNumber: true }
753
+ });
754
+ ```
755
+
756
+ ### Including Relations (include)
757
+
758
+ Use the `include` option to fetch related records (foreign key relations):
759
+
760
+ ```tsx
761
+ // Include all fields from a related model
762
+ const { data: posts } = useSuparisma.post({
763
+ include: { author: true }
764
+ });
765
+ // Returns: [{ id: "123", title: "...", author: { id: "456", name: "John" } }, ...]
766
+
767
+ // Include specific fields from a relation
768
+ const { data: posts } = useSuparisma.post({
769
+ include: {
770
+ author: {
771
+ select: { id: true, name: true }
772
+ }
773
+ }
774
+ });
775
+
776
+ // Combine select and include
777
+ const { data: posts } = useSuparisma.post({
778
+ select: { id: true, title: true },
779
+ include: { author: true, comments: true }
780
+ });
781
+ ```
782
+
783
+ **Note:** The relation names in `include` should match your Prisma schema relation field names.
784
+
736
785
  ### Search Functionality
737
786
 
738
787
  Suparisma provides powerful PostgreSQL full-text search capabilities with automatic RPC function generation and type-safe search methods. Search is enabled per field using annotations in your Prisma schema.
@@ -1525,8 +1574,8 @@ const { data } = useSuparisma.thing({
1525
1574
  | `limit` | `number` | Maximum number of records to return |
1526
1575
  | `offset` | `number` | Number of records to skip for pagination |
1527
1576
  | `realtime` | `boolean` | Enable/disable real-time updates |
1528
- | `select` | `object` | Fields to include in the response |
1529
- | `include` | `object` | Related records to include |
1577
+ | `select` | `object` | Fields to include in the response. Use `{ fieldName: true }` syntax |
1578
+ | `include` | `object` | Related records to include. Use `{ relationName: true }` or `{ relationName: { select: {...} } }` |
1530
1579
  | `search` | `object` | Full-text search configuration |
1531
1580
 
1532
1581
  ### Hook Return Value
@@ -169,7 +169,39 @@ export type AdvancedWhereInput<T> = {
169
169
  * limit: 10
170
170
  * });
171
171
  */
172
- export type SuparismaOptions<TWhereInput, TOrderByInput> = {
172
+ /**
173
+ * Select input type - specify which fields to return
174
+ * Use true to include a field, or use an object for relations
175
+ * @example
176
+ * // Select specific fields
177
+ * { id: true, name: true, email: true }
178
+ *
179
+ * @example
180
+ * // Select fields with relations
181
+ * { id: true, name: true, posts: true }
182
+ */
183
+ export type SelectInput<T> = {
184
+ [K in keyof T]?: boolean;
185
+ };
186
+
187
+ /**
188
+ * Include input type - specify which relations to include
189
+ * @example
190
+ * // Include a relation with all fields
191
+ * { posts: true }
192
+ *
193
+ * @example
194
+ * // Include a relation with specific fields
195
+ * { posts: { select: { id: true, title: true } } }
196
+ */
197
+ export type IncludeValue = boolean | { select?: Record<string, boolean> };
198
+
199
+ export type SuparismaOptions<
200
+ TWhereInput,
201
+ TOrderByInput,
202
+ TSelectInput = Record<string, boolean>,
203
+ TIncludeInput = Record<never, never>
204
+ > = {
173
205
  /** Whether to enable realtime updates (default: true) */
174
206
  realtime?: boolean;
175
207
  /** Custom channel name for realtime subscription */
@@ -184,6 +216,23 @@ export type SuparismaOptions<TWhereInput, TOrderByInput> = {
184
216
  limit?: number;
185
217
  /** Offset for pagination (skip records) */
186
218
  offset?: number;
219
+ /**
220
+ * Select specific fields to return. Reduces payload size.
221
+ * @example { id: true, name: true, email: true }
222
+ */
223
+ select?: TSelectInput;
224
+ /**
225
+ * Include related records (foreign key relations).
226
+ * @example { posts: true } or { posts: { select: { id: true, title: true } } }
227
+ */
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;
187
236
  };
188
237
 
189
238
  /**
@@ -733,6 +782,90 @@ function matchesFilter<T>(record: any, filter: T): boolean {
733
782
  return conditions.every(condition => condition);
734
783
  }
735
784
 
785
+ /**
786
+ * Build a Supabase select string from select and include options.
787
+ *
788
+ * @param select - Object specifying which fields to select { field: true }
789
+ * @param include - Object specifying which relations to include { relation: true }
790
+ * @returns A Supabase-compatible select string
791
+ *
792
+ * @example
793
+ * // Select specific fields
794
+ * buildSelectString({ id: true, name: true }) // Returns "id,name"
795
+ *
796
+ * @example
797
+ * // Include relations
798
+ * buildSelectString(undefined, { posts: true }) // Returns "*,posts(*)"
799
+ *
800
+ * @example
801
+ * // Select fields and include relations with specific fields
802
+ * buildSelectString({ id: true, name: true }, { posts: { select: { id: true, title: true } } })
803
+ * // Returns "id,name,posts(id,title)"
804
+ */
805
+ export function buildSelectString<TSelect, TInclude>(
806
+ select?: TSelect,
807
+ include?: TInclude,
808
+ relationMappings?: Record<string, string>
809
+ ): string {
810
+ const parts: string[] = [];
811
+
812
+ // Handle select - if provided, only return specified fields
813
+ if (select && typeof select === 'object') {
814
+ const selectedFields = Object.entries(select)
815
+ .filter(([_, value]) => value === true)
816
+ .map(([key]) => key);
817
+
818
+ if (selectedFields.length > 0) {
819
+ parts.push(...selectedFields);
820
+ }
821
+ }
822
+
823
+ // Handle include - add related records
824
+ if (include && typeof include === 'object') {
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
+
833
+ if (relationValue === true) {
834
+ // Include all fields from the relation
835
+ parts.push(\`\${embedName}(*)\`);
836
+ } else if (typeof relationValue === 'object' && relationValue !== null) {
837
+ // Include specific fields from the relation
838
+ const relationOptions = relationValue as { select?: Record<string, boolean> };
839
+ if (relationOptions.select) {
840
+ const relationFields = Object.entries(relationOptions.select)
841
+ .filter(([_, value]) => value === true)
842
+ .map(([key]) => key);
843
+
844
+ if (relationFields.length > 0) {
845
+ parts.push(\`\${embedName}(\${relationFields.join(',')})\`);
846
+ } else {
847
+ parts.push(\`\${embedName}(*)\`);
848
+ }
849
+ } else {
850
+ parts.push(\`\${embedName}(*)\`);
851
+ }
852
+ }
853
+ }
854
+ }
855
+
856
+ // If no select specified but include is, we need to include base table fields too
857
+ if (parts.length === 0) {
858
+ return '*';
859
+ }
860
+
861
+ // If only include was specified (no select), we need all base fields plus relations
862
+ if (!select && include) {
863
+ return '*,' + parts.join(',');
864
+ }
865
+
866
+ return parts.join(',');
867
+ }
868
+
736
869
  /**
737
870
  * Apply order by to the query builder
738
871
  */
@@ -789,6 +922,7 @@ export function createSuparismaHook<
789
922
  defaultValues?: Record<string, string>;
790
923
  createdAtField?: string;
791
924
  updatedAtField?: string;
925
+ relationMappings?: Record<string, string>;
792
926
  }) {
793
927
  const {
794
928
  tableName,
@@ -797,7 +931,8 @@ export function createSuparismaHook<
797
931
  searchFields = [],
798
932
  defaultValues = {},
799
933
  createdAtField = 'createdAt',
800
- updatedAtField = 'updatedAt'
934
+ updatedAtField = 'updatedAt',
935
+ relationMappings = {}
801
936
  } = config;
802
937
 
803
938
  /**
@@ -828,13 +963,21 @@ export function createSuparismaHook<
828
963
  orderBy,
829
964
  limit,
830
965
  offset,
966
+ select,
967
+ include,
968
+ enabled = true,
831
969
  } = options;
832
970
 
971
+ // Build the select string once for reuse
972
+ const selectString = buildSelectString(select, include, relationMappings);
973
+
833
974
  // Refs to store the latest options for realtime handlers
834
975
  const whereRef = useRef(where);
835
976
  const orderByRef = useRef(orderBy);
836
977
  const limitRef = useRef(limit);
837
978
  const offsetRef = useRef(offset);
979
+ const selectStringRef = useRef(selectString);
980
+ const enabledRef = useRef(enabled);
838
981
 
839
982
  // Update refs whenever options change
840
983
  useEffect(() => {
@@ -853,6 +996,14 @@ export function createSuparismaHook<
853
996
  offsetRef.current = offset;
854
997
  }, [offset]);
855
998
 
999
+ useEffect(() => {
1000
+ selectStringRef.current = selectString;
1001
+ }, [selectString]);
1002
+
1003
+ useEffect(() => {
1004
+ enabledRef.current = enabled;
1005
+ }, [enabled]);
1006
+
856
1007
  // Single data collection for holding results
857
1008
  const [data, setData] = useState<TWithRelations[]>([]);
858
1009
  const [error, setError] = useState<Error | null>(null);
@@ -873,7 +1024,8 @@ export function createSuparismaHook<
873
1024
  // Function to fetch the total count from Supabase with current filters
874
1025
  const fetchTotalCount = useCallback(async () => {
875
1026
  try {
876
- // Skip count updates during search
1027
+ // Skip count updates when disabled or during search
1028
+ if (!enabledRef.current) return;
877
1029
  if (isSearchingRef.current) return;
878
1030
 
879
1031
  let countQuery = supabase.from(tableName).select('*', { count: 'exact', head: true });
@@ -1198,7 +1350,8 @@ export function createSuparismaHook<
1198
1350
  setLoading(true);
1199
1351
  setError(null);
1200
1352
 
1201
- let query = supabase.from(tableName).select('*');
1353
+ // Use selectString for field selection (includes relations if specified)
1354
+ let query = supabase.from(tableName).select(selectString);
1202
1355
 
1203
1356
  // Apply where conditions if provided
1204
1357
  if (params?.where) {
@@ -1285,7 +1438,7 @@ export function createSuparismaHook<
1285
1438
 
1286
1439
  const { data, error } = await supabase
1287
1440
  .from(tableName)
1288
- .select('*')
1441
+ .select(selectString)
1289
1442
  .eq(primaryKey, value)
1290
1443
  .maybeSingle();
1291
1444
 
@@ -1303,7 +1456,8 @@ export function createSuparismaHook<
1303
1456
 
1304
1457
  // Set up realtime subscription for the list - ONCE and listen to ALL events
1305
1458
  useEffect(() => {
1306
- if (!realtime) return;
1459
+ // Skip subscription if not enabled or realtime is off
1460
+ if (!enabled || !realtime) return;
1307
1461
 
1308
1462
  // Clean up previous subscription if it exists
1309
1463
  if (channelRef.current) {
@@ -1605,10 +1759,10 @@ export function createSuparismaHook<
1605
1759
  searchTimeoutRef.current = null;
1606
1760
  }
1607
1761
  };
1608
- }, [realtime, channelName, tableName]); // NEVER include 'where' - subscription should persist
1762
+ }, [realtime, channelName, tableName, enabled]); // NEVER include 'where' - subscription should persist
1609
1763
 
1610
1764
  // Create a memoized options object to prevent unnecessary re-renders
1611
- const optionsRef = useRef({ where, orderBy, limit, offset });
1765
+ const optionsRef = useRef({ where, orderBy, limit, offset, selectString });
1612
1766
 
1613
1767
  // Compare current options with previous options
1614
1768
  const optionsChanged = useCallback(() => {
@@ -1623,19 +1777,26 @@ export function createSuparismaHook<
1623
1777
  whereStr !== prevWhereStr ||
1624
1778
  orderByStr !== prevOrderByStr ||
1625
1779
  limit !== optionsRef.current.limit ||
1626
- offset !== optionsRef.current.offset;
1780
+ offset !== optionsRef.current.offset ||
1781
+ selectString !== optionsRef.current.selectString;
1627
1782
 
1628
1783
  if (hasChanged) {
1629
1784
  // Update the ref with the new values
1630
- optionsRef.current = { where, orderBy, limit, offset };
1785
+ optionsRef.current = { where, orderBy, limit, offset, selectString };
1631
1786
  return true;
1632
1787
  }
1633
1788
 
1634
1789
  return false;
1635
- }, [where, orderBy, limit, offset]);
1790
+ }, [where, orderBy, limit, offset, selectString]);
1636
1791
 
1637
1792
  // Load initial data and refetch when options change (BUT NEVER TOUCH SUBSCRIPTION)
1638
1793
  useEffect(() => {
1794
+ // Skip fetching if not enabled
1795
+ if (!enabled) {
1796
+ setLoading(false); // Don't show loading spinner when disabled
1797
+ return;
1798
+ }
1799
+
1639
1800
  // Skip if search is active
1640
1801
  if (isSearchingRef.current) return;
1641
1802
 
@@ -1668,7 +1829,31 @@ export function createSuparismaHook<
1668
1829
 
1669
1830
  // Initial count fetch
1670
1831
  fetchTotalCount();
1671
- }, [findMany, where, orderBy, limit, offset, optionsChanged, fetchTotalCount]);
1832
+ }, [findMany, where, orderBy, limit, offset, optionsChanged, fetchTotalCount, enabled]);
1833
+
1834
+ // Track previous enabled state to detect changes from false to true
1835
+ const prevEnabledRef = useRef(enabled);
1836
+
1837
+ // Fetch when enabled changes from false to true
1838
+ useEffect(() => {
1839
+ const wasDisabled = !prevEnabledRef.current;
1840
+ const isNowEnabled = enabled;
1841
+
1842
+ // Update the previous value
1843
+ prevEnabledRef.current = enabled;
1844
+
1845
+ // If we just became enabled and have already done initial load, refetch
1846
+ if (wasDisabled && isNowEnabled && initialLoadRef.current) {
1847
+ console.log(\`Hook enabled for \${tableName}, fetching data\`);
1848
+ findMany({
1849
+ where,
1850
+ orderBy,
1851
+ take: limit,
1852
+ skip: offset
1853
+ });
1854
+ fetchTotalCount();
1855
+ }
1856
+ }, [enabled, findMany, where, orderBy, limit, offset, fetchTotalCount]);
1672
1857
 
1673
1858
  /**
1674
1859
  * Create a new record with the provided data.
@@ -1756,7 +1941,7 @@ export function createSuparismaHook<
1756
1941
  const { data: result, error } = await supabase
1757
1942
  .from(tableName)
1758
1943
  .insert([itemWithDefaults])
1759
- .select();
1944
+ .select(selectString);
1760
1945
 
1761
1946
  if (error) throw error;
1762
1947
 
@@ -1848,7 +2033,7 @@ export function createSuparismaHook<
1848
2033
  .from(tableName)
1849
2034
  .update(itemWithDefaults)
1850
2035
  .eq(primaryKey, value)
1851
- .select();
2036
+ .select(selectString);
1852
2037
 
1853
2038
  if (error) throw error;
1854
2039
 
@@ -1903,7 +2088,7 @@ export function createSuparismaHook<
1903
2088
  // First fetch the record to return it
1904
2089
  const { data: recordToDelete } = await supabase
1905
2090
  .from(tableName)
1906
- .select('*')
2091
+ .select(selectString)
1907
2092
  .eq(primaryKey, value)
1908
2093
  .maybeSingle();
1909
2094
 
@@ -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
@@ -42,6 +46,8 @@ import type {
42
46
  ${modelName}WhereInput,
43
47
  ${modelName}WhereUniqueInput,
44
48
  ${modelName}OrderByInput,
49
+ ${modelName}SelectInput,
50
+ ${modelName}IncludeInput,
45
51
  ${modelName}HookApi,
46
52
  Use${modelName}Options
47
53
  } from '../types/${modelName}Types';
@@ -92,6 +98,18 @@ import type {
92
98
  * orderBy: { // ordering },
93
99
  * take: 20 // limit
94
100
  * });
101
+ *
102
+ * @example
103
+ * // Select specific fields only
104
+ * const ${modelName.toLowerCase()} = ${config_1.HOOK_NAME_PREFIX}${modelName}({
105
+ * select: { id: true, name: true }
106
+ * });
107
+ *
108
+ * @example
109
+ * // Include related records
110
+ * const ${modelName.toLowerCase()} = ${config_1.HOOK_NAME_PREFIX}${modelName}({
111
+ * include: { relatedModel: true }
112
+ * });
95
113
  */
96
114
  export const ${config_1.HOOK_NAME_PREFIX}${modelName} = createSuparismaHook<
97
115
  ${modelName}WithRelations,
@@ -104,7 +122,7 @@ export const ${config_1.HOOK_NAME_PREFIX}${modelName} = createSuparismaHook<
104
122
  >({
105
123
  tableName: '${tableName}',
106
124
  hasCreatedAt: ${hasCreatedAt},
107
- hasUpdatedAt: ${hasUpdatedAt}${searchConfig}${defaultValuesConfig}${fieldNamesConfig}
125
+ hasUpdatedAt: ${hasUpdatedAt}${searchConfig}${defaultValuesConfig}${fieldNamesConfig}${relationMappingsConfig}
108
126
  }) as unknown as (options?: Use${modelName}Options) => ${modelName}HookApi;
109
127
  `;
110
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) && !foreignKeyFields.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,7 +371,28 @@ export type ${modelName}WhereUniqueInput = {
349
371
  * });
350
372
  */
351
373
  export type ${modelName}OrderByInput = {
352
- [key in keyof ${modelName}WithRelations]?: 'asc' | 'desc';
374
+ [key in keyof ${modelName}SelectInput]?: 'asc' | 'desc';
375
+ };
376
+
377
+ /**
378
+ * Select specific scalar fields to return from ${modelName} queries.
379
+ * Relation fields are intentionally excluded; use \`include\` for relations.
380
+ */
381
+ export type ${modelName}SelectInput = {
382
+ ${model.fields
383
+ .filter((f) => !relationObjectFields.includes(f.name))
384
+ .map((f) => ` ${f.name}?: boolean;`)
385
+ .join('\n')}
386
+ };
387
+
388
+ /**
389
+ * Include related records when querying ${modelName}.
390
+ * Only real Prisma relation fields are allowed here.
391
+ */
392
+ export type ${modelName}IncludeInput = {
393
+ ${relationFields
394
+ .map((f) => ` ${f.name}?: IncludeValue | { select?: ${f.type}SelectInput };`)
395
+ .join('\n')}
353
396
  };
354
397
 
355
398
  /**
@@ -364,8 +407,14 @@ export type ${modelName}ManyResult = ModelResult<${modelName}WithRelations[]>;
364
407
 
365
408
  /**
366
409
  * Configuration options for the ${modelName} hook.
410
+ * Includes where filters, ordering, pagination, and field selection.
367
411
  */
368
- export type Use${modelName}Options = SuparismaOptions<${modelName}WhereInput, ${modelName}OrderByInput>;
412
+ export type Use${modelName}Options = SuparismaOptions<
413
+ ${modelName}WhereInput,
414
+ ${modelName}OrderByInput,
415
+ ${modelName}SelectInput,
416
+ ${modelName}IncludeInput
417
+ >;
369
418
 
370
419
  /**
371
420
  * The complete API for interacting with ${modelName} records.
@@ -688,6 +737,7 @@ ${createInputProps
688
737
  defaultValues: Object.keys(defaultValues).length > 0 ? defaultValues : undefined,
689
738
  createdAtField,
690
739
  updatedAtField,
740
+ relationMappings: Object.keys(relationMappings).length > 0 ? relationMappings : undefined,
691
741
  zodImports: model.zodImports // Pass through zod imports
692
742
  };
693
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
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "suparisma",
3
- "version": "1.2.2",
3
+ "version": "1.2.6",
4
4
  "description": "Opinionated typesafe React realtime CRUD hooks generator for all your Supabase tables, powered by Prisma. Works with Next.js, Remix, React Native, and Expo.",
5
5
  "main": "dist/index.js",
6
6
  "repository": {