suparisma 1.1.1 → 1.2.1
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 +439 -14
- package/SEARCH_FEATURES.md +430 -0
- package/dist/generators/coreGenerator.js +242 -70
- package/dist/generators/typeGenerator.js +5 -22
- package/dist/index.js +206 -15
- package/dist/parser.js +47 -37
- package/package.json +1 -1
- package/prisma/schema.prisma +6 -1
|
@@ -23,9 +23,13 @@ import { supabase } from './supabase-client';
|
|
|
23
23
|
* @example
|
|
24
24
|
* // Search for users with names containing "john"
|
|
25
25
|
* const query = { field: "name", value: "john" };
|
|
26
|
+
*
|
|
27
|
+
* @example
|
|
28
|
+
* // Search across multiple fields
|
|
29
|
+
* const query = { field: "multi", value: "john" };
|
|
26
30
|
*/
|
|
27
31
|
export type SearchQuery = {
|
|
28
|
-
/** The field name to search in */
|
|
32
|
+
/** The field name to search in, or "multi" for multi-field search */
|
|
29
33
|
field: string;
|
|
30
34
|
/** The search term/value to look for */
|
|
31
35
|
value: string;
|
|
@@ -34,6 +38,15 @@ export type SearchQuery = {
|
|
|
34
38
|
// Define type for Supabase query builder
|
|
35
39
|
export type SupabaseQueryBuilder = ReturnType<ReturnType<typeof supabase.from>['select']>;
|
|
36
40
|
|
|
41
|
+
/**
|
|
42
|
+
* Utility function to escape regex special characters for safe RegExp usage
|
|
43
|
+
* Prevents "Invalid regular expression" errors when search terms contain special characters
|
|
44
|
+
*/
|
|
45
|
+
export function escapeRegexCharacters(str: string): string {
|
|
46
|
+
// Escape all special regex characters: ( ) [ ] { } + * ? ^ $ | . \\
|
|
47
|
+
return str.replace(/[()\\[\\]{}+*?^$|.\\\\]/g, '\\\\\\\\$&');
|
|
48
|
+
}
|
|
49
|
+
|
|
37
50
|
/**
|
|
38
51
|
* Advanced filter operators for complex queries
|
|
39
52
|
* @example
|
|
@@ -162,10 +175,22 @@ export type ModelResult<T> = Promise<{
|
|
|
162
175
|
* users.search.addQuery({ field: "name", value: "john" });
|
|
163
176
|
*
|
|
164
177
|
* @example
|
|
178
|
+
* // Search across multiple fields
|
|
179
|
+
* users.search.searchMultiField("john doe");
|
|
180
|
+
*
|
|
181
|
+
* @example
|
|
165
182
|
* // Check if search is loading
|
|
166
183
|
* if (users.search.loading) {
|
|
167
184
|
* return <div>Searching...</div>;
|
|
168
185
|
* }
|
|
186
|
+
*
|
|
187
|
+
* @example
|
|
188
|
+
* // Get current search terms for highlighting
|
|
189
|
+
* const searchTerms = users.search.getCurrentSearchTerms();
|
|
190
|
+
*
|
|
191
|
+
* @example
|
|
192
|
+
* // Safely escape regex characters
|
|
193
|
+
* const escaped = users.search.escapeRegex("user@example.com");
|
|
169
194
|
*/
|
|
170
195
|
export type SearchState = {
|
|
171
196
|
/** Current active search queries */
|
|
@@ -180,6 +205,14 @@ export type SearchState = {
|
|
|
180
205
|
removeQuery: (field: string) => void;
|
|
181
206
|
/** Clear all search queries and return to normal data fetching */
|
|
182
207
|
clearQueries: () => void;
|
|
208
|
+
/** Search across multiple fields (convenience method) */
|
|
209
|
+
searchMultiField: (value: string) => void;
|
|
210
|
+
/** Search in a specific field (convenience method) */
|
|
211
|
+
searchField: (field: string, value: string) => void;
|
|
212
|
+
/** Get current search terms for custom highlighting */
|
|
213
|
+
getCurrentSearchTerms: () => string[];
|
|
214
|
+
/** Safely escape regex special characters */
|
|
215
|
+
escapeRegex: (text: string) => string;
|
|
183
216
|
};
|
|
184
217
|
|
|
185
218
|
/**
|
|
@@ -248,19 +281,23 @@ export function buildFilterString<T>(where?: T): string | undefined {
|
|
|
248
281
|
}
|
|
249
282
|
|
|
250
283
|
if ('gt' in advancedOps && advancedOps.gt !== undefined) {
|
|
251
|
-
|
|
284
|
+
const value = advancedOps.gt instanceof Date ? advancedOps.gt.toISOString() : advancedOps.gt;
|
|
285
|
+
filters.push(\`\${key}=gt.\${value}\`);
|
|
252
286
|
}
|
|
253
287
|
|
|
254
288
|
if ('gte' in advancedOps && advancedOps.gte !== undefined) {
|
|
255
|
-
|
|
289
|
+
const value = advancedOps.gte instanceof Date ? advancedOps.gte.toISOString() : advancedOps.gte;
|
|
290
|
+
filters.push(\`\${key}=gte.\${value}\`);
|
|
256
291
|
}
|
|
257
292
|
|
|
258
293
|
if ('lt' in advancedOps && advancedOps.lt !== undefined) {
|
|
259
|
-
|
|
294
|
+
const value = advancedOps.lt instanceof Date ? advancedOps.lt.toISOString() : advancedOps.lt;
|
|
295
|
+
filters.push(\`\${key}=lt.\${value}\`);
|
|
260
296
|
}
|
|
261
297
|
|
|
262
298
|
if ('lte' in advancedOps && advancedOps.lte !== undefined) {
|
|
263
|
-
|
|
299
|
+
const value = advancedOps.lte instanceof Date ? advancedOps.lte.toISOString() : advancedOps.lte;
|
|
300
|
+
filters.push(\`\${key}=lte.\${value}\`);
|
|
264
301
|
}
|
|
265
302
|
|
|
266
303
|
if ('in' in advancedOps && advancedOps.in?.length) {
|
|
@@ -345,23 +382,31 @@ function applyConditionGroup<T>(
|
|
|
345
382
|
}
|
|
346
383
|
|
|
347
384
|
if ('gt' in advancedOps && advancedOps.gt !== undefined) {
|
|
385
|
+
// Convert Date objects to ISO strings for Supabase
|
|
386
|
+
const value = advancedOps.gt instanceof Date ? advancedOps.gt.toISOString() : advancedOps.gt;
|
|
348
387
|
// @ts-ignore: Supabase typing issue
|
|
349
|
-
filteredQuery = filteredQuery.gt(key,
|
|
388
|
+
filteredQuery = filteredQuery.gt(key, value);
|
|
350
389
|
}
|
|
351
390
|
|
|
352
391
|
if ('gte' in advancedOps && advancedOps.gte !== undefined) {
|
|
392
|
+
// Convert Date objects to ISO strings for Supabase
|
|
393
|
+
const value = advancedOps.gte instanceof Date ? advancedOps.gte.toISOString() : advancedOps.gte;
|
|
353
394
|
// @ts-ignore: Supabase typing issue
|
|
354
|
-
filteredQuery = filteredQuery.gte(key,
|
|
395
|
+
filteredQuery = filteredQuery.gte(key, value);
|
|
355
396
|
}
|
|
356
397
|
|
|
357
398
|
if ('lt' in advancedOps && advancedOps.lt !== undefined) {
|
|
399
|
+
// Convert Date objects to ISO strings for Supabase
|
|
400
|
+
const value = advancedOps.lt instanceof Date ? advancedOps.lt.toISOString() : advancedOps.lt;
|
|
358
401
|
// @ts-ignore: Supabase typing issue
|
|
359
|
-
filteredQuery = filteredQuery.lt(key,
|
|
402
|
+
filteredQuery = filteredQuery.lt(key, value);
|
|
360
403
|
}
|
|
361
404
|
|
|
362
405
|
if ('lte' in advancedOps && advancedOps.lte !== undefined) {
|
|
406
|
+
// Convert Date objects to ISO strings for Supabase
|
|
407
|
+
const value = advancedOps.lte instanceof Date ? advancedOps.lte.toISOString() : advancedOps.lte;
|
|
363
408
|
// @ts-ignore: Supabase typing issue
|
|
364
|
-
filteredQuery = filteredQuery.lte(key,
|
|
409
|
+
filteredQuery = filteredQuery.lte(key, value);
|
|
365
410
|
}
|
|
366
411
|
|
|
367
412
|
if ('in' in advancedOps && advancedOps.in?.length) {
|
|
@@ -458,13 +503,17 @@ export function applyFilter<T>(
|
|
|
458
503
|
} else if ('not' in advancedOps && advancedOps.not !== undefined) {
|
|
459
504
|
orFilters.push(\`\${key}.neq.\${advancedOps.not}\`);
|
|
460
505
|
} else if ('gt' in advancedOps && advancedOps.gt !== undefined) {
|
|
461
|
-
|
|
506
|
+
const value = advancedOps.gt instanceof Date ? advancedOps.gt.toISOString() : advancedOps.gt;
|
|
507
|
+
orFilters.push(\`\${key}.gt.\${value}\`);
|
|
462
508
|
} else if ('gte' in advancedOps && advancedOps.gte !== undefined) {
|
|
463
|
-
|
|
509
|
+
const value = advancedOps.gte instanceof Date ? advancedOps.gte.toISOString() : advancedOps.gte;
|
|
510
|
+
orFilters.push(\`\${key}.gte.\${value}\`);
|
|
464
511
|
} else if ('lt' in advancedOps && advancedOps.lt !== undefined) {
|
|
465
|
-
|
|
512
|
+
const value = advancedOps.lt instanceof Date ? advancedOps.lt.toISOString() : advancedOps.lt;
|
|
513
|
+
orFilters.push(\`\${key}.lt.\${value}\`);
|
|
466
514
|
} else if ('lte' in advancedOps && advancedOps.lte !== undefined) {
|
|
467
|
-
|
|
515
|
+
const value = advancedOps.lte instanceof Date ? advancedOps.lte.toISOString() : advancedOps.lte;
|
|
516
|
+
orFilters.push(\`\${key}.lte.\${value}\`);
|
|
468
517
|
} else if ('in' in advancedOps && advancedOps.in?.length) {
|
|
469
518
|
orFilters.push(\`\${key}.in.(\${advancedOps.in.join(',')})\`);
|
|
470
519
|
} else if ('contains' in advancedOps && advancedOps.contains !== undefined) {
|
|
@@ -528,6 +577,18 @@ function matchesFilter<T>(record: any, filter: T): boolean {
|
|
|
528
577
|
}
|
|
529
578
|
}
|
|
530
579
|
|
|
580
|
+
// Helper function to convert values to comparable format for date/time comparisons
|
|
581
|
+
const getComparableValue = (value: any): any => {
|
|
582
|
+
if (value instanceof Date) {
|
|
583
|
+
return value.getTime();
|
|
584
|
+
}
|
|
585
|
+
if (typeof value === 'string' && value.match(/^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}/)) {
|
|
586
|
+
// ISO date string
|
|
587
|
+
return new Date(value).getTime();
|
|
588
|
+
}
|
|
589
|
+
return value;
|
|
590
|
+
};
|
|
591
|
+
|
|
531
592
|
// Helper function to check individual field conditions
|
|
532
593
|
const checkFieldConditions = (conditions: any): boolean => {
|
|
533
594
|
for (const [key, value] of Object.entries(conditions)) {
|
|
@@ -547,19 +608,27 @@ function matchesFilter<T>(record: any, filter: T): boolean {
|
|
|
547
608
|
}
|
|
548
609
|
|
|
549
610
|
if ('gt' in advancedOps && advancedOps.gt !== undefined) {
|
|
550
|
-
|
|
611
|
+
const recordComparable = getComparableValue(recordValue);
|
|
612
|
+
const filterComparable = getComparableValue(advancedOps.gt);
|
|
613
|
+
if (!(recordComparable > filterComparable)) return false;
|
|
551
614
|
}
|
|
552
615
|
|
|
553
616
|
if ('gte' in advancedOps && advancedOps.gte !== undefined) {
|
|
554
|
-
|
|
617
|
+
const recordComparable = getComparableValue(recordValue);
|
|
618
|
+
const filterComparable = getComparableValue(advancedOps.gte);
|
|
619
|
+
if (!(recordComparable >= filterComparable)) return false;
|
|
555
620
|
}
|
|
556
621
|
|
|
557
622
|
if ('lt' in advancedOps && advancedOps.lt !== undefined) {
|
|
558
|
-
|
|
623
|
+
const recordComparable = getComparableValue(recordValue);
|
|
624
|
+
const filterComparable = getComparableValue(advancedOps.lt);
|
|
625
|
+
if (!(recordComparable < filterComparable)) return false;
|
|
559
626
|
}
|
|
560
627
|
|
|
561
628
|
if ('lte' in advancedOps && advancedOps.lte !== undefined) {
|
|
562
|
-
|
|
629
|
+
const recordComparable = getComparableValue(recordValue);
|
|
630
|
+
const filterComparable = getComparableValue(advancedOps.lte);
|
|
631
|
+
if (!(recordComparable <= filterComparable)) return false;
|
|
563
632
|
}
|
|
564
633
|
|
|
565
634
|
if ('in' in advancedOps && advancedOps.in?.length) {
|
|
@@ -861,7 +930,39 @@ export function createSuparismaHook<
|
|
|
861
930
|
setSearchQueries([]);
|
|
862
931
|
isSearchingRef.current = false;
|
|
863
932
|
findMany({ where, orderBy, take: limit, skip: offset });
|
|
864
|
-
}, [where, orderBy, limit, offset])
|
|
933
|
+
}, [where, orderBy, limit, offset]),
|
|
934
|
+
|
|
935
|
+
// Search across multiple fields (convenience method)
|
|
936
|
+
searchMultiField: useCallback((value: string) => {
|
|
937
|
+
if (searchFields.length <= 1) {
|
|
938
|
+
console.warn('Multi-field search requires at least 2 searchable fields');
|
|
939
|
+
return;
|
|
940
|
+
}
|
|
941
|
+
|
|
942
|
+
setSearchQueries([{ field: 'multi', value }]);
|
|
943
|
+
executeSearch([{ field: 'multi', value }]);
|
|
944
|
+
}, [searchFields.length]),
|
|
945
|
+
|
|
946
|
+
// Search in a specific field (convenience method)
|
|
947
|
+
searchField: useCallback((field: string, value: string) => {
|
|
948
|
+
if (!searchFields.includes(field)) {
|
|
949
|
+
console.warn(\`Field "\${field}" is not searchable. Available fields: \${searchFields.join(', ')}\`);
|
|
950
|
+
return;
|
|
951
|
+
}
|
|
952
|
+
|
|
953
|
+
setSearchQueries([{ field, value }]);
|
|
954
|
+
executeSearch([{ field, value }]);
|
|
955
|
+
}, [searchFields]),
|
|
956
|
+
|
|
957
|
+
// Get current search terms for custom highlighting
|
|
958
|
+
getCurrentSearchTerms: useCallback(() => {
|
|
959
|
+
return searchQueries.map(q => q.value.trim());
|
|
960
|
+
}, [searchQueries]),
|
|
961
|
+
|
|
962
|
+
// Safely escape regex special characters
|
|
963
|
+
escapeRegex: useCallback((text: string) => {
|
|
964
|
+
return escapeRegexCharacters(text);
|
|
965
|
+
}, [])
|
|
865
966
|
};
|
|
866
967
|
|
|
867
968
|
// Execute search based on queries
|
|
@@ -884,13 +985,52 @@ export function createSuparismaHook<
|
|
|
884
985
|
try {
|
|
885
986
|
let results: TWithRelations[] = [];
|
|
886
987
|
|
|
988
|
+
// Validate search queries
|
|
989
|
+
const validQueries = queries.filter(query => {
|
|
990
|
+
if (!query.field || !query.value) {
|
|
991
|
+
console.warn('Invalid search query - missing field or value:', query);
|
|
992
|
+
return false;
|
|
993
|
+
}
|
|
994
|
+
// Allow "multi" as a special field for multi-field search
|
|
995
|
+
if (query.field === 'multi' && searchFields.length > 1) {
|
|
996
|
+
return true;
|
|
997
|
+
}
|
|
998
|
+
if (!searchFields.includes(query.field)) {
|
|
999
|
+
console.warn(\`Field "\${query.field}" is not searchable. Available fields: \${searchFields.join(', ')}, or "multi" for multi-field search\`);
|
|
1000
|
+
return false;
|
|
1001
|
+
}
|
|
1002
|
+
return true;
|
|
1003
|
+
});
|
|
1004
|
+
|
|
1005
|
+
if (validQueries.length === 0) {
|
|
1006
|
+
console.log('No valid search queries found');
|
|
1007
|
+
setData([]);
|
|
1008
|
+
setCount(0);
|
|
1009
|
+
return;
|
|
1010
|
+
}
|
|
1011
|
+
|
|
887
1012
|
// Execute RPC function for each query using Promise.all
|
|
888
|
-
const searchPromises =
|
|
889
|
-
// Build function name
|
|
890
|
-
const functionName =
|
|
1013
|
+
const searchPromises = validQueries.map(query => {
|
|
1014
|
+
// Build function name based on field type
|
|
1015
|
+
const functionName = query.field === 'multi'
|
|
1016
|
+
? \`search_\${tableName.toLowerCase()}_multi_field\`
|
|
1017
|
+
: \`search_\${tableName.toLowerCase()}_by_\${query.field.toLowerCase()}_prefix\`;
|
|
1018
|
+
|
|
1019
|
+
console.log(\`🔍 Executing search: \${functionName}(search_prefix: "\${query.value.trim()}")\`);
|
|
891
1020
|
|
|
892
|
-
// Call RPC function
|
|
893
|
-
return supabase.rpc(functionName, {
|
|
1021
|
+
// Call RPC function with proper error handling
|
|
1022
|
+
return Promise.resolve(supabase.rpc(functionName, { search_prefix: query.value.trim() }))
|
|
1023
|
+
.then((result: any) => ({
|
|
1024
|
+
...result,
|
|
1025
|
+
queryField: query.field,
|
|
1026
|
+
queryValue: query.value
|
|
1027
|
+
}))
|
|
1028
|
+
.catch((error: any) => ({
|
|
1029
|
+
data: null,
|
|
1030
|
+
error: error,
|
|
1031
|
+
queryField: query.field,
|
|
1032
|
+
queryValue: query.value
|
|
1033
|
+
}));
|
|
894
1034
|
});
|
|
895
1035
|
|
|
896
1036
|
// Execute all search queries in parallel
|
|
@@ -898,82 +1038,88 @@ export function createSuparismaHook<
|
|
|
898
1038
|
|
|
899
1039
|
// Combine and deduplicate results
|
|
900
1040
|
const allResults: Record<string, TWithRelations> = {};
|
|
1041
|
+
let hasErrors = false;
|
|
901
1042
|
|
|
902
1043
|
// Process each search result
|
|
903
|
-
searchResults.forEach((result, index) => {
|
|
1044
|
+
searchResults.forEach((result: any, index: number) => {
|
|
904
1045
|
if (result.error) {
|
|
905
|
-
console.error(
|
|
1046
|
+
console.error(\`🔍 Search error for field "\${result.queryField}" with value "\${result.queryValue}":\`, result.error);
|
|
1047
|
+
hasErrors = true;
|
|
906
1048
|
return;
|
|
907
1049
|
}
|
|
908
1050
|
|
|
909
|
-
if (result.data) {
|
|
1051
|
+
if (result.data && Array.isArray(result.data)) {
|
|
1052
|
+
console.log(\`🔍 Search results for "\${result.queryField}": \${result.data.length} items\`);
|
|
1053
|
+
|
|
910
1054
|
// Add each result, using id as key to deduplicate
|
|
911
1055
|
for (const item of result.data as TWithRelations[]) {
|
|
912
1056
|
// @ts-ignore: Assume item has an id property
|
|
913
|
-
if (item.id) {
|
|
1057
|
+
if (item && typeof item === 'object' && 'id' in item && item.id) {
|
|
914
1058
|
// @ts-ignore: Add to results using id as key
|
|
915
1059
|
allResults[item.id] = item;
|
|
916
1060
|
}
|
|
917
1061
|
}
|
|
1062
|
+
} else if (result.data) {
|
|
1063
|
+
console.warn(\`🔍 Unexpected search result format for "\${result.queryField}":\`, typeof result.data);
|
|
918
1064
|
}
|
|
919
1065
|
});
|
|
920
1066
|
|
|
921
1067
|
// Convert back to array
|
|
922
1068
|
results = Object.values(allResults);
|
|
1069
|
+
console.log(\`🔍 Combined search results: \${results.length} unique items\`);
|
|
923
1070
|
|
|
924
|
-
// Apply any where conditions client-side
|
|
1071
|
+
// Apply any where conditions client-side (now using the proper filter function)
|
|
925
1072
|
if (where) {
|
|
926
|
-
|
|
927
|
-
|
|
928
|
-
|
|
929
|
-
// Skip complex filters for now
|
|
930
|
-
continue;
|
|
931
|
-
}
|
|
932
|
-
|
|
933
|
-
if (item[key as keyof typeof item] !== value) {
|
|
934
|
-
return false;
|
|
935
|
-
}
|
|
936
|
-
}
|
|
937
|
-
return true;
|
|
938
|
-
});
|
|
1073
|
+
const originalCount = results.length;
|
|
1074
|
+
results = results.filter((item) => matchesFilter(item, where));
|
|
1075
|
+
console.log(\`🔍 After applying where filter: \${results.length}/\${originalCount} items\`);
|
|
939
1076
|
}
|
|
940
1077
|
|
|
941
1078
|
// Set count directly for search results
|
|
942
1079
|
setCount(results.length);
|
|
943
1080
|
|
|
944
|
-
// Apply ordering if needed
|
|
1081
|
+
// Apply ordering if needed (using the proper compare function)
|
|
945
1082
|
if (orderBy) {
|
|
946
|
-
const
|
|
947
|
-
if (orderEntries.length > 0) {
|
|
948
|
-
const [orderField, direction] = orderEntries[0] || [];
|
|
1083
|
+
const orderByArray = Array.isArray(orderBy) ? orderBy : [orderBy];
|
|
949
1084
|
results = [...results].sort((a, b) => {
|
|
950
|
-
|
|
951
|
-
const
|
|
1085
|
+
for (const orderByClause of orderByArray) {
|
|
1086
|
+
for (const [field, direction] of Object.entries(orderByClause)) {
|
|
1087
|
+
const aValue = a[field as keyof typeof a];
|
|
1088
|
+
const bValue = b[field as keyof typeof b];
|
|
952
1089
|
|
|
953
|
-
|
|
954
|
-
|
|
955
|
-
|
|
956
|
-
return aValue > bValue ? -1 : aValue < bValue ? 1 : 0;
|
|
1090
|
+
if (aValue === bValue) continue;
|
|
1091
|
+
|
|
1092
|
+
return compareValues(aValue, bValue, direction as 'asc' | 'desc');
|
|
957
1093
|
}
|
|
958
|
-
}
|
|
959
|
-
|
|
1094
|
+
}
|
|
1095
|
+
return 0;
|
|
1096
|
+
});
|
|
960
1097
|
}
|
|
961
1098
|
|
|
962
1099
|
// Apply pagination if needed
|
|
963
1100
|
let paginatedResults = results;
|
|
964
|
-
if (limit && limit > 0) {
|
|
965
|
-
paginatedResults = results.slice(0, limit);
|
|
966
|
-
}
|
|
967
|
-
|
|
968
1101
|
if (offset && offset > 0) {
|
|
969
1102
|
paginatedResults = paginatedResults.slice(offset);
|
|
970
1103
|
}
|
|
971
1104
|
|
|
1105
|
+
if (limit && limit > 0) {
|
|
1106
|
+
paginatedResults = paginatedResults.slice(0, limit);
|
|
1107
|
+
}
|
|
1108
|
+
|
|
1109
|
+
console.log(\`🔍 Final search results: \${paginatedResults.length} items (total: \${results.length})\`);
|
|
1110
|
+
|
|
972
1111
|
// Update data with search results
|
|
973
1112
|
setData(paginatedResults);
|
|
1113
|
+
|
|
1114
|
+
// Show error if there were issues but still show partial results
|
|
1115
|
+
if (hasErrors && results.length === 0) {
|
|
1116
|
+
setError(new Error('Search failed - please check if search functions are properly configured'));
|
|
1117
|
+
}
|
|
974
1118
|
} catch (err) {
|
|
975
|
-
console.error('Search error:', err);
|
|
1119
|
+
console.error('🔍 Search error:', err);
|
|
976
1120
|
setError(err as Error);
|
|
1121
|
+
setData([]);
|
|
1122
|
+
setCount(0);
|
|
977
1123
|
} finally {
|
|
978
1124
|
setSearchLoading(false);
|
|
979
1125
|
}
|
|
@@ -1518,7 +1664,20 @@ export function createSuparismaHook<
|
|
|
1518
1664
|
setLoading(true);
|
|
1519
1665
|
setError(null);
|
|
1520
1666
|
|
|
1521
|
-
const now = new Date()
|
|
1667
|
+
const now = new Date();
|
|
1668
|
+
|
|
1669
|
+
// Helper function to convert Date objects to ISO strings for database
|
|
1670
|
+
const convertDatesForDatabase = (obj: any): any => {
|
|
1671
|
+
const result: any = {};
|
|
1672
|
+
for (const [key, value] of Object.entries(obj)) {
|
|
1673
|
+
if (value instanceof Date) {
|
|
1674
|
+
result[key] = value.toISOString();
|
|
1675
|
+
} else {
|
|
1676
|
+
result[key] = value;
|
|
1677
|
+
}
|
|
1678
|
+
}
|
|
1679
|
+
return result;
|
|
1680
|
+
};
|
|
1522
1681
|
|
|
1523
1682
|
// Apply default values from schema
|
|
1524
1683
|
const appliedDefaults: Record<string, any> = {};
|
|
@@ -1529,7 +1688,7 @@ export function createSuparismaHook<
|
|
|
1529
1688
|
if (!(field in data)) {
|
|
1530
1689
|
// Parse the default value based on its type
|
|
1531
1690
|
if (defaultValue.includes('now()') || defaultValue.includes('now')) {
|
|
1532
|
-
appliedDefaults[field] = now;
|
|
1691
|
+
appliedDefaults[field] = now.toISOString(); // Database expects ISO string
|
|
1533
1692
|
} else if (defaultValue.includes('uuid()') || defaultValue.includes('uuid')) {
|
|
1534
1693
|
appliedDefaults[field] = crypto.randomUUID();
|
|
1535
1694
|
} else if (defaultValue.includes('cuid()') || defaultValue.includes('cuid')) {
|
|
@@ -1550,13 +1709,13 @@ export function createSuparismaHook<
|
|
|
1550
1709
|
}
|
|
1551
1710
|
}
|
|
1552
1711
|
|
|
1553
|
-
const itemWithDefaults = {
|
|
1712
|
+
const itemWithDefaults = convertDatesForDatabase({
|
|
1554
1713
|
...appliedDefaults, // Apply schema defaults first
|
|
1555
1714
|
...data, // Then user data (overrides defaults)
|
|
1556
|
-
// Use the actual field names from Prisma
|
|
1557
|
-
...(hasCreatedAt ? { [createdAtField]: now } : {}),
|
|
1558
|
-
...(hasUpdatedAt ? { [updatedAtField]: now } : {})
|
|
1559
|
-
};
|
|
1715
|
+
// Use the actual field names from Prisma - convert Date to ISO string for database
|
|
1716
|
+
...(hasCreatedAt ? { [createdAtField]: now.toISOString() } : {}),
|
|
1717
|
+
...(hasUpdatedAt ? { [updatedAtField]: now.toISOString() } : {})
|
|
1718
|
+
});
|
|
1560
1719
|
|
|
1561
1720
|
const { data: result, error } = await supabase
|
|
1562
1721
|
.from(tableName)
|
|
@@ -1624,17 +1783,30 @@ export function createSuparismaHook<
|
|
|
1624
1783
|
throw new Error('A unique identifier is required');
|
|
1625
1784
|
}
|
|
1626
1785
|
|
|
1627
|
-
const now = new Date()
|
|
1786
|
+
const now = new Date();
|
|
1787
|
+
|
|
1788
|
+
// Helper function to convert Date objects to ISO strings for database
|
|
1789
|
+
const convertDatesForDatabase = (obj: any): any => {
|
|
1790
|
+
const result: any = {};
|
|
1791
|
+
for (const [key, value] of Object.entries(obj)) {
|
|
1792
|
+
if (value instanceof Date) {
|
|
1793
|
+
result[key] = value.toISOString();
|
|
1794
|
+
} else {
|
|
1795
|
+
result[key] = value;
|
|
1796
|
+
}
|
|
1797
|
+
}
|
|
1798
|
+
return result;
|
|
1799
|
+
};
|
|
1628
1800
|
|
|
1629
1801
|
// We do not apply default values for updates
|
|
1630
1802
|
// Default values are only for creation, not for updates,
|
|
1631
1803
|
// as existing records already have these values set
|
|
1632
1804
|
|
|
1633
|
-
const itemWithDefaults = {
|
|
1805
|
+
const itemWithDefaults = convertDatesForDatabase({
|
|
1634
1806
|
...params.data,
|
|
1635
|
-
// Use the actual updatedAt field name from Prisma
|
|
1636
|
-
...(hasUpdatedAt ? { [updatedAtField]: now } : {})
|
|
1637
|
-
};
|
|
1807
|
+
// Use the actual updatedAt field name from Prisma - convert Date to ISO string for database
|
|
1808
|
+
...(hasUpdatedAt ? { [updatedAtField]: now.toISOString() } : {})
|
|
1809
|
+
});
|
|
1638
1810
|
|
|
1639
1811
|
const { data, error } = await supabase
|
|
1640
1812
|
.from(tableName)
|
|
@@ -69,7 +69,7 @@ function generateModelTypesFile(model) {
|
|
|
69
69
|
baseType = 'boolean';
|
|
70
70
|
break;
|
|
71
71
|
case 'DateTime':
|
|
72
|
-
baseType = '
|
|
72
|
+
baseType = 'Date'; // Proper Date type for DateTime fields
|
|
73
73
|
break;
|
|
74
74
|
case 'Json':
|
|
75
75
|
baseType = 'any'; // Or a more specific structured type if available
|
|
@@ -118,33 +118,16 @@ function generateModelTypesFile(model) {
|
|
|
118
118
|
// Generate imports section for zod custom types
|
|
119
119
|
let customImports = '';
|
|
120
120
|
if (model.zodImports && model.zodImports.length > 0) {
|
|
121
|
-
//
|
|
122
|
-
|
|
123
|
-
// Get the zod schemas file path from environment variable
|
|
124
|
-
const zodSchemasPath = process.env.ZOD_SCHEMAS_FILE_PATH || '../commonTypes';
|
|
125
|
-
// Add custom imports with environment variable path
|
|
126
|
-
customImports += model.zodImports
|
|
127
|
-
.map(zodImport => {
|
|
128
|
-
// Extract the types from the original import statement
|
|
129
|
-
const typeMatch = zodImport.importStatement.match(/import\s+{\s*([^}]+)\s*}\s+from/);
|
|
130
|
-
if (typeMatch) {
|
|
131
|
-
const types = typeMatch[1].trim();
|
|
132
|
-
return `import { ${types} } from '${zodSchemasPath}'`;
|
|
133
|
-
}
|
|
134
|
-
// Fallback to original import if parsing fails
|
|
135
|
-
return zodImport.importStatement;
|
|
136
|
-
})
|
|
137
|
-
.join('\n') + '\n\n';
|
|
138
|
-
// Add type definitions for imported zod schemas if needed
|
|
139
|
-
// This is a simplified approach - you might want to make this more sophisticated
|
|
121
|
+
// For projects without zod dependency, fallback to any types instead of importing zod
|
|
122
|
+
// This prevents TypeScript errors when zod is not installed
|
|
140
123
|
const customTypeDefinitions = model.zodImports
|
|
141
124
|
.flatMap(zodImport => zodImport.types)
|
|
142
125
|
.filter((type, index, array) => array.indexOf(type) === index) // Remove duplicates
|
|
143
126
|
.map(type => {
|
|
144
|
-
// If it ends with 'Schema', create a corresponding type
|
|
127
|
+
// If it ends with 'Schema', create a corresponding type using any instead of zod
|
|
145
128
|
if (type.endsWith('Schema')) {
|
|
146
129
|
const typeName = type.replace('Schema', '');
|
|
147
|
-
return
|
|
130
|
+
return `// Fallback type when zod is not available\nexport type ${typeName} = any;`;
|
|
148
131
|
}
|
|
149
132
|
return '';
|
|
150
133
|
})
|