suparisma 1.0.7 → 1.0.8
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 +211 -0
- package/dist/generators/coreGenerator.js +323 -115
- package/dist/generators/typeGenerator.js +65 -33
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -23,6 +23,7 @@ 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)
|
|
26
27
|
- [Sorting Data](#sorting-data)
|
|
27
28
|
- [Pagination](#pagination)
|
|
28
29
|
- [Search Functionality](#search-functionality)
|
|
@@ -61,6 +62,7 @@ Suparisma bridges this gap by:
|
|
|
61
62
|
- 🔄 **Real-time updates by default** for all tables (with opt-out capability)
|
|
62
63
|
- 🔒 **Type-safe interfaces** for all database operations
|
|
63
64
|
- 🔍 **Full-text search** with configurable annotations *(currently under maintenance)*
|
|
65
|
+
- ⚡ **Advanced filtering** with OR/AND conditions for complex queries
|
|
64
66
|
- 🔢 **Pagination and sorting** built into every hook
|
|
65
67
|
- 🧩 **Prisma-like API** that feels familiar if you already use Prisma
|
|
66
68
|
- 📱 **Works with any React framework** including Next.js, Remix, etc.
|
|
@@ -580,6 +582,215 @@ function ProductFilter() {
|
|
|
580
582
|
}
|
|
581
583
|
```
|
|
582
584
|
|
|
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
|
+
|
|
583
794
|
### Sorting Data
|
|
584
795
|
|
|
585
796
|
Sort data using Prisma-like ordering:
|
|
@@ -94,6 +94,21 @@ 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>[];
|
|
97
112
|
};
|
|
98
113
|
|
|
99
114
|
/**
|
|
@@ -177,6 +192,19 @@ export type SearchState = {
|
|
|
177
192
|
clearQueries: () => void;
|
|
178
193
|
};
|
|
179
194
|
|
|
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
|
+
|
|
180
208
|
/**
|
|
181
209
|
* Compare two values for sorting with proper type handling
|
|
182
210
|
*/
|
|
@@ -215,9 +243,73 @@ export function buildFilterString<T>(where?: T): string | undefined {
|
|
|
215
243
|
if (!where) return undefined;
|
|
216
244
|
|
|
217
245
|
const filters: string[] = [];
|
|
246
|
+
const orConditions: string[] = [];
|
|
218
247
|
|
|
219
248
|
for (const [key, value] of Object.entries(where)) {
|
|
220
249
|
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
|
+
|
|
221
313
|
if (typeof value === 'object' && value !== null) {
|
|
222
314
|
// Handle advanced operators
|
|
223
315
|
const advancedOps = value as unknown as FilterOperators<any>;
|
|
@@ -265,19 +357,22 @@ export function buildFilterString<T>(where?: T): string | undefined {
|
|
|
265
357
|
// Array-specific operators
|
|
266
358
|
if ('has' in advancedOps && advancedOps.has !== undefined) {
|
|
267
359
|
// Array contains ANY of the specified items (overlaps)
|
|
268
|
-
|
|
360
|
+
// Use PostgreSQL array syntax: {item1,item2} not ["item1","item2"]
|
|
361
|
+
const arrayValue = \`{\${advancedOps.has.map((item: any) => escapeFilterValue(item)).join(',')}}\`;
|
|
269
362
|
filters.push(\`\${key}=ov.\${arrayValue}\`);
|
|
270
363
|
}
|
|
271
364
|
|
|
272
365
|
if ('hasEvery' in advancedOps && advancedOps.hasEvery !== undefined) {
|
|
273
366
|
// Array contains ALL of the specified items (contains)
|
|
274
|
-
|
|
367
|
+
// Use PostgreSQL array syntax: {item1,item2} not ["item1","item2"]
|
|
368
|
+
const arrayValue = \`{\${advancedOps.hasEvery.map((item: any) => escapeFilterValue(item)).join(',')}}\`;
|
|
275
369
|
filters.push(\`\${key}=cs.\${arrayValue}\`);
|
|
276
370
|
}
|
|
277
371
|
|
|
278
372
|
if ('hasSome' in advancedOps && advancedOps.hasSome !== undefined) {
|
|
279
373
|
// Array contains ANY of the specified items (overlaps)
|
|
280
|
-
|
|
374
|
+
// Use PostgreSQL array syntax: {item1,item2} not ["item1","item2"]
|
|
375
|
+
const arrayValue = \`{\${advancedOps.hasSome.map((item: any) => escapeFilterValue(item)).join(',')}}\`;
|
|
281
376
|
filters.push(\`\${key}=ov.\${arrayValue}\`);
|
|
282
377
|
}
|
|
283
378
|
|
|
@@ -297,7 +392,18 @@ export function buildFilterString<T>(where?: T): string | undefined {
|
|
|
297
392
|
}
|
|
298
393
|
}
|
|
299
394
|
|
|
300
|
-
|
|
395
|
+
// Combine filters with OR conditions
|
|
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;
|
|
301
407
|
}
|
|
302
408
|
|
|
303
409
|
/**
|
|
@@ -314,6 +420,69 @@ export function applyFilter<T>(
|
|
|
314
420
|
// Apply each filter condition
|
|
315
421
|
for (const [key, value] of Object.entries(where)) {
|
|
316
422
|
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
|
+
|
|
317
486
|
if (typeof value === 'object' && value !== null) {
|
|
318
487
|
// Handle advanced operators
|
|
319
488
|
const advancedOps = value as unknown as FilterOperators<any>;
|
|
@@ -445,6 +614,112 @@ export function applyOrderBy<T>(
|
|
|
445
614
|
return orderedQuery;
|
|
446
615
|
}
|
|
447
616
|
|
|
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
|
+
console.log(\`Filter mismatch on \${key}\`, { expected: value, actual: record[key as keyof typeof record] });
|
|
715
|
+
return false;
|
|
716
|
+
}
|
|
717
|
+
}
|
|
718
|
+
}
|
|
719
|
+
|
|
720
|
+
return true;
|
|
721
|
+
}
|
|
722
|
+
|
|
448
723
|
/**
|
|
449
724
|
* Core hook factory function that creates a type-safe realtime hook for a specific model.
|
|
450
725
|
* This is the foundation for all Suparisma hooks.
|
|
@@ -488,12 +763,23 @@ export function createSuparismaHook<
|
|
|
488
763
|
* const users = useSuparismaUser();
|
|
489
764
|
* const { data, loading, error } = users;
|
|
490
765
|
*
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
766
|
+
* @example
|
|
767
|
+
* // With filtering
|
|
768
|
+
* const users = useSuparismaUser({
|
|
769
|
+
* where: { role: 'admin' },
|
|
770
|
+
* orderBy: { created_at: 'desc' }
|
|
771
|
+
* });
|
|
772
|
+
*
|
|
773
|
+
* @example
|
|
774
|
+
* // With OR conditions for search
|
|
775
|
+
* const users = useSuparismaUser({
|
|
776
|
+
* where: {
|
|
777
|
+
* OR: [
|
|
778
|
+
* { name: { contains: "john" } },
|
|
779
|
+
* { email: { contains: "john" } }
|
|
780
|
+
* ]
|
|
781
|
+
* }
|
|
782
|
+
* });
|
|
497
783
|
*/
|
|
498
784
|
return function useSuparismaHook(options: SuparismaOptions<TWhereInput, TOrderByInput> = {}) {
|
|
499
785
|
const {
|
|
@@ -704,19 +990,7 @@ export function createSuparismaHook<
|
|
|
704
990
|
|
|
705
991
|
// Apply any where conditions client-side
|
|
706
992
|
if (where) {
|
|
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
|
+
results = results.filter((item) => clientSideFilterCheck(item, where));
|
|
720
994
|
}
|
|
721
995
|
|
|
722
996
|
// Set count directly for search results
|
|
@@ -912,19 +1186,33 @@ export function createSuparismaHook<
|
|
|
912
1186
|
|
|
913
1187
|
const channelId = channelName || \`changes_to_\${tableName}_\${Math.random().toString(36).substring(2, 15)}\`;
|
|
914
1188
|
|
|
915
|
-
// Check if we have complex array filters that should be handled client-side only
|
|
1189
|
+
// Check if we have complex array filters or OR/AND conditions that should be handled client-side only
|
|
916
1190
|
let hasComplexArrayFilters = false;
|
|
917
1191
|
if (where) {
|
|
918
|
-
|
|
919
|
-
|
|
920
|
-
|
|
921
|
-
|
|
922
|
-
|
|
923
|
-
|
|
924
|
-
|
|
1192
|
+
const checkForComplexFilters = (whereClause: any): boolean => {
|
|
1193
|
+
for (const [key, value] of Object.entries(whereClause)) {
|
|
1194
|
+
// OR/AND conditions require client-side filtering
|
|
1195
|
+
if (key === 'OR' || key === 'AND') {
|
|
1196
|
+
return true;
|
|
1197
|
+
}
|
|
1198
|
+
|
|
1199
|
+
if (typeof value === 'object' && value !== null) {
|
|
1200
|
+
// Check if it's an array of conditions (nested OR/AND)
|
|
1201
|
+
if (Array.isArray(value)) {
|
|
1202
|
+
return true;
|
|
1203
|
+
}
|
|
1204
|
+
|
|
1205
|
+
const advancedOps = value as any;
|
|
1206
|
+
// Check for complex array operators
|
|
1207
|
+
if ('has' in advancedOps || 'hasEvery' in advancedOps || 'hasSome' in advancedOps || 'isEmpty' in advancedOps) {
|
|
1208
|
+
return true;
|
|
1209
|
+
}
|
|
925
1210
|
}
|
|
926
1211
|
}
|
|
927
|
-
|
|
1212
|
+
return false;
|
|
1213
|
+
};
|
|
1214
|
+
|
|
1215
|
+
hasComplexArrayFilters = checkForComplexFilters(where);
|
|
928
1216
|
}
|
|
929
1217
|
|
|
930
1218
|
// For complex array filters, use no database filter and rely on client-side filtering
|
|
@@ -981,49 +1269,7 @@ export function createSuparismaHook<
|
|
|
981
1269
|
// ALWAYS check if this record matches our filter client-side
|
|
982
1270
|
// This is especially important for complex array filters
|
|
983
1271
|
if (currentWhere) { // Use ref value
|
|
984
|
-
|
|
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
|
-
}
|
|
1272
|
+
const matchesFilter = clientSideFilterCheck(newRecord, currentWhere);
|
|
1027
1273
|
|
|
1028
1274
|
if (!matchesFilter) {
|
|
1029
1275
|
console.log('New record does not match filter criteria, skipping');
|
|
@@ -1109,46 +1355,7 @@ export function createSuparismaHook<
|
|
|
1109
1355
|
|
|
1110
1356
|
// Check if the updated record still matches our current filter
|
|
1111
1357
|
if (currentWhere) {
|
|
1112
|
-
|
|
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
|
-
}
|
|
1358
|
+
const matchesFilter = clientSideFilterCheck(updatedRecord, currentWhere);
|
|
1152
1359
|
|
|
1153
1360
|
// If the updated record doesn't match the filter, remove it from the list
|
|
1154
1361
|
if (!matchesFilter) {
|
|
@@ -1874,6 +2081,7 @@ export function createSuparismaHook<
|
|
|
1874
2081
|
: api;
|
|
1875
2082
|
};
|
|
1876
2083
|
}
|
|
2084
|
+
|
|
1877
2085
|
`; // Ensure template literal is closed
|
|
1878
2086
|
// Output to the UTILS_DIR
|
|
1879
2087
|
const outputPath = path_1.default.join(config_1.UTILS_DIR, 'core.ts');
|
|
@@ -118,40 +118,44 @@ 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
|
-
|
|
121
|
+
try {
|
|
122
|
+
// Get the zod schemas file path from environment variable
|
|
123
|
+
const zodSchemasPath = process.env.ZOD_SCHEMAS_FILE_PATH || '../commonTypes';
|
|
124
|
+
// Add custom imports with environment variable path, but make them conditional
|
|
125
|
+
const importStatements = model.zodImports
|
|
126
|
+
.map(zodImport => {
|
|
127
|
+
// Extract the types from the original import statement
|
|
128
|
+
const typeMatch = zodImport.importStatement.match(/import\s+{\s*([^}]+)\s*}\s+from/);
|
|
129
|
+
if (typeMatch) {
|
|
130
|
+
const types = typeMatch[1].trim();
|
|
131
|
+
return `// Optional: install zod and create the commonTypes file if you want type safety for JSON fields\n// import { ${types} } from '${zodSchemasPath}'`;
|
|
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';
|
|
133
153
|
}
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
.
|
|
138
|
-
|
|
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';
|
|
154
|
+
}
|
|
155
|
+
catch (error) {
|
|
156
|
+
// If there's any error with zod imports, just use a fallback
|
|
157
|
+
console.warn('Warning: Skipping custom zod imports due to configuration issues');
|
|
158
|
+
customImports = '';
|
|
155
159
|
}
|
|
156
160
|
}
|
|
157
161
|
// Generate the type content with TSDoc comments
|
|
@@ -264,6 +268,28 @@ ${withRelationsProps
|
|
|
264
268
|
.join(',\n')}
|
|
265
269
|
* }
|
|
266
270
|
* });
|
|
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
|
+
* });
|
|
267
293
|
*/
|
|
268
294
|
export type ${modelName}WhereInput = {
|
|
269
295
|
${model.fields
|
|
@@ -285,6 +311,12 @@ ${model.fields
|
|
|
285
311
|
}
|
|
286
312
|
return '';
|
|
287
313
|
}).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
|
+
])
|
|
288
320
|
.join('\n')}
|
|
289
321
|
};
|
|
290
322
|
|
package/package.json
CHANGED