suparisma 1.0.9 → 1.1.0
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 +1 -212
- package/dist/generators/coreGenerator.js +167 -344
- package/dist/generators/hookGenerator.js +1 -0
- package/dist/generators/indexGenerator.js +1 -0
- package/dist/generators/supabaseClientGenerator.js +1 -0
- package/dist/generators/typeGenerator.js +34 -65
- package/dist/index.js +42 -2
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -23,7 +23,6 @@ A powerful, typesafe React hook generator for Supabase, driven by your Prisma sc
|
|
|
23
23
|
- [Filtering Data](#filtering-data)
|
|
24
24
|
- [⚠️ IMPORTANT: Using Dynamic Filters with React](#️-important-using-dynamic-filters-with-react)
|
|
25
25
|
- [Array Filtering](#array-filtering)
|
|
26
|
-
- [OR/AND Conditions](#orand-conditions)
|
|
27
26
|
- [Sorting Data](#sorting-data)
|
|
28
27
|
- [Pagination](#pagination)
|
|
29
28
|
- [Search Functionality](#search-functionality)
|
|
@@ -62,7 +61,6 @@ Suparisma bridges this gap by:
|
|
|
62
61
|
- 🔄 **Real-time updates by default** for all tables (with opt-out capability)
|
|
63
62
|
- 🔒 **Type-safe interfaces** for all database operations
|
|
64
63
|
- 🔍 **Full-text search** with configurable annotations *(currently under maintenance)*
|
|
65
|
-
- ⚡ **Advanced filtering** with OR/AND conditions for complex queries
|
|
66
64
|
- 🔢 **Pagination and sorting** built into every hook
|
|
67
65
|
- 🧩 **Prisma-like API** that feels familiar if you already use Prisma
|
|
68
66
|
- 📱 **Works with any React framework** including Next.js, Remix, etc.
|
|
@@ -582,215 +580,6 @@ function ProductFilter() {
|
|
|
582
580
|
}
|
|
583
581
|
```
|
|
584
582
|
|
|
585
|
-
### OR/AND Conditions
|
|
586
|
-
|
|
587
|
-
Suparisma supports powerful logical operations to combine multiple filter conditions:
|
|
588
|
-
|
|
589
|
-
#### OR Conditions - Match ANY condition
|
|
590
|
-
|
|
591
|
-
Use `OR` to find records that match **any** of the provided conditions:
|
|
592
|
-
|
|
593
|
-
```tsx
|
|
594
|
-
// Find users with name "John" OR email containing "@admin"
|
|
595
|
-
const { data: users } = useSuparisma.user({
|
|
596
|
-
where: {
|
|
597
|
-
OR: [
|
|
598
|
-
{ name: "John" },
|
|
599
|
-
{ email: { contains: "@admin" } }
|
|
600
|
-
]
|
|
601
|
-
}
|
|
602
|
-
});
|
|
603
|
-
|
|
604
|
-
// Complex OR with multiple field types
|
|
605
|
-
const { data: posts } = useSuparisma.post({
|
|
606
|
-
where: {
|
|
607
|
-
OR: [
|
|
608
|
-
{ title: { contains: "react" } },
|
|
609
|
-
{ tags: { has: ["typescript"] } },
|
|
610
|
-
{ author: { name: "John Doe" } },
|
|
611
|
-
{ publishedAt: { gte: new Date('2024-01-01') } }
|
|
612
|
-
]
|
|
613
|
-
}
|
|
614
|
-
});
|
|
615
|
-
|
|
616
|
-
// OR with array operators
|
|
617
|
-
const { data: products } = useSuparisma.product({
|
|
618
|
-
where: {
|
|
619
|
-
OR: [
|
|
620
|
-
{ categories: { has: ["electronics"] } },
|
|
621
|
-
{ categories: { has: ["gaming"] } },
|
|
622
|
-
{ price: { lt: 100 } }
|
|
623
|
-
]
|
|
624
|
-
}
|
|
625
|
-
});
|
|
626
|
-
```
|
|
627
|
-
|
|
628
|
-
#### AND Conditions - Match ALL conditions
|
|
629
|
-
|
|
630
|
-
Use `AND` for explicit conjunction (though regular object properties are ANDed by default):
|
|
631
|
-
|
|
632
|
-
```tsx
|
|
633
|
-
// Explicit AND conditions
|
|
634
|
-
const { data: premiumUsers } = useSuparisma.user({
|
|
635
|
-
where: {
|
|
636
|
-
AND: [
|
|
637
|
-
{ active: true },
|
|
638
|
-
{ subscriptionTier: "premium" },
|
|
639
|
-
{ lastLoginAt: { gte: new Date('2024-01-01') } }
|
|
640
|
-
]
|
|
641
|
-
}
|
|
642
|
-
});
|
|
643
|
-
|
|
644
|
-
// Mix AND with other conditions
|
|
645
|
-
const { data: qualifiedPosts } = useSuparisma.post({
|
|
646
|
-
where: {
|
|
647
|
-
published: true, // Regular condition (implicit AND)
|
|
648
|
-
AND: [
|
|
649
|
-
{ views: { gte: 1000 } },
|
|
650
|
-
{ likes: { gte: 100 } }
|
|
651
|
-
]
|
|
652
|
-
}
|
|
653
|
-
});
|
|
654
|
-
```
|
|
655
|
-
|
|
656
|
-
#### Combining OR and AND
|
|
657
|
-
|
|
658
|
-
Create complex nested logic by combining OR and AND:
|
|
659
|
-
|
|
660
|
-
```tsx
|
|
661
|
-
// Advanced search: (Premium users OR moderators) AND active
|
|
662
|
-
const { data: privilegedUsers } = useSuparisma.user({
|
|
663
|
-
where: {
|
|
664
|
-
active: true, // Must be active
|
|
665
|
-
OR: [
|
|
666
|
-
{ role: "premium" },
|
|
667
|
-
{ role: "moderator" }
|
|
668
|
-
]
|
|
669
|
-
}
|
|
670
|
-
});
|
|
671
|
-
|
|
672
|
-
// Content filtering: (Recent posts OR popular posts) AND published
|
|
673
|
-
const { data: featuredPosts } = useSuparisma.post({
|
|
674
|
-
where: {
|
|
675
|
-
published: true,
|
|
676
|
-
OR: [
|
|
677
|
-
{ createdAt: { gte: new Date('2024-01-01') } }, // Recent
|
|
678
|
-
{ views: { gte: 10000 } } // Popular
|
|
679
|
-
]
|
|
680
|
-
}
|
|
681
|
-
});
|
|
682
|
-
|
|
683
|
-
// Complex search across multiple fields and conditions
|
|
684
|
-
const { data: searchResults } = useSuparisma.product({
|
|
685
|
-
where: {
|
|
686
|
-
active: true,
|
|
687
|
-
AND: [
|
|
688
|
-
{
|
|
689
|
-
OR: [
|
|
690
|
-
{ name: { contains: searchTerm } },
|
|
691
|
-
{ description: { contains: searchTerm } },
|
|
692
|
-
{ tags: { has: [searchTerm] } }
|
|
693
|
-
]
|
|
694
|
-
},
|
|
695
|
-
{
|
|
696
|
-
OR: [
|
|
697
|
-
{ price: { between: [minPrice, maxPrice] } },
|
|
698
|
-
{ onSale: true }
|
|
699
|
-
]
|
|
700
|
-
}
|
|
701
|
-
]
|
|
702
|
-
}
|
|
703
|
-
});
|
|
704
|
-
```
|
|
705
|
-
|
|
706
|
-
#### Real-World Examples
|
|
707
|
-
|
|
708
|
-
**Multi-field search for e-commerce:**
|
|
709
|
-
```tsx
|
|
710
|
-
const { data: products } = useSuparisma.product({
|
|
711
|
-
where: {
|
|
712
|
-
OR: [
|
|
713
|
-
{ name: { contains: "laptop" } },
|
|
714
|
-
{ description: { contains: "laptop" } },
|
|
715
|
-
{ categories: { has: ["computers", "electronics"] } },
|
|
716
|
-
{ brand: { in: ["Apple", "Dell", "HP"] } }
|
|
717
|
-
]
|
|
718
|
-
}
|
|
719
|
-
});
|
|
720
|
-
```
|
|
721
|
-
|
|
722
|
-
**User permission filtering:**
|
|
723
|
-
```tsx
|
|
724
|
-
const { data: accessiblePosts } = useSuparisma.post({
|
|
725
|
-
where: {
|
|
726
|
-
OR: [
|
|
727
|
-
{ public: true },
|
|
728
|
-
{ authorId: currentUserId },
|
|
729
|
-
{
|
|
730
|
-
AND: [
|
|
731
|
-
{ published: true },
|
|
732
|
-
{ collaborators: { has: [currentUserId] } }
|
|
733
|
-
]
|
|
734
|
-
}
|
|
735
|
-
]
|
|
736
|
-
}
|
|
737
|
-
});
|
|
738
|
-
```
|
|
739
|
-
|
|
740
|
-
**Dynamic search with React state:**
|
|
741
|
-
```tsx
|
|
742
|
-
import { useMemo } from 'react';
|
|
743
|
-
|
|
744
|
-
function SearchComponent() {
|
|
745
|
-
const [searchTerm, setSearchTerm] = useState("");
|
|
746
|
-
const [category, setCategory] = useState("");
|
|
747
|
-
const [priceRange, setPriceRange] = useState({ min: 0, max: 1000 });
|
|
748
|
-
|
|
749
|
-
// ✅ Use useMemo for stable object reference
|
|
750
|
-
const searchFilter = useMemo(() => {
|
|
751
|
-
const conditions = [];
|
|
752
|
-
|
|
753
|
-
// Add search term condition
|
|
754
|
-
if (searchTerm) {
|
|
755
|
-
conditions.push({
|
|
756
|
-
OR: [
|
|
757
|
-
{ name: { contains: searchTerm } },
|
|
758
|
-
{ description: { contains: searchTerm } },
|
|
759
|
-
{ tags: { has: [searchTerm] } }
|
|
760
|
-
]
|
|
761
|
-
});
|
|
762
|
-
}
|
|
763
|
-
|
|
764
|
-
// Add category condition
|
|
765
|
-
if (category) {
|
|
766
|
-
conditions.push({
|
|
767
|
-
categories: { has: [category] }
|
|
768
|
-
});
|
|
769
|
-
}
|
|
770
|
-
|
|
771
|
-
// Add price range condition
|
|
772
|
-
conditions.push({
|
|
773
|
-
price: { gte: priceRange.min, lte: priceRange.max }
|
|
774
|
-
});
|
|
775
|
-
|
|
776
|
-
return conditions.length > 0 ? { AND: conditions } : undefined;
|
|
777
|
-
}, [searchTerm, category, priceRange]);
|
|
778
|
-
|
|
779
|
-
const { data: products } = useSuparisma.product({
|
|
780
|
-
where: searchFilter
|
|
781
|
-
});
|
|
782
|
-
|
|
783
|
-
// ... rest of component
|
|
784
|
-
}
|
|
785
|
-
```
|
|
786
|
-
|
|
787
|
-
#### Important Notes
|
|
788
|
-
|
|
789
|
-
1. **Performance**: OR conditions may require client-side filtering for realtime updates
|
|
790
|
-
2. **Realtime**: Complex OR/AND combinations automatically enable client-side filtering mode
|
|
791
|
-
3. **Memory**: Use `useMemo` for dynamic OR/AND filters to maintain stable object references
|
|
792
|
-
4. **Nesting**: You can nest OR within AND and vice versa for complex logic
|
|
793
|
-
|
|
794
583
|
### Sorting Data
|
|
795
584
|
|
|
796
585
|
Sort data using Prisma-like ordering:
|
|
@@ -1166,7 +955,7 @@ async function handleSubmit(event) {
|
|
|
1166
955
|
name: formData.name,
|
|
1167
956
|
someNumber: parseInt(formData.number)
|
|
1168
957
|
});
|
|
1169
|
-
|
|
958
|
+
console.log('Created!', result);
|
|
1170
959
|
} catch (err) {
|
|
1171
960
|
console.error('Failed to create thing:', err);
|
|
1172
961
|
}
|
|
@@ -94,21 +94,6 @@ export type FilterOperators<T> = {
|
|
|
94
94
|
// Type for a single field in an advanced where filter
|
|
95
95
|
export type AdvancedWhereInput<T> = {
|
|
96
96
|
[K in keyof T]?: T[K] | FilterOperators<T[K]>;
|
|
97
|
-
} & {
|
|
98
|
-
/**
|
|
99
|
-
* OR condition - match records that satisfy ANY of the provided conditions
|
|
100
|
-
* @example
|
|
101
|
-
* // Find users where name is "John" OR email contains "@admin"
|
|
102
|
-
* { OR: [{ name: "John" }, { email: { contains: "@admin" } }] }
|
|
103
|
-
*/
|
|
104
|
-
OR?: AdvancedWhereInput<T>[];
|
|
105
|
-
/**
|
|
106
|
-
* AND condition - match records that satisfy ALL of the provided conditions (default behavior)
|
|
107
|
-
* @example
|
|
108
|
-
* // Find users where name is "John" AND age > 18
|
|
109
|
-
* { AND: [{ name: "John" }, { age: { gt: 18 } }] }
|
|
110
|
-
*/
|
|
111
|
-
AND?: AdvancedWhereInput<T>[];
|
|
112
97
|
};
|
|
113
98
|
|
|
114
99
|
/**
|
|
@@ -192,19 +177,6 @@ export type SearchState = {
|
|
|
192
177
|
clearQueries: () => void;
|
|
193
178
|
};
|
|
194
179
|
|
|
195
|
-
/**
|
|
196
|
-
* Escape values for Supabase filter strings
|
|
197
|
-
*/
|
|
198
|
-
function escapeFilterValue(value: any): string {
|
|
199
|
-
if (typeof value === 'string') {
|
|
200
|
-
// If the string contains spaces or special characters, wrap it in quotes
|
|
201
|
-
if (value.includes(' ') || value.includes(',') || value.includes('(') || value.includes(')')) {
|
|
202
|
-
return \`"\${value.replace(/"/g, '\\\\"')}"\`;
|
|
203
|
-
}
|
|
204
|
-
}
|
|
205
|
-
return String(value);
|
|
206
|
-
}
|
|
207
|
-
|
|
208
180
|
/**
|
|
209
181
|
* Compare two values for sorting with proper type handling
|
|
210
182
|
*/
|
|
@@ -243,73 +215,9 @@ export function buildFilterString<T>(where?: T): string | undefined {
|
|
|
243
215
|
if (!where) return undefined;
|
|
244
216
|
|
|
245
217
|
const filters: string[] = [];
|
|
246
|
-
const orConditions: string[] = [];
|
|
247
218
|
|
|
248
219
|
for (const [key, value] of Object.entries(where)) {
|
|
249
220
|
if (value !== undefined) {
|
|
250
|
-
// Handle OR conditions
|
|
251
|
-
if (key === 'OR' && Array.isArray(value)) {
|
|
252
|
-
const orParts: string[] = [];
|
|
253
|
-
for (const orCondition of value) {
|
|
254
|
-
// Build individual OR condition parts using proper dot notation
|
|
255
|
-
for (const [orKey, orValue] of Object.entries(orCondition)) {
|
|
256
|
-
if (orValue !== undefined) {
|
|
257
|
-
if (typeof orValue === 'object' && orValue !== null) {
|
|
258
|
-
// Handle advanced operators in OR conditions
|
|
259
|
-
const advancedOps = orValue as unknown as FilterOperators<any>;
|
|
260
|
-
|
|
261
|
-
if ('equals' in advancedOps && advancedOps.equals !== undefined) {
|
|
262
|
-
orParts.push(\`\${orKey}.eq.\${advancedOps.equals}\`);
|
|
263
|
-
} else if ('contains' in advancedOps && advancedOps.contains !== undefined) {
|
|
264
|
-
orParts.push(\`\${orKey}.ilike.*\${advancedOps.contains}*\`);
|
|
265
|
-
} else if ('has' in advancedOps && advancedOps.has !== undefined) {
|
|
266
|
-
// Use PostgreSQL array syntax: {item1,item2} not ["item1","item2"]
|
|
267
|
-
const arrayValue = \`{\${advancedOps.has.map((item: any) => escapeFilterValue(item)).join(',')}}\`;
|
|
268
|
-
orParts.push(\`\${orKey}.ov.\${arrayValue}\`);
|
|
269
|
-
} else if ('hasEvery' in advancedOps && advancedOps.hasEvery !== undefined) {
|
|
270
|
-
// Use PostgreSQL array syntax: {item1,item2} not ["item1","item2"]
|
|
271
|
-
const arrayValue = \`{\${advancedOps.hasEvery.map((item: any) => escapeFilterValue(item)).join(',')}}\`;
|
|
272
|
-
orParts.push(\`\${orKey}.cs.\${arrayValue}\`);
|
|
273
|
-
} else if ('hasSome' in advancedOps && advancedOps.hasSome !== undefined) {
|
|
274
|
-
// Use PostgreSQL array syntax: {item1,item2} not ["item1","item2"]
|
|
275
|
-
const arrayValue = \`{\${advancedOps.hasSome.map((item: any) => escapeFilterValue(item)).join(',')}}\`;
|
|
276
|
-
orParts.push(\`\${orKey}.ov.\${arrayValue}\`);
|
|
277
|
-
} else if ('gt' in advancedOps && advancedOps.gt !== undefined) {
|
|
278
|
-
orParts.push(\`\${orKey}.gt.\${advancedOps.gt}\`);
|
|
279
|
-
} else if ('gte' in advancedOps && advancedOps.gte !== undefined) {
|
|
280
|
-
orParts.push(\`\${orKey}.gte.\${advancedOps.gte}\`);
|
|
281
|
-
} else if ('lt' in advancedOps && advancedOps.lt !== undefined) {
|
|
282
|
-
orParts.push(\`\${orKey}.lt.\${advancedOps.lt}\`);
|
|
283
|
-
} else if ('lte' in advancedOps && advancedOps.lte !== undefined) {
|
|
284
|
-
orParts.push(\`\${orKey}.lte.\${advancedOps.lte}\`);
|
|
285
|
-
} else if ('in' in advancedOps && advancedOps.in?.length) {
|
|
286
|
-
orParts.push(\`\${orKey}.in.(\${advancedOps.in.join(',')})\`);
|
|
287
|
-
}
|
|
288
|
-
} else {
|
|
289
|
-
// Simple equality in OR condition
|
|
290
|
-
orParts.push(\`\${orKey}.eq.\${escapeFilterValue(orValue)}\`);
|
|
291
|
-
}
|
|
292
|
-
}
|
|
293
|
-
}
|
|
294
|
-
}
|
|
295
|
-
|
|
296
|
-
if (orParts.length > 0) {
|
|
297
|
-
orConditions.push(\`or(\${orParts.join(',')})\`);
|
|
298
|
-
}
|
|
299
|
-
continue;
|
|
300
|
-
}
|
|
301
|
-
|
|
302
|
-
// Handle AND conditions (explicit)
|
|
303
|
-
if (key === 'AND' && Array.isArray(value)) {
|
|
304
|
-
for (const andCondition of value) {
|
|
305
|
-
const andFilter = buildFilterString(andCondition);
|
|
306
|
-
if (andFilter) {
|
|
307
|
-
filters.push(andFilter);
|
|
308
|
-
}
|
|
309
|
-
}
|
|
310
|
-
continue;
|
|
311
|
-
}
|
|
312
|
-
|
|
313
221
|
if (typeof value === 'object' && value !== null) {
|
|
314
222
|
// Handle advanced operators
|
|
315
223
|
const advancedOps = value as unknown as FilterOperators<any>;
|
|
@@ -357,22 +265,19 @@ export function buildFilterString<T>(where?: T): string | undefined {
|
|
|
357
265
|
// Array-specific operators
|
|
358
266
|
if ('has' in advancedOps && advancedOps.has !== undefined) {
|
|
359
267
|
// Array contains ANY of the specified items (overlaps)
|
|
360
|
-
|
|
361
|
-
const arrayValue = \`{\${advancedOps.has.map((item: any) => escapeFilterValue(item)).join(',')}}\`;
|
|
268
|
+
const arrayValue = JSON.stringify(advancedOps.has);
|
|
362
269
|
filters.push(\`\${key}=ov.\${arrayValue}\`);
|
|
363
270
|
}
|
|
364
271
|
|
|
365
272
|
if ('hasEvery' in advancedOps && advancedOps.hasEvery !== undefined) {
|
|
366
273
|
// Array contains ALL of the specified items (contains)
|
|
367
|
-
|
|
368
|
-
const arrayValue = \`{\${advancedOps.hasEvery.map((item: any) => escapeFilterValue(item)).join(',')}}\`;
|
|
274
|
+
const arrayValue = JSON.stringify(advancedOps.hasEvery);
|
|
369
275
|
filters.push(\`\${key}=cs.\${arrayValue}\`);
|
|
370
276
|
}
|
|
371
277
|
|
|
372
278
|
if ('hasSome' in advancedOps && advancedOps.hasSome !== undefined) {
|
|
373
279
|
// Array contains ANY of the specified items (overlaps)
|
|
374
|
-
|
|
375
|
-
const arrayValue = \`{\${advancedOps.hasSome.map((item: any) => escapeFilterValue(item)).join(',')}}\`;
|
|
280
|
+
const arrayValue = JSON.stringify(advancedOps.hasSome);
|
|
376
281
|
filters.push(\`\${key}=ov.\${arrayValue}\`);
|
|
377
282
|
}
|
|
378
283
|
|
|
@@ -392,18 +297,7 @@ export function buildFilterString<T>(where?: T): string | undefined {
|
|
|
392
297
|
}
|
|
393
298
|
}
|
|
394
299
|
|
|
395
|
-
|
|
396
|
-
const allConditions: string[] = [];
|
|
397
|
-
|
|
398
|
-
if (filters.length > 0) {
|
|
399
|
-
allConditions.push(filters.join(','));
|
|
400
|
-
}
|
|
401
|
-
|
|
402
|
-
if (orConditions.length > 0) {
|
|
403
|
-
allConditions.push(\`or(\${orConditions.join(',')})\`);
|
|
404
|
-
}
|
|
405
|
-
|
|
406
|
-
return allConditions.length > 0 ? allConditions.join(',') : undefined;
|
|
300
|
+
return filters.length > 0 ? filters.join(',') : undefined;
|
|
407
301
|
}
|
|
408
302
|
|
|
409
303
|
/**
|
|
@@ -420,69 +314,6 @@ export function applyFilter<T>(
|
|
|
420
314
|
// Apply each filter condition
|
|
421
315
|
for (const [key, value] of Object.entries(where)) {
|
|
422
316
|
if (value !== undefined) {
|
|
423
|
-
// Handle OR conditions
|
|
424
|
-
if (key === 'OR' && Array.isArray(value)) {
|
|
425
|
-
// For OR conditions, build the proper dot notation filter string
|
|
426
|
-
const orParts: string[] = [];
|
|
427
|
-
|
|
428
|
-
for (const orCondition of value) {
|
|
429
|
-
for (const [orKey, orValue] of Object.entries(orCondition)) {
|
|
430
|
-
if (orValue !== undefined) {
|
|
431
|
-
if (typeof orValue === 'object' && orValue !== null) {
|
|
432
|
-
// Handle advanced operators in OR conditions
|
|
433
|
-
const advancedOps = orValue as unknown as FilterOperators<any>;
|
|
434
|
-
|
|
435
|
-
if ('equals' in advancedOps && advancedOps.equals !== undefined) {
|
|
436
|
-
orParts.push(\`\${orKey}.eq.\${advancedOps.equals}\`);
|
|
437
|
-
} else if ('contains' in advancedOps && advancedOps.contains !== undefined) {
|
|
438
|
-
orParts.push(\`\${orKey}.ilike.*\${advancedOps.contains}*\`);
|
|
439
|
-
} else if ('has' in advancedOps && advancedOps.has !== undefined) {
|
|
440
|
-
// Use PostgreSQL array syntax: {item1,item2} not ["item1","item2"]
|
|
441
|
-
const arrayValue = \`{\${advancedOps.has.map((item: any) => escapeFilterValue(item)).join(',')}}\`;
|
|
442
|
-
orParts.push(\`\${orKey}.ov.\${arrayValue}\`);
|
|
443
|
-
} else if ('hasEvery' in advancedOps && advancedOps.hasEvery !== undefined) {
|
|
444
|
-
// Use PostgreSQL array syntax: {item1,item2} not ["item1","item2"]
|
|
445
|
-
const arrayValue = \`{\${advancedOps.hasEvery.map((item: any) => escapeFilterValue(item)).join(',')}}\`;
|
|
446
|
-
orParts.push(\`\${orKey}.cs.\${arrayValue}\`);
|
|
447
|
-
} else if ('hasSome' in advancedOps && advancedOps.hasSome !== undefined) {
|
|
448
|
-
// Use PostgreSQL array syntax: {item1,item2} not ["item1","item2"]
|
|
449
|
-
const arrayValue = \`{\${advancedOps.hasSome.map((item: any) => escapeFilterValue(item)).join(',')}}\`;
|
|
450
|
-
orParts.push(\`\${orKey}.ov.\${arrayValue}\`);
|
|
451
|
-
} else if ('gt' in advancedOps && advancedOps.gt !== undefined) {
|
|
452
|
-
orParts.push(\`\${orKey}.gt.\${advancedOps.gt}\`);
|
|
453
|
-
} else if ('gte' in advancedOps && advancedOps.gte !== undefined) {
|
|
454
|
-
orParts.push(\`\${orKey}.gte.\${advancedOps.gte}\`);
|
|
455
|
-
} else if ('lt' in advancedOps && advancedOps.lt !== undefined) {
|
|
456
|
-
orParts.push(\`\${orKey}.lt.\${advancedOps.lt}\`);
|
|
457
|
-
} else if ('lte' in advancedOps && advancedOps.lte !== undefined) {
|
|
458
|
-
orParts.push(\`\${orKey}.lte.\${advancedOps.lte}\`);
|
|
459
|
-
} else if ('in' in advancedOps && advancedOps.in?.length) {
|
|
460
|
-
orParts.push(\`\${orKey}.in.(\${advancedOps.in.join(',')})\`);
|
|
461
|
-
}
|
|
462
|
-
} else {
|
|
463
|
-
// Simple equality in OR condition
|
|
464
|
-
orParts.push(\`\${orKey}.eq.\${escapeFilterValue(orValue)}\`);
|
|
465
|
-
}
|
|
466
|
-
}
|
|
467
|
-
}
|
|
468
|
-
}
|
|
469
|
-
|
|
470
|
-
if (orParts.length > 0) {
|
|
471
|
-
// @ts-ignore: Supabase typing issue
|
|
472
|
-
filteredQuery = filteredQuery.or(orParts.join(','));
|
|
473
|
-
}
|
|
474
|
-
continue;
|
|
475
|
-
}
|
|
476
|
-
|
|
477
|
-
// Handle AND conditions (explicit)
|
|
478
|
-
if (key === 'AND' && Array.isArray(value)) {
|
|
479
|
-
// For explicit AND conditions, apply each condition normally
|
|
480
|
-
for (const andCondition of value) {
|
|
481
|
-
filteredQuery = applyFilter(filteredQuery, andCondition);
|
|
482
|
-
}
|
|
483
|
-
continue;
|
|
484
|
-
}
|
|
485
|
-
|
|
486
317
|
if (typeof value === 'object' && value !== null) {
|
|
487
318
|
// Handle advanced operators
|
|
488
319
|
const advancedOps = value as unknown as FilterOperators<any>;
|
|
@@ -614,111 +445,6 @@ export function applyOrderBy<T>(
|
|
|
614
445
|
return orderedQuery;
|
|
615
446
|
}
|
|
616
447
|
|
|
617
|
-
/**
|
|
618
|
-
* Client-side filter validation for realtime events
|
|
619
|
-
*/
|
|
620
|
-
function clientSideFilterCheck<T>(record: any, where: T): boolean {
|
|
621
|
-
if (!where) return true;
|
|
622
|
-
|
|
623
|
-
for (const [key, value] of Object.entries(where)) {
|
|
624
|
-
if (value !== undefined) {
|
|
625
|
-
// Handle OR conditions
|
|
626
|
-
if (key === 'OR' && Array.isArray(value)) {
|
|
627
|
-
// For OR, at least one condition must match
|
|
628
|
-
const orMatches = value.some(orCondition => clientSideFilterCheck(record, orCondition));
|
|
629
|
-
if (!orMatches) {
|
|
630
|
-
return false;
|
|
631
|
-
}
|
|
632
|
-
continue;
|
|
633
|
-
}
|
|
634
|
-
|
|
635
|
-
// Handle AND conditions (explicit)
|
|
636
|
-
if (key === 'AND' && Array.isArray(value)) {
|
|
637
|
-
// For AND, all conditions must match
|
|
638
|
-
const andMatches = value.every(andCondition => clientSideFilterCheck(record, andCondition));
|
|
639
|
-
if (!andMatches) {
|
|
640
|
-
return false;
|
|
641
|
-
}
|
|
642
|
-
continue;
|
|
643
|
-
}
|
|
644
|
-
|
|
645
|
-
if (typeof value === 'object' && value !== null) {
|
|
646
|
-
// Handle complex array filters client-side
|
|
647
|
-
const advancedOps = value as any;
|
|
648
|
-
const recordValue = record[key as keyof typeof record] as any;
|
|
649
|
-
|
|
650
|
-
// Array-specific operators validation
|
|
651
|
-
if ('has' in advancedOps && advancedOps.has !== undefined) {
|
|
652
|
-
// Array contains ANY of the specified items
|
|
653
|
-
if (!Array.isArray(recordValue) || !advancedOps.has.some((item: any) => recordValue.includes(item))) {
|
|
654
|
-
return false;
|
|
655
|
-
}
|
|
656
|
-
} else if ('hasEvery' in advancedOps && advancedOps.hasEvery !== undefined) {
|
|
657
|
-
// Array contains ALL of the specified items
|
|
658
|
-
if (!Array.isArray(recordValue) || !advancedOps.hasEvery.every((item: any) => recordValue.includes(item))) {
|
|
659
|
-
return false;
|
|
660
|
-
}
|
|
661
|
-
} else if ('hasSome' in advancedOps && advancedOps.hasSome !== undefined) {
|
|
662
|
-
// Array contains ANY of the specified items
|
|
663
|
-
if (!Array.isArray(recordValue) || !advancedOps.hasSome.some((item: any) => recordValue.includes(item))) {
|
|
664
|
-
return false;
|
|
665
|
-
}
|
|
666
|
-
} else if ('isEmpty' in advancedOps && advancedOps.isEmpty !== undefined) {
|
|
667
|
-
// Array is empty or not empty
|
|
668
|
-
const isEmpty = !Array.isArray(recordValue) || recordValue.length === 0;
|
|
669
|
-
if (isEmpty !== advancedOps.isEmpty) {
|
|
670
|
-
return false;
|
|
671
|
-
}
|
|
672
|
-
} else if ('equals' in advancedOps && advancedOps.equals !== undefined) {
|
|
673
|
-
if (recordValue !== advancedOps.equals) {
|
|
674
|
-
return false;
|
|
675
|
-
}
|
|
676
|
-
} else if ('not' in advancedOps && advancedOps.not !== undefined) {
|
|
677
|
-
if (recordValue === advancedOps.not) {
|
|
678
|
-
return false;
|
|
679
|
-
}
|
|
680
|
-
} else if ('gt' in advancedOps && advancedOps.gt !== undefined) {
|
|
681
|
-
if (!(recordValue > advancedOps.gt)) {
|
|
682
|
-
return false;
|
|
683
|
-
}
|
|
684
|
-
} else if ('gte' in advancedOps && advancedOps.gte !== undefined) {
|
|
685
|
-
if (!(recordValue >= advancedOps.gte)) {
|
|
686
|
-
return false;
|
|
687
|
-
}
|
|
688
|
-
} else if ('lt' in advancedOps && advancedOps.lt !== undefined) {
|
|
689
|
-
if (!(recordValue < advancedOps.lt)) {
|
|
690
|
-
return false;
|
|
691
|
-
}
|
|
692
|
-
} else if ('lte' in advancedOps && advancedOps.lte !== undefined) {
|
|
693
|
-
if (!(recordValue <= advancedOps.lte)) {
|
|
694
|
-
return false;
|
|
695
|
-
}
|
|
696
|
-
} else if ('in' in advancedOps && advancedOps.in !== undefined) {
|
|
697
|
-
if (!advancedOps.in.includes(recordValue)) {
|
|
698
|
-
return false;
|
|
699
|
-
}
|
|
700
|
-
} else if ('contains' in advancedOps && advancedOps.contains !== undefined) {
|
|
701
|
-
if (!String(recordValue).toLowerCase().includes(String(advancedOps.contains).toLowerCase())) {
|
|
702
|
-
return false;
|
|
703
|
-
}
|
|
704
|
-
} else if ('startsWith' in advancedOps && advancedOps.startsWith !== undefined) {
|
|
705
|
-
if (!String(recordValue).toLowerCase().startsWith(String(advancedOps.startsWith).toLowerCase())) {
|
|
706
|
-
return false;
|
|
707
|
-
}
|
|
708
|
-
} else if ('endsWith' in advancedOps && advancedOps.endsWith !== undefined) {
|
|
709
|
-
if (!String(recordValue).toLowerCase().endsWith(String(advancedOps.endsWith).toLowerCase())) {
|
|
710
|
-
return false;
|
|
711
|
-
}
|
|
712
|
-
}
|
|
713
|
-
} else if (record[key as keyof typeof record] !== value) {
|
|
714
|
-
return false;
|
|
715
|
-
}
|
|
716
|
-
}
|
|
717
|
-
}
|
|
718
|
-
|
|
719
|
-
return true;
|
|
720
|
-
}
|
|
721
|
-
|
|
722
448
|
/**
|
|
723
449
|
* Core hook factory function that creates a type-safe realtime hook for a specific model.
|
|
724
450
|
* This is the foundation for all Suparisma hooks.
|
|
@@ -762,23 +488,12 @@ export function createSuparismaHook<
|
|
|
762
488
|
* const users = useSuparismaUser();
|
|
763
489
|
* const { data, loading, error } = users;
|
|
764
490
|
*
|
|
765
|
-
|
|
766
|
-
|
|
767
|
-
|
|
768
|
-
|
|
769
|
-
|
|
770
|
-
|
|
771
|
-
*
|
|
772
|
-
* @example
|
|
773
|
-
* // With OR conditions for search
|
|
774
|
-
* const users = useSuparismaUser({
|
|
775
|
-
* where: {
|
|
776
|
-
* OR: [
|
|
777
|
-
* { name: { contains: "john" } },
|
|
778
|
-
* { email: { contains: "john" } }
|
|
779
|
-
* ]
|
|
780
|
-
* }
|
|
781
|
-
* });
|
|
491
|
+
* @example
|
|
492
|
+
* // With filtering
|
|
493
|
+
* const users = useSuparismaUser({
|
|
494
|
+
* where: { role: 'admin' },
|
|
495
|
+
* orderBy: { created_at: 'desc' }
|
|
496
|
+
* });
|
|
782
497
|
*/
|
|
783
498
|
return function useSuparismaHook(options: SuparismaOptions<TWhereInput, TOrderByInput> = {}) {
|
|
784
499
|
const {
|
|
@@ -989,7 +704,19 @@ export function createSuparismaHook<
|
|
|
989
704
|
|
|
990
705
|
// Apply any where conditions client-side
|
|
991
706
|
if (where) {
|
|
992
|
-
results = results.filter((item) =>
|
|
707
|
+
results = results.filter((item) => {
|
|
708
|
+
for (const [key, value] of Object.entries(where)) {
|
|
709
|
+
if (typeof value === 'object' && value !== null) {
|
|
710
|
+
// Skip complex filters for now
|
|
711
|
+
continue;
|
|
712
|
+
}
|
|
713
|
+
|
|
714
|
+
if (item[key as keyof typeof item] !== value) {
|
|
715
|
+
return false;
|
|
716
|
+
}
|
|
717
|
+
}
|
|
718
|
+
return true;
|
|
719
|
+
});
|
|
993
720
|
}
|
|
994
721
|
|
|
995
722
|
// Set count directly for search results
|
|
@@ -1185,33 +912,19 @@ export function createSuparismaHook<
|
|
|
1185
912
|
|
|
1186
913
|
const channelId = channelName || \`changes_to_\${tableName}_\${Math.random().toString(36).substring(2, 15)}\`;
|
|
1187
914
|
|
|
1188
|
-
// Check if we have complex array filters
|
|
915
|
+
// Check if we have complex array filters that should be handled client-side only
|
|
1189
916
|
let hasComplexArrayFilters = false;
|
|
1190
917
|
if (where) {
|
|
1191
|
-
const
|
|
1192
|
-
|
|
1193
|
-
|
|
1194
|
-
|
|
1195
|
-
|
|
1196
|
-
|
|
1197
|
-
|
|
1198
|
-
if (typeof value === 'object' && value !== null) {
|
|
1199
|
-
// Check if it's an array of conditions (nested OR/AND)
|
|
1200
|
-
if (Array.isArray(value)) {
|
|
1201
|
-
return true;
|
|
1202
|
-
}
|
|
1203
|
-
|
|
1204
|
-
const advancedOps = value as any;
|
|
1205
|
-
// Check for complex array operators
|
|
1206
|
-
if ('has' in advancedOps || 'hasEvery' in advancedOps || 'hasSome' in advancedOps || 'isEmpty' in advancedOps) {
|
|
1207
|
-
return true;
|
|
1208
|
-
}
|
|
918
|
+
for (const [key, value] of Object.entries(where)) {
|
|
919
|
+
if (typeof value === 'object' && value !== null) {
|
|
920
|
+
const advancedOps = value as any;
|
|
921
|
+
// Check for complex array operators
|
|
922
|
+
if ('has' in advancedOps || 'hasEvery' in advancedOps || 'hasSome' in advancedOps || 'isEmpty' in advancedOps) {
|
|
923
|
+
hasComplexArrayFilters = true;
|
|
924
|
+
break;
|
|
1209
925
|
}
|
|
1210
926
|
}
|
|
1211
|
-
|
|
1212
|
-
};
|
|
1213
|
-
|
|
1214
|
-
hasComplexArrayFilters = checkForComplexFilters(where);
|
|
927
|
+
}
|
|
1215
928
|
}
|
|
1216
929
|
|
|
1217
930
|
// For complex array filters, use no database filter and rely on client-side filtering
|
|
@@ -1224,15 +937,18 @@ export function createSuparismaHook<
|
|
|
1224
937
|
|
|
1225
938
|
if (hasComplexArrayFilters) {
|
|
1226
939
|
// Don't include filter at all for complex array operations
|
|
940
|
+
console.log(\`Setting up subscription for \${tableName} with NO FILTER (complex array filters detected) - will receive ALL events\`);
|
|
1227
941
|
} else if (where) {
|
|
1228
942
|
// Include filter for simple operations
|
|
1229
943
|
const filter = buildFilterString(where);
|
|
1230
944
|
if (filter) {
|
|
1231
945
|
subscriptionConfig.filter = filter;
|
|
1232
946
|
}
|
|
947
|
+
console.log(\`Setting up subscription for \${tableName} with database filter: \${filter}\`);
|
|
1233
948
|
} else if (realtimeFilter) {
|
|
1234
949
|
// Use custom realtime filter if provided
|
|
1235
950
|
subscriptionConfig.filter = realtimeFilter;
|
|
951
|
+
console.log(\`Setting up subscription for \${tableName} with custom filter: \${realtimeFilter}\`);
|
|
1236
952
|
}
|
|
1237
953
|
|
|
1238
954
|
const channel = supabase
|
|
@@ -1241,6 +957,7 @@ export function createSuparismaHook<
|
|
|
1241
957
|
'postgres_changes',
|
|
1242
958
|
subscriptionConfig,
|
|
1243
959
|
(payload) => {
|
|
960
|
+
console.log(\`🔥 REALTIME EVENT RECEIVED for \${tableName}:\`, payload.eventType, payload);
|
|
1244
961
|
|
|
1245
962
|
// Access current options via refs inside the event handler
|
|
1246
963
|
const currentWhere = whereRef.current;
|
|
@@ -1250,6 +967,7 @@ export function createSuparismaHook<
|
|
|
1250
967
|
|
|
1251
968
|
// Skip realtime updates when search is active
|
|
1252
969
|
if (isSearchingRef.current) {
|
|
970
|
+
console.log('⏭️ Skipping realtime update - search is active');
|
|
1253
971
|
return;
|
|
1254
972
|
}
|
|
1255
973
|
|
|
@@ -1258,13 +976,57 @@ export function createSuparismaHook<
|
|
|
1258
976
|
setData((prev) => {
|
|
1259
977
|
try {
|
|
1260
978
|
const newRecord = payload.new as TWithRelations;
|
|
979
|
+
console.log(\`Processing INSERT for \${tableName}\`, { newRecord });
|
|
1261
980
|
|
|
1262
981
|
// ALWAYS check if this record matches our filter client-side
|
|
1263
982
|
// This is especially important for complex array filters
|
|
1264
983
|
if (currentWhere) { // Use ref value
|
|
1265
|
-
|
|
984
|
+
let matchesFilter = true;
|
|
985
|
+
|
|
986
|
+
// Check each filter condition client-side for complex filters
|
|
987
|
+
for (const [key, value] of Object.entries(currentWhere)) {
|
|
988
|
+
if (typeof value === 'object' && value !== null) {
|
|
989
|
+
// Handle complex array filters client-side
|
|
990
|
+
const advancedOps = value as any;
|
|
991
|
+
const recordValue = newRecord[key as keyof typeof newRecord] as any;
|
|
992
|
+
|
|
993
|
+
// Array-specific operators validation
|
|
994
|
+
if ('has' in advancedOps && advancedOps.has !== undefined) {
|
|
995
|
+
// Array contains ANY of the specified items
|
|
996
|
+
if (!Array.isArray(recordValue) || !advancedOps.has.some((item: any) => recordValue.includes(item))) {
|
|
997
|
+
matchesFilter = false;
|
|
998
|
+
break;
|
|
999
|
+
}
|
|
1000
|
+
} else if ('hasEvery' in advancedOps && advancedOps.hasEvery !== undefined) {
|
|
1001
|
+
// Array contains ALL of the specified items
|
|
1002
|
+
if (!Array.isArray(recordValue) || !advancedOps.hasEvery.every((item: any) => recordValue.includes(item))) {
|
|
1003
|
+
matchesFilter = false;
|
|
1004
|
+
break;
|
|
1005
|
+
}
|
|
1006
|
+
} else if ('hasSome' in advancedOps && advancedOps.hasSome !== undefined) {
|
|
1007
|
+
// Array contains ANY of the specified items
|
|
1008
|
+
if (!Array.isArray(recordValue) || !advancedOps.hasSome.some((item: any) => recordValue.includes(item))) {
|
|
1009
|
+
matchesFilter = false;
|
|
1010
|
+
break;
|
|
1011
|
+
}
|
|
1012
|
+
} else if ('isEmpty' in advancedOps && advancedOps.isEmpty !== undefined) {
|
|
1013
|
+
// Array is empty or not empty
|
|
1014
|
+
const isEmpty = !Array.isArray(recordValue) || recordValue.length === 0;
|
|
1015
|
+
if (isEmpty !== advancedOps.isEmpty) {
|
|
1016
|
+
matchesFilter = false;
|
|
1017
|
+
break;
|
|
1018
|
+
}
|
|
1019
|
+
}
|
|
1020
|
+
// Add other complex filter validations as needed
|
|
1021
|
+
} else if (newRecord[key as keyof typeof newRecord] !== value) {
|
|
1022
|
+
matchesFilter = false;
|
|
1023
|
+
console.log(\`Filter mismatch on \${key}\`, { expected: value, actual: newRecord[key as keyof typeof newRecord] });
|
|
1024
|
+
break;
|
|
1025
|
+
}
|
|
1026
|
+
}
|
|
1266
1027
|
|
|
1267
1028
|
if (!matchesFilter) {
|
|
1029
|
+
console.log('New record does not match filter criteria, skipping');
|
|
1268
1030
|
return prev;
|
|
1269
1031
|
}
|
|
1270
1032
|
}
|
|
@@ -1276,6 +1038,7 @@ export function createSuparismaHook<
|
|
|
1276
1038
|
);
|
|
1277
1039
|
|
|
1278
1040
|
if (exists) {
|
|
1041
|
+
console.log('Record already exists, skipping insertion');
|
|
1279
1042
|
return prev;
|
|
1280
1043
|
}
|
|
1281
1044
|
|
|
@@ -1346,10 +1109,50 @@ export function createSuparismaHook<
|
|
|
1346
1109
|
|
|
1347
1110
|
// Check if the updated record still matches our current filter
|
|
1348
1111
|
if (currentWhere) {
|
|
1349
|
-
|
|
1112
|
+
let matchesFilter = true;
|
|
1113
|
+
|
|
1114
|
+
for (const [key, value] of Object.entries(currentWhere)) {
|
|
1115
|
+
if (typeof value === 'object' && value !== null) {
|
|
1116
|
+
// Handle complex array filters client-side
|
|
1117
|
+
const advancedOps = value as any;
|
|
1118
|
+
const recordValue = updatedRecord[key as keyof typeof updatedRecord] as any;
|
|
1119
|
+
|
|
1120
|
+
// Array-specific operators validation
|
|
1121
|
+
if ('has' in advancedOps && advancedOps.has !== undefined) {
|
|
1122
|
+
// Array contains ANY of the specified items
|
|
1123
|
+
if (!Array.isArray(recordValue) || !advancedOps.has.some((item: any) => recordValue.includes(item))) {
|
|
1124
|
+
matchesFilter = false;
|
|
1125
|
+
break;
|
|
1126
|
+
}
|
|
1127
|
+
} else if ('hasEvery' in advancedOps && advancedOps.hasEvery !== undefined) {
|
|
1128
|
+
// Array contains ALL of the specified items
|
|
1129
|
+
if (!Array.isArray(recordValue) || !advancedOps.hasEvery.every((item: any) => recordValue.includes(item))) {
|
|
1130
|
+
matchesFilter = false;
|
|
1131
|
+
break;
|
|
1132
|
+
}
|
|
1133
|
+
} else if ('hasSome' in advancedOps && advancedOps.hasSome !== undefined) {
|
|
1134
|
+
// Array contains ANY of the specified items
|
|
1135
|
+
if (!Array.isArray(recordValue) || !advancedOps.hasSome.some((item: any) => recordValue.includes(item))) {
|
|
1136
|
+
matchesFilter = false;
|
|
1137
|
+
break;
|
|
1138
|
+
}
|
|
1139
|
+
} else if ('isEmpty' in advancedOps && advancedOps.isEmpty !== undefined) {
|
|
1140
|
+
// Array is empty or not empty
|
|
1141
|
+
const isEmpty = !Array.isArray(recordValue) || recordValue.length === 0;
|
|
1142
|
+
if (isEmpty !== advancedOps.isEmpty) {
|
|
1143
|
+
matchesFilter = false;
|
|
1144
|
+
break;
|
|
1145
|
+
}
|
|
1146
|
+
}
|
|
1147
|
+
} else if (updatedRecord[key as keyof typeof updatedRecord] !== value) {
|
|
1148
|
+
matchesFilter = false;
|
|
1149
|
+
break;
|
|
1150
|
+
}
|
|
1151
|
+
}
|
|
1350
1152
|
|
|
1351
1153
|
// If the updated record doesn't match the filter, remove it from the list
|
|
1352
1154
|
if (!matchesFilter) {
|
|
1155
|
+
console.log('Updated record no longer matches filter, removing from list');
|
|
1353
1156
|
return prev.filter((item) =>
|
|
1354
1157
|
// @ts-ignore: Supabase typing issue
|
|
1355
1158
|
!('id' in item && 'id' in updatedRecord && item.id === updatedRecord.id)
|
|
@@ -1408,7 +1211,9 @@ export function createSuparismaHook<
|
|
|
1408
1211
|
});
|
|
1409
1212
|
} else if (payload.eventType === 'DELETE') {
|
|
1410
1213
|
// Process delete event
|
|
1214
|
+
console.log('🗑️ Processing DELETE event for', tableName);
|
|
1411
1215
|
setData((prev) => {
|
|
1216
|
+
console.log('🗑️ DELETE: Current data before deletion:', prev.length, 'items');
|
|
1412
1217
|
|
|
1413
1218
|
// Access current options via refs
|
|
1414
1219
|
const currentWhere = whereRef.current;
|
|
@@ -1418,6 +1223,7 @@ export function createSuparismaHook<
|
|
|
1418
1223
|
|
|
1419
1224
|
// Skip if search is active
|
|
1420
1225
|
if (isSearchingRef.current) {
|
|
1226
|
+
console.log('⏭️ DELETE: Skipping - search is active');
|
|
1421
1227
|
return prev;
|
|
1422
1228
|
}
|
|
1423
1229
|
|
|
@@ -1427,14 +1233,21 @@ export function createSuparismaHook<
|
|
|
1427
1233
|
// Filter out the deleted item
|
|
1428
1234
|
const filteredData = prev.filter((item) => {
|
|
1429
1235
|
// @ts-ignore: Supabase typing issue
|
|
1430
|
-
|
|
1236
|
+
const shouldKeep = !('id' in item && 'id' in payload.old && item.id === payload.old.id);
|
|
1237
|
+
if (!shouldKeep) {
|
|
1238
|
+
console.log('🗑️ DELETE: Removing item with ID:', item.id);
|
|
1239
|
+
}
|
|
1240
|
+
return shouldKeep;
|
|
1431
1241
|
});
|
|
1432
1242
|
|
|
1243
|
+
console.log('🗑️ DELETE: Data after deletion:', filteredData.length, 'items (was', currentSize, ')');
|
|
1244
|
+
|
|
1433
1245
|
// Fetch the updated count after the data changes
|
|
1434
1246
|
setTimeout(() => fetchTotalCount(), 0);
|
|
1435
1247
|
|
|
1436
1248
|
// If we need to maintain the size with a limit
|
|
1437
1249
|
if (currentLimit && currentLimit > 0 && filteredData.length < currentSize && currentSize === currentLimit) { // Use ref value
|
|
1250
|
+
console.log(\`🗑️ DELETE: Record deleted with limit \${currentLimit}, will fetch additional record to maintain size\`);
|
|
1438
1251
|
|
|
1439
1252
|
// Use setTimeout to ensure this state update completes first
|
|
1440
1253
|
setTimeout(() => {
|
|
@@ -1492,16 +1305,19 @@ export function createSuparismaHook<
|
|
|
1492
1305
|
}
|
|
1493
1306
|
}
|
|
1494
1307
|
)
|
|
1495
|
-
.subscribe()
|
|
1308
|
+
.subscribe((status) => {
|
|
1309
|
+
console.log(\`Subscription status for \${tableName}\`, status);
|
|
1310
|
+
});
|
|
1496
1311
|
|
|
1497
1312
|
// Store the channel ref
|
|
1498
1313
|
channelRef.current = channel;
|
|
1499
1314
|
|
|
1500
|
-
|
|
1501
|
-
|
|
1502
|
-
|
|
1503
|
-
|
|
1504
|
-
|
|
1315
|
+
return () => {
|
|
1316
|
+
console.log(\`Unsubscribing from \${channelId}\`);
|
|
1317
|
+
if (channelRef.current) {
|
|
1318
|
+
supabase.removeChannel(channelRef.current); // Correct way to remove channel
|
|
1319
|
+
channelRef.current = null;
|
|
1320
|
+
}
|
|
1505
1321
|
|
|
1506
1322
|
if (searchTimeoutRef.current) {
|
|
1507
1323
|
clearTimeout(searchTimeoutRef.current);
|
|
@@ -1546,6 +1362,7 @@ export function createSuparismaHook<
|
|
|
1546
1362
|
if (initialLoadRef.current) {
|
|
1547
1363
|
// Only reload if options have changed significantly
|
|
1548
1364
|
if (optionsChanged()) {
|
|
1365
|
+
console.log(\`Options changed for \${tableName}, reloading data\`);
|
|
1549
1366
|
findMany({
|
|
1550
1367
|
where,
|
|
1551
1368
|
orderBy,
|
|
@@ -1575,6 +1392,8 @@ export function createSuparismaHook<
|
|
|
1575
1392
|
/**
|
|
1576
1393
|
* Create a new record with the provided data.
|
|
1577
1394
|
* Default values from the schema will be applied if not provided.
|
|
1395
|
+
* NOTE: This operation does NOT immediately update the local state.
|
|
1396
|
+
* The state will be updated when the realtime INSERT event is received.
|
|
1578
1397
|
*
|
|
1579
1398
|
* @param data - The data to create the record with
|
|
1580
1399
|
* @returns A promise with the created record or error
|
|
@@ -1647,8 +1466,8 @@ export function createSuparismaHook<
|
|
|
1647
1466
|
|
|
1648
1467
|
if (error) throw error;
|
|
1649
1468
|
|
|
1650
|
-
//
|
|
1651
|
-
|
|
1469
|
+
// DO NOT UPDATE LOCAL STATE HERE - Let realtime INSERT event handle it
|
|
1470
|
+
console.log('✅ Created ' + tableName + ' record, waiting for realtime INSERT event to update state');
|
|
1652
1471
|
|
|
1653
1472
|
// Return created record
|
|
1654
1473
|
return { data: result?.[0] as TWithRelations, error: null };
|
|
@@ -1659,10 +1478,12 @@ export function createSuparismaHook<
|
|
|
1659
1478
|
} finally {
|
|
1660
1479
|
setLoading(false);
|
|
1661
1480
|
}
|
|
1662
|
-
}, [
|
|
1481
|
+
}, []);
|
|
1663
1482
|
|
|
1664
1483
|
/**
|
|
1665
1484
|
* Update an existing record identified by a unique identifier.
|
|
1485
|
+
* NOTE: This operation does NOT immediately update the local state.
|
|
1486
|
+
* The state will be updated when the realtime UPDATE event is received.
|
|
1666
1487
|
*
|
|
1667
1488
|
* @param params - Object containing the identifier and update data
|
|
1668
1489
|
* @returns A promise with the updated record or error
|
|
@@ -1724,10 +1545,8 @@ export function createSuparismaHook<
|
|
|
1724
1545
|
|
|
1725
1546
|
if (error) throw error;
|
|
1726
1547
|
|
|
1727
|
-
//
|
|
1728
|
-
|
|
1729
|
-
// updates can sometimes affect filtering based on updated values
|
|
1730
|
-
setTimeout(() => fetchTotalCount(), 0);
|
|
1548
|
+
// DO NOT UPDATE LOCAL STATE HERE - Let realtime UPDATE event handle it
|
|
1549
|
+
console.log('✅ Updated ' + tableName + ' record, waiting for realtime UPDATE event to update state');
|
|
1731
1550
|
|
|
1732
1551
|
// Return updated record
|
|
1733
1552
|
return { data: data?.[0] as TWithRelations, error: null };
|
|
@@ -1738,10 +1557,12 @@ export function createSuparismaHook<
|
|
|
1738
1557
|
} finally {
|
|
1739
1558
|
setLoading(false);
|
|
1740
1559
|
}
|
|
1741
|
-
}, [
|
|
1560
|
+
}, []);
|
|
1742
1561
|
|
|
1743
1562
|
/**
|
|
1744
1563
|
* Delete a record by its unique identifier.
|
|
1564
|
+
* NOTE: This operation does NOT immediately update the local state.
|
|
1565
|
+
* The state will be updated when the realtime DELETE event is received.
|
|
1745
1566
|
*
|
|
1746
1567
|
* @param where - The unique identifier to delete the record by
|
|
1747
1568
|
* @returns A promise with the deleted record or error
|
|
@@ -1791,8 +1612,8 @@ export function createSuparismaHook<
|
|
|
1791
1612
|
|
|
1792
1613
|
if (error) throw error;
|
|
1793
1614
|
|
|
1794
|
-
//
|
|
1795
|
-
|
|
1615
|
+
// DO NOT UPDATE LOCAL STATE HERE - Let realtime DELETE event handle it
|
|
1616
|
+
console.log('✅ Deleted ' + tableName + ' record, waiting for realtime DELETE event to update state');
|
|
1796
1617
|
|
|
1797
1618
|
// Return the deleted record
|
|
1798
1619
|
return { data: recordToDelete as TWithRelations, error: null };
|
|
@@ -1803,10 +1624,12 @@ export function createSuparismaHook<
|
|
|
1803
1624
|
} finally {
|
|
1804
1625
|
setLoading(false);
|
|
1805
1626
|
}
|
|
1806
|
-
}, [
|
|
1627
|
+
}, []);
|
|
1807
1628
|
|
|
1808
1629
|
/**
|
|
1809
1630
|
* Delete multiple records matching the filter criteria.
|
|
1631
|
+
* NOTE: This operation does NOT immediately update the local state.
|
|
1632
|
+
* The state will be updated when realtime DELETE events are received for each record.
|
|
1810
1633
|
*
|
|
1811
1634
|
* @param params - Query parameters for filtering records to delete
|
|
1812
1635
|
* @returns A promise with the count of deleted records or error
|
|
@@ -1816,7 +1639,7 @@ export function createSuparismaHook<
|
|
|
1816
1639
|
* const result = await users.deleteMany({
|
|
1817
1640
|
* where: { active: false }
|
|
1818
1641
|
* });
|
|
1819
|
-
* console.log(
|
|
1642
|
+
* console.log('Deleted ' + result.count + ' inactive users');
|
|
1820
1643
|
*
|
|
1821
1644
|
* @example
|
|
1822
1645
|
* // Delete all records (use with caution!)
|
|
@@ -1860,8 +1683,8 @@ export function createSuparismaHook<
|
|
|
1860
1683
|
|
|
1861
1684
|
if (deleteError) throw deleteError;
|
|
1862
1685
|
|
|
1863
|
-
//
|
|
1864
|
-
|
|
1686
|
+
// DO NOT UPDATE LOCAL STATE HERE - Let realtime DELETE events handle it
|
|
1687
|
+
console.log('✅ Deleted ' + recordsToDelete.length + ' ' + tableName + ' records, waiting for realtime DELETE events to update state');
|
|
1865
1688
|
|
|
1866
1689
|
// Return the count of deleted records
|
|
1867
1690
|
return { count: recordsToDelete.length, error: null };
|
|
@@ -1872,7 +1695,7 @@ export function createSuparismaHook<
|
|
|
1872
1695
|
} finally {
|
|
1873
1696
|
setLoading(false);
|
|
1874
1697
|
}
|
|
1875
|
-
}, [
|
|
1698
|
+
}, []);
|
|
1876
1699
|
|
|
1877
1700
|
/**
|
|
1878
1701
|
* Find the first record matching the filter criteria.
|
|
@@ -2057,7 +1880,6 @@ export function createSuparismaHook<
|
|
|
2057
1880
|
: api;
|
|
2058
1881
|
};
|
|
2059
1882
|
}
|
|
2060
|
-
|
|
2061
1883
|
`; // Ensure template literal is closed
|
|
2062
1884
|
// Output to the UTILS_DIR
|
|
2063
1885
|
const outputPath = path_1.default.join(config_1.UTILS_DIR, 'core.ts');
|
|
@@ -2065,4 +1887,5 @@ export function createSuparismaHook<
|
|
|
2065
1887
|
fs_1.default.mkdirSync(config_1.UTILS_DIR, { recursive: true });
|
|
2066
1888
|
}
|
|
2067
1889
|
fs_1.default.writeFileSync(outputPath, coreContent);
|
|
1890
|
+
console.log(`Generated core utility file at ${outputPath}`);
|
|
2068
1891
|
}
|
|
@@ -113,4 +113,5 @@ export const ${config_1.HOOK_NAME_PREFIX}${modelName} = createSuparismaHook<
|
|
|
113
113
|
fs_1.default.mkdirSync(config_1.HOOKS_DIR, { recursive: true });
|
|
114
114
|
}
|
|
115
115
|
fs_1.default.writeFileSync(outputPath, hookContent);
|
|
116
|
+
console.log(`Generated hook for ${modelName} at ${outputPath}`);
|
|
116
117
|
}
|
|
@@ -118,44 +118,40 @@ 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
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
}
|
|
133
|
-
return `// ${zodImport.importStatement}`;
|
|
134
|
-
})
|
|
135
|
-
.join('\n') + '\n\n';
|
|
136
|
-
customImports += importStatements;
|
|
137
|
-
// Add type definitions for imported zod schemas as simple types
|
|
138
|
-
const customTypeDefinitions = model.zodImports
|
|
139
|
-
.flatMap(zodImport => zodImport.types)
|
|
140
|
-
.filter((type, index, array) => array.indexOf(type) === index) // Remove duplicates
|
|
141
|
-
.map(type => {
|
|
142
|
-
// If it ends with 'Schema', create a corresponding type as any for now
|
|
143
|
-
if (type.endsWith('Schema')) {
|
|
144
|
-
const typeName = type.replace('Schema', '');
|
|
145
|
-
return `// Fallback type - replace with actual type if you implement zod schemas\nexport type ${typeName} = any;`;
|
|
146
|
-
}
|
|
147
|
-
return '';
|
|
148
|
-
})
|
|
149
|
-
.filter(Boolean)
|
|
150
|
-
.join('\n');
|
|
151
|
-
if (customTypeDefinitions) {
|
|
152
|
-
customImports += customTypeDefinitions + '\n\n';
|
|
121
|
+
// Add zod import for type inference
|
|
122
|
+
customImports = 'import { z } from \'zod\';\n';
|
|
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}'`;
|
|
153
133
|
}
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
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
|
|
140
|
+
const customTypeDefinitions = model.zodImports
|
|
141
|
+
.flatMap(zodImport => zodImport.types)
|
|
142
|
+
.filter((type, index, array) => array.indexOf(type) === index) // Remove duplicates
|
|
143
|
+
.map(type => {
|
|
144
|
+
// If it ends with 'Schema', create a corresponding type
|
|
145
|
+
if (type.endsWith('Schema')) {
|
|
146
|
+
const typeName = type.replace('Schema', '');
|
|
147
|
+
return `export type ${typeName} = z.infer<typeof ${type}>;`;
|
|
148
|
+
}
|
|
149
|
+
return '';
|
|
150
|
+
})
|
|
151
|
+
.filter(Boolean)
|
|
152
|
+
.join('\n');
|
|
153
|
+
if (customTypeDefinitions) {
|
|
154
|
+
customImports += customTypeDefinitions + '\n\n';
|
|
159
155
|
}
|
|
160
156
|
}
|
|
161
157
|
// Generate the type content with TSDoc comments
|
|
@@ -268,28 +264,6 @@ ${withRelationsProps
|
|
|
268
264
|
.join(',\n')}
|
|
269
265
|
* }
|
|
270
266
|
* });
|
|
271
|
-
*
|
|
272
|
-
* @example
|
|
273
|
-
* // OR conditions - find records matching ANY condition
|
|
274
|
-
* ${modelName.toLowerCase()}.findMany({
|
|
275
|
-
* where: {
|
|
276
|
-
* OR: [
|
|
277
|
-
* { name: "John" },
|
|
278
|
-
* { email: { contains: "@admin" } }
|
|
279
|
-
* ]
|
|
280
|
-
* }
|
|
281
|
-
* });
|
|
282
|
-
*
|
|
283
|
-
* @example
|
|
284
|
-
* // AND conditions - find records matching ALL conditions
|
|
285
|
-
* ${modelName.toLowerCase()}.findMany({
|
|
286
|
-
* where: {
|
|
287
|
-
* AND: [
|
|
288
|
-
* { active: true },
|
|
289
|
-
* { age: { gte: 18 } }
|
|
290
|
-
* ]
|
|
291
|
-
* }
|
|
292
|
-
* });
|
|
293
267
|
*/
|
|
294
268
|
export type ${modelName}WhereInput = {
|
|
295
269
|
${model.fields
|
|
@@ -311,12 +285,6 @@ ${model.fields
|
|
|
311
285
|
}
|
|
312
286
|
return '';
|
|
313
287
|
}).filter(Boolean))
|
|
314
|
-
.concat([
|
|
315
|
-
' /** OR condition - match records that satisfy ANY of the provided conditions */',
|
|
316
|
-
` OR?: ${modelName}WhereInput[];`,
|
|
317
|
-
' /** AND condition - match records that satisfy ALL of the provided conditions */',
|
|
318
|
-
` AND?: ${modelName}WhereInput[];`
|
|
319
|
-
])
|
|
320
288
|
.join('\n')}
|
|
321
289
|
};
|
|
322
290
|
|
|
@@ -687,6 +655,7 @@ ${createInputProps
|
|
|
687
655
|
fs_1.default.mkdirSync(config_1.TYPES_DIR, { recursive: true });
|
|
688
656
|
}
|
|
689
657
|
fs_1.default.writeFileSync(outputPath, typeContent);
|
|
658
|
+
console.log(`Generated type definitions for ${modelName} at ${outputPath}`);
|
|
690
659
|
return {
|
|
691
660
|
modelName,
|
|
692
661
|
tableName,
|
package/dist/index.js
CHANGED
|
@@ -84,6 +84,7 @@ function checkEnvironmentVariables() {
|
|
|
84
84
|
errorMessage += '\nPlease add these variables to your .env file or ensure they are available in your environment and try again.';
|
|
85
85
|
throw new Error(errorMessage);
|
|
86
86
|
}
|
|
87
|
+
console.log('✅ All required environment variables are set.');
|
|
87
88
|
}
|
|
88
89
|
function analyzePrismaSchema(schemaPath) {
|
|
89
90
|
try {
|
|
@@ -147,6 +148,8 @@ function analyzePrismaSchema(schemaPath) {
|
|
|
147
148
|
*/
|
|
148
149
|
async function configurePrismaTablesForSuparisma(schemaPath) {
|
|
149
150
|
try {
|
|
151
|
+
// COMPLETELY BYPASS NORMAL OPERATION FOR SIMPLICITY
|
|
152
|
+
console.log('🔧 Using direct SQL approach to avoid PostgreSQL case sensitivity issues...');
|
|
150
153
|
// Load environment variables
|
|
151
154
|
dotenv.config();
|
|
152
155
|
// Get direct PostgreSQL connection URL
|
|
@@ -159,6 +162,7 @@ async function configurePrismaTablesForSuparisma(schemaPath) {
|
|
|
159
162
|
const pg = await Promise.resolve().then(() => __importStar(require('pg')));
|
|
160
163
|
const { Pool } = pg.default || pg;
|
|
161
164
|
const pool = new Pool({ connectionString: process.env.DIRECT_URL });
|
|
165
|
+
console.log('🔌 Connected to PostgreSQL database for configuration.');
|
|
162
166
|
const { rows: allTables } = await pool.query(`SELECT table_name FROM information_schema.tables WHERE table_schema = 'public'`);
|
|
163
167
|
for (const model of modelInfos) {
|
|
164
168
|
const matchingTable = allTables.find((t) => t.table_name.toLowerCase() === model.tableName.toLowerCase());
|
|
@@ -167,20 +171,29 @@ async function configurePrismaTablesForSuparisma(schemaPath) {
|
|
|
167
171
|
continue;
|
|
168
172
|
}
|
|
169
173
|
const actualTableName = matchingTable.table_name;
|
|
174
|
+
console.log(`Processing model ${model.name} (table: "${actualTableName}")`);
|
|
170
175
|
// Realtime setup (existing logic)
|
|
171
176
|
if (model.enableRealtime) {
|
|
172
177
|
const alterPublicationQuery = `ALTER PUBLICATION supabase_realtime ADD TABLE "${actualTableName}";`;
|
|
173
178
|
try {
|
|
174
179
|
await pool.query(alterPublicationQuery);
|
|
180
|
+
console.log(` ✅ Added "${actualTableName}" to supabase_realtime publication for real-time updates.`);
|
|
175
181
|
}
|
|
176
182
|
catch (err) {
|
|
177
|
-
if (
|
|
183
|
+
if (err.message.includes('already member')) {
|
|
184
|
+
console.log(` ℹ️ Table "${actualTableName}" was already in supabase_realtime publication.`);
|
|
185
|
+
}
|
|
186
|
+
else {
|
|
178
187
|
console.error(` ❌ Failed to add "${actualTableName}" to supabase_realtime: ${err.message}`);
|
|
179
188
|
}
|
|
180
189
|
}
|
|
181
190
|
}
|
|
191
|
+
else {
|
|
192
|
+
console.log(` ℹ️ Realtime disabled for model ${model.name}.`);
|
|
193
|
+
}
|
|
182
194
|
// Search setup
|
|
183
195
|
if (model.searchFields.length > 0) {
|
|
196
|
+
console.log(` 🔍 Setting up full-text search for model ${model.name}:`);
|
|
184
197
|
const { rows: columns } = await pool.query(`SELECT column_name FROM information_schema.columns WHERE table_schema = 'public' AND table_name = $1`, [actualTableName]);
|
|
185
198
|
for (const searchField of model.searchFields) {
|
|
186
199
|
const matchingColumn = columns.find((c) => c.column_name.toLowerCase() === searchField.name.toLowerCase());
|
|
@@ -191,6 +204,7 @@ async function configurePrismaTablesForSuparisma(schemaPath) {
|
|
|
191
204
|
const actualColumnName = matchingColumn.column_name;
|
|
192
205
|
const functionName = `search_${actualTableName.toLowerCase()}_by_${actualColumnName.toLowerCase()}_prefix`;
|
|
193
206
|
const indexName = `idx_gin_search_${actualTableName.toLowerCase()}_${actualColumnName.toLowerCase()}`;
|
|
207
|
+
console.log(` ➡️ Configuring field "${actualColumnName}":`);
|
|
194
208
|
try {
|
|
195
209
|
// Create search function
|
|
196
210
|
const createFunctionQuery = `
|
|
@@ -203,6 +217,7 @@ async function configurePrismaTablesForSuparisma(schemaPath) {
|
|
|
203
217
|
END;
|
|
204
218
|
$$ LANGUAGE plpgsql STABLE;`; // Added STABLE for potential performance benefits
|
|
205
219
|
await pool.query(createFunctionQuery);
|
|
220
|
+
console.log(` ✅ Created/Replaced RPC function: "${functionName}"(search_prefix text)`);
|
|
206
221
|
// Create GIN index
|
|
207
222
|
const createIndexQuery = `
|
|
208
223
|
DO $$
|
|
@@ -214,21 +229,39 @@ async function configurePrismaTablesForSuparisma(schemaPath) {
|
|
|
214
229
|
AND indexname = '${indexName}'
|
|
215
230
|
) THEN
|
|
216
231
|
CREATE INDEX "${indexName}" ON "public"."${actualTableName}" USING GIN (to_tsvector('english', "${actualColumnName}"));
|
|
232
|
+
RAISE NOTICE ' ✅ Created GIN index: "${indexName}" on "${actualTableName}"("${actualColumnName}")';
|
|
233
|
+
ELSE
|
|
234
|
+
RAISE NOTICE ' ℹ️ GIN index "${indexName}" on "${actualTableName}"("${actualColumnName}") already exists.';
|
|
217
235
|
END IF;
|
|
218
236
|
END;
|
|
219
237
|
$$;`;
|
|
220
|
-
await pool.query(createIndexQuery);
|
|
238
|
+
const indexResult = await pool.query(createIndexQuery);
|
|
239
|
+
// Output notices from the DO $$ block (PostgreSQL specific)
|
|
240
|
+
if (indexResult.rows.length > 0 && indexResult.rows[0].notice) {
|
|
241
|
+
console.log(indexResult.rows[0].notice.replace(/^NOTICE: /, ''));
|
|
242
|
+
}
|
|
243
|
+
else if (!indexResult.rows.find((r) => r.notice?.includes('Created GIN index'))) {
|
|
244
|
+
// If DO $$ block doesn't emit specific notice for creation and it didn't say exists.
|
|
245
|
+
// This is a fallback log, actual creation/existence is handled by the DO block.
|
|
246
|
+
// The important part is that the index will be there.
|
|
247
|
+
}
|
|
221
248
|
}
|
|
222
249
|
catch (err) {
|
|
223
250
|
console.error(` ❌ Failed to set up search for "${actualTableName}"."${actualColumnName}": ${err.message}`);
|
|
224
251
|
}
|
|
225
252
|
}
|
|
226
253
|
}
|
|
254
|
+
else {
|
|
255
|
+
console.log(` ℹ️ No fields marked with // @enableSearch for model ${model.name}.`);
|
|
256
|
+
}
|
|
257
|
+
console.log('---------------------------------------------------');
|
|
227
258
|
}
|
|
228
259
|
await pool.end();
|
|
260
|
+
console.log('🎉 Database configuration complete.');
|
|
229
261
|
}
|
|
230
262
|
catch (err) {
|
|
231
263
|
console.error('❌ Error during database configuration:', err);
|
|
264
|
+
console.log('⚠️ Hook generation will continue, but database features like search or realtime might not be fully configured.');
|
|
232
265
|
}
|
|
233
266
|
}
|
|
234
267
|
/**
|
|
@@ -236,16 +269,22 @@ async function configurePrismaTablesForSuparisma(schemaPath) {
|
|
|
236
269
|
*/
|
|
237
270
|
async function generateHooks() {
|
|
238
271
|
try {
|
|
272
|
+
console.log('🚀 Starting Suparisma hook generation...');
|
|
239
273
|
checkEnvironmentVariables();
|
|
274
|
+
console.log(`Prisma schema path: ${config_1.PRISMA_SCHEMA_PATH}`);
|
|
275
|
+
console.log(`Output directory: ${config_1.OUTPUT_DIR}`);
|
|
240
276
|
// Delete the entire output directory if it exists to clean up any stale files
|
|
241
277
|
if (fs_1.default.existsSync(config_1.OUTPUT_DIR)) {
|
|
278
|
+
console.log(`🧹 Cleaning up previous generated files in ${config_1.OUTPUT_DIR}...`);
|
|
242
279
|
fs_1.default.rmSync(config_1.OUTPUT_DIR, { recursive: true, force: true });
|
|
280
|
+
console.log(`✅ Removed previous generated directory`);
|
|
243
281
|
}
|
|
244
282
|
// Ensure all specific output directories exist, OUTPUT_DIR is the root and will be created if needed by sub-creations.
|
|
245
283
|
const dirsToEnsure = [config_1.TYPES_DIR, config_1.HOOKS_DIR, config_1.UTILS_DIR];
|
|
246
284
|
dirsToEnsure.forEach(dir => {
|
|
247
285
|
if (!fs_1.default.existsSync(dir)) {
|
|
248
286
|
fs_1.default.mkdirSync(dir, { recursive: true });
|
|
287
|
+
console.log(`Created directory: ${dir}`);
|
|
249
288
|
}
|
|
250
289
|
});
|
|
251
290
|
// Generate Supabase client file (goes to UTILS_DIR)
|
|
@@ -261,6 +300,7 @@ async function generateHooks() {
|
|
|
261
300
|
modelInfos.push(modelInfo);
|
|
262
301
|
}
|
|
263
302
|
(0, indexGenerator_1.generateMainIndexFile)(modelInfos);
|
|
303
|
+
console.log(`✅ Successfully generated all suparisma hooks and types in "${config_1.OUTPUT_DIR}"!`);
|
|
264
304
|
}
|
|
265
305
|
catch (error) {
|
|
266
306
|
console.error('❌ Error generating hooks:', error);
|
package/package.json
CHANGED