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.
- package/README.md +51 -2
- package/dist/generators/coreGenerator.js +200 -15
- package/dist/generators/hookGenerator.js +20 -2
- package/dist/generators/typeGenerator.js +55 -5
- package/dist/index.js +6 -1
- 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/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
|
-
|
|
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
|
-
|
|
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
|
|
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)
|
|
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}
|
|
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
|
|
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.
|
|
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": {
|