suparisma 1.1.0 → 1.1.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -91,9 +91,14 @@ export type FilterOperators<T> = {
91
91
  isEmpty?: T extends Array<any> ? boolean : never;
92
92
  };
93
93
 
94
- // Type for a single field in an advanced where filter
94
+ // Type for a single field in an advanced where filter with OR/AND support
95
95
  export type AdvancedWhereInput<T> = {
96
96
  [K in keyof T]?: T[K] | FilterOperators<T[K]>;
97
+ } & {
98
+ /** Match ANY of the provided conditions */
99
+ OR?: AdvancedWhereInput<T>[];
100
+ /** Match ALL of the provided conditions */
101
+ AND?: AdvancedWhereInput<T>[];
97
102
  };
98
103
 
99
104
  /**
@@ -210,14 +215,26 @@ function compareValues(a: any, b: any, direction: 'asc' | 'desc'): number {
210
215
 
211
216
  /**
212
217
  * Convert a type-safe where filter to Supabase filter string
218
+ * Note: Complex OR/AND operations may not be fully supported in realtime filters
219
+ * and will fall back to client-side filtering
213
220
  */
214
221
  export function buildFilterString<T>(where?: T): string | undefined {
215
222
  if (!where) return undefined;
216
223
 
224
+ const whereObj = where as any;
225
+
226
+ // Check for OR/AND operations - these are complex for realtime filters
227
+ if (whereObj.OR || whereObj.AND) {
228
+ console.log('⚠️ Complex OR/AND filters detected - realtime will use client-side filtering');
229
+ // For complex logical operations, we'll rely on client-side filtering
230
+ // Return undefined to indicate no database-level filter should be applied
231
+ return undefined;
232
+ }
233
+
217
234
  const filters: string[] = [];
218
235
 
219
- for (const [key, value] of Object.entries(where)) {
220
- if (value !== undefined) {
236
+ for (const [key, value] of Object.entries(whereObj)) {
237
+ if (value !== undefined && key !== 'OR' && key !== 'AND') {
221
238
  if (typeof value === 'object' && value !== null) {
222
239
  // Handle advanced operators
223
240
  const advancedOps = value as unknown as FilterOperators<any>;
@@ -301,19 +318,18 @@ export function buildFilterString<T>(where?: T): string | undefined {
301
318
  }
302
319
 
303
320
  /**
304
- * Apply filter to the query builder
321
+ * Apply a single condition group to the query builder
305
322
  */
306
- export function applyFilter<T>(
323
+ function applyConditionGroup<T>(
307
324
  query: SupabaseQueryBuilder,
308
- where: T
325
+ conditions: T
309
326
  ): SupabaseQueryBuilder {
310
- if (!where) return query;
327
+ if (!conditions) return query;
311
328
 
312
329
  let filteredQuery = query;
313
330
 
314
- // Apply each filter condition
315
- for (const [key, value] of Object.entries(where)) {
316
- if (value !== undefined) {
331
+ for (const [key, value] of Object.entries(conditions)) {
332
+ if (value !== undefined && key !== 'OR' && key !== 'AND') {
317
333
  if (typeof value === 'object' && value !== null) {
318
334
  // Handle advanced operators
319
335
  const advancedOps = value as unknown as FilterOperators<any>;
@@ -409,6 +425,209 @@ export function applyFilter<T>(
409
425
  return filteredQuery;
410
426
  }
411
427
 
428
+ /**
429
+ * Apply filter to the query builder with OR/AND support
430
+ */
431
+ export function applyFilter<T>(
432
+ query: SupabaseQueryBuilder,
433
+ where: T
434
+ ): SupabaseQueryBuilder {
435
+ if (!where) return query;
436
+
437
+ const whereObj = where as any;
438
+ let filteredQuery = query;
439
+
440
+ // Handle regular conditions first (these are implicitly AND-ed)
441
+ filteredQuery = applyConditionGroup(filteredQuery, whereObj);
442
+
443
+ // Handle OR conditions
444
+ if (whereObj.OR && Array.isArray(whereObj.OR) && whereObj.OR.length > 0) {
445
+ // @ts-ignore: Supabase typing issue
446
+ filteredQuery = filteredQuery.or(
447
+ whereObj.OR.map((orCondition: any, index: number) => {
448
+ // Convert each OR condition to a filter string
449
+ const orFilters: string[] = [];
450
+
451
+ for (const [key, value] of Object.entries(orCondition)) {
452
+ if (value !== undefined && key !== 'OR' && key !== 'AND') {
453
+ if (typeof value === 'object' && value !== null) {
454
+ const advancedOps = value as unknown as FilterOperators<any>;
455
+
456
+ if ('equals' in advancedOps && advancedOps.equals !== undefined) {
457
+ orFilters.push(\`\${key}.eq.\${advancedOps.equals}\`);
458
+ } else if ('not' in advancedOps && advancedOps.not !== undefined) {
459
+ orFilters.push(\`\${key}.neq.\${advancedOps.not}\`);
460
+ } else if ('gt' in advancedOps && advancedOps.gt !== undefined) {
461
+ orFilters.push(\`\${key}.gt.\${advancedOps.gt}\`);
462
+ } else if ('gte' in advancedOps && advancedOps.gte !== undefined) {
463
+ orFilters.push(\`\${key}.gte.\${advancedOps.gte}\`);
464
+ } else if ('lt' in advancedOps && advancedOps.lt !== undefined) {
465
+ orFilters.push(\`\${key}.lt.\${advancedOps.lt}\`);
466
+ } else if ('lte' in advancedOps && advancedOps.lte !== undefined) {
467
+ orFilters.push(\`\${key}.lte.\${advancedOps.lte}\`);
468
+ } else if ('in' in advancedOps && advancedOps.in?.length) {
469
+ orFilters.push(\`\${key}.in.(\${advancedOps.in.join(',')})\`);
470
+ } else if ('contains' in advancedOps && advancedOps.contains !== undefined) {
471
+ orFilters.push(\`\${key}.ilike.*\${advancedOps.contains}*\`);
472
+ } else if ('startsWith' in advancedOps && advancedOps.startsWith !== undefined) {
473
+ orFilters.push(\`\${key}.ilike.\${advancedOps.startsWith}%\`);
474
+ } else if ('endsWith' in advancedOps && advancedOps.endsWith !== undefined) {
475
+ orFilters.push(\`\${key}.ilike.%\${advancedOps.endsWith}\`);
476
+ } else if ('has' in advancedOps && advancedOps.has !== undefined) {
477
+ orFilters.push(\`\${key}.ov.\${JSON.stringify(advancedOps.has)}\`);
478
+ } else if ('hasEvery' in advancedOps && advancedOps.hasEvery !== undefined) {
479
+ orFilters.push(\`\${key}.cs.\${JSON.stringify(advancedOps.hasEvery)}\`);
480
+ } else if ('hasSome' in advancedOps && advancedOps.hasSome !== undefined) {
481
+ orFilters.push(\`\${key}.ov.\${JSON.stringify(advancedOps.hasSome)}\`);
482
+ } else if ('isEmpty' in advancedOps && advancedOps.isEmpty !== undefined) {
483
+ if (advancedOps.isEmpty) {
484
+ orFilters.push(\`\${key}.eq.{}\`);
485
+ } else {
486
+ orFilters.push(\`\${key}.neq.{}\`);
487
+ }
488
+ }
489
+ } else {
490
+ // Simple equality
491
+ orFilters.push(\`\${key}.eq.\${value}\`);
492
+ }
493
+ }
494
+ }
495
+
496
+ return orFilters.join(',');
497
+ }).join(',')
498
+ );
499
+ }
500
+
501
+ // Handle AND conditions (these are applied in addition to regular conditions)
502
+ if (whereObj.AND && Array.isArray(whereObj.AND) && whereObj.AND.length > 0) {
503
+ for (const andCondition of whereObj.AND) {
504
+ filteredQuery = applyConditionGroup(filteredQuery, andCondition);
505
+ }
506
+ }
507
+
508
+ return filteredQuery;
509
+ }
510
+
511
+ /**
512
+ * Evaluate if a record matches filter criteria (including OR/AND logic)
513
+ */
514
+ function matchesFilter<T>(record: any, filter: T): boolean {
515
+ if (!filter) return true;
516
+
517
+ const filterObj = filter as any;
518
+
519
+ // Separate regular conditions from OR/AND
520
+ const hasOr = filterObj.OR && Array.isArray(filterObj.OR) && filterObj.OR.length > 0;
521
+ const hasAnd = filterObj.AND && Array.isArray(filterObj.AND) && filterObj.AND.length > 0;
522
+
523
+ // Check regular field conditions (these are implicitly AND-ed)
524
+ const regularConditions: any = {};
525
+ for (const [key, value] of Object.entries(filterObj)) {
526
+ if (value !== undefined && key !== 'OR' && key !== 'AND') {
527
+ regularConditions[key] = value;
528
+ }
529
+ }
530
+
531
+ // Helper function to check individual field conditions
532
+ const checkFieldConditions = (conditions: any): boolean => {
533
+ for (const [key, value] of Object.entries(conditions)) {
534
+ if (value !== undefined) {
535
+ const recordValue = record[key];
536
+
537
+ if (typeof value === 'object' && value !== null) {
538
+ // Handle advanced operators
539
+ const advancedOps = value as unknown as FilterOperators<any>;
540
+
541
+ if ('equals' in advancedOps && advancedOps.equals !== undefined) {
542
+ if (recordValue !== advancedOps.equals) return false;
543
+ }
544
+
545
+ if ('not' in advancedOps && advancedOps.not !== undefined) {
546
+ if (recordValue === advancedOps.not) return false;
547
+ }
548
+
549
+ if ('gt' in advancedOps && advancedOps.gt !== undefined) {
550
+ if (!(recordValue > advancedOps.gt)) return false;
551
+ }
552
+
553
+ if ('gte' in advancedOps && advancedOps.gte !== undefined) {
554
+ if (!(recordValue >= advancedOps.gte)) return false;
555
+ }
556
+
557
+ if ('lt' in advancedOps && advancedOps.lt !== undefined) {
558
+ if (!(recordValue < advancedOps.lt)) return false;
559
+ }
560
+
561
+ if ('lte' in advancedOps && advancedOps.lte !== undefined) {
562
+ if (!(recordValue <= advancedOps.lte)) return false;
563
+ }
564
+
565
+ if ('in' in advancedOps && advancedOps.in?.length) {
566
+ if (!advancedOps.in.includes(recordValue)) return false;
567
+ }
568
+
569
+ if ('contains' in advancedOps && advancedOps.contains !== undefined) {
570
+ if (!recordValue || !String(recordValue).toLowerCase().includes(String(advancedOps.contains).toLowerCase())) return false;
571
+ }
572
+
573
+ if ('startsWith' in advancedOps && advancedOps.startsWith !== undefined) {
574
+ if (!recordValue || !String(recordValue).toLowerCase().startsWith(String(advancedOps.startsWith).toLowerCase())) return false;
575
+ }
576
+
577
+ if ('endsWith' in advancedOps && advancedOps.endsWith !== undefined) {
578
+ if (!recordValue || !String(recordValue).toLowerCase().endsWith(String(advancedOps.endsWith).toLowerCase())) return false;
579
+ }
580
+
581
+ // Array-specific operators
582
+ if ('has' in advancedOps && advancedOps.has !== undefined) {
583
+ if (!Array.isArray(recordValue) || !advancedOps.has.some((item: any) => recordValue.includes(item))) return false;
584
+ }
585
+
586
+ if ('hasEvery' in advancedOps && advancedOps.hasEvery !== undefined) {
587
+ if (!Array.isArray(recordValue) || !advancedOps.hasEvery.every((item: any) => recordValue.includes(item))) return false;
588
+ }
589
+
590
+ if ('hasSome' in advancedOps && advancedOps.hasSome !== undefined) {
591
+ if (!Array.isArray(recordValue) || !advancedOps.hasSome.some((item: any) => recordValue.includes(item))) return false;
592
+ }
593
+
594
+ if ('isEmpty' in advancedOps && advancedOps.isEmpty !== undefined) {
595
+ const isEmpty = !Array.isArray(recordValue) || recordValue.length === 0;
596
+ if (isEmpty !== advancedOps.isEmpty) return false;
597
+ }
598
+ } else {
599
+ // Simple equality
600
+ if (recordValue !== value) return false;
601
+ }
602
+ }
603
+ }
604
+ return true;
605
+ };
606
+
607
+ // All conditions that must be true
608
+ const conditions: boolean[] = [];
609
+
610
+ // Regular field conditions (implicitly AND-ed)
611
+ if (Object.keys(regularConditions).length > 0) {
612
+ conditions.push(checkFieldConditions(regularConditions));
613
+ }
614
+
615
+ // AND conditions (all must be true)
616
+ if (hasAnd) {
617
+ const andResult = filterObj.AND.every((andCondition: any) => matchesFilter(record, andCondition));
618
+ conditions.push(andResult);
619
+ }
620
+
621
+ // OR conditions (at least one must be true)
622
+ if (hasOr) {
623
+ const orResult = filterObj.OR.some((orCondition: any) => matchesFilter(record, orCondition));
624
+ conditions.push(orResult);
625
+ }
626
+
627
+ // All conditions must be true
628
+ return conditions.every(condition => condition);
629
+ }
630
+
412
631
  /**
413
632
  * Apply order by to the query builder
414
633
  */
@@ -900,7 +1119,7 @@ export function createSuparismaHook<
900
1119
  }
901
1120
  }, []);
902
1121
 
903
- // Set up realtime subscription for the list
1122
+ // Set up realtime subscription for the list - ONCE and listen to ALL events
904
1123
  useEffect(() => {
905
1124
  if (!realtime) return;
906
1125
 
@@ -912,44 +1131,14 @@ export function createSuparismaHook<
912
1131
 
913
1132
  const channelId = channelName || \`changes_to_\${tableName}_\${Math.random().toString(36).substring(2, 15)}\`;
914
1133
 
915
- // Check if we have complex array filters that should be handled client-side only
916
- let hasComplexArrayFilters = false;
917
- if (where) {
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;
925
- }
926
- }
927
- }
928
- }
929
-
930
- // For complex array filters, use no database filter and rely on client-side filtering
931
- // For simple filters, use database-level filtering
1134
+ // ALWAYS listen to ALL events and filter client-side for maximum reliability
932
1135
  let subscriptionConfig: any = {
933
1136
  event: '*',
934
1137
  schema: 'public',
935
1138
  table: tableName,
936
1139
  };
937
1140
 
938
- if (hasComplexArrayFilters) {
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\`);
941
- } else if (where) {
942
- // Include filter for simple operations
943
- const filter = buildFilterString(where);
944
- if (filter) {
945
- subscriptionConfig.filter = filter;
946
- }
947
- console.log(\`Setting up subscription for \${tableName} with database filter: \${filter}\`);
948
- } else if (realtimeFilter) {
949
- // Use custom realtime filter if provided
950
- subscriptionConfig.filter = realtimeFilter;
951
- console.log(\`Setting up subscription for \${tableName} with custom filter: \${realtimeFilter}\`);
952
- }
1141
+ console.log(\`Setting up subscription for \${tableName} - listening to ALL events (client-side filtering)\`);
953
1142
 
954
1143
  const channel = supabase
955
1144
  .channel(channelId)
@@ -979,56 +1168,10 @@ export function createSuparismaHook<
979
1168
  console.log(\`Processing INSERT for \${tableName}\`, { newRecord });
980
1169
 
981
1170
  // ALWAYS check if this record matches our filter client-side
982
- // This is especially important for complex array filters
983
- if (currentWhere) { // Use ref value
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
- }
1027
-
1028
- if (!matchesFilter) {
1171
+ // This is especially important for complex OR/AND/array filters
1172
+ if (currentWhere && !matchesFilter(newRecord, currentWhere)) {
1029
1173
  console.log('New record does not match filter criteria, skipping');
1030
1174
  return prev;
1031
- }
1032
1175
  }
1033
1176
 
1034
1177
  // Check if record already exists (avoid duplicates)
@@ -1108,56 +1251,12 @@ export function createSuparismaHook<
1108
1251
  const updatedRecord = payload.new as TWithRelations;
1109
1252
 
1110
1253
  // Check if the updated record still matches our current filter
1111
- if (currentWhere) {
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
- }
1152
-
1153
- // If the updated record doesn't match the filter, remove it from the list
1154
- if (!matchesFilter) {
1254
+ if (currentWhere && !matchesFilter(updatedRecord, currentWhere)) {
1155
1255
  console.log('Updated record no longer matches filter, removing from list');
1156
1256
  return prev.filter((item) =>
1157
1257
  // @ts-ignore: Supabase typing issue
1158
1258
  !('id' in item && 'id' in updatedRecord && item.id === updatedRecord.id)
1159
1259
  );
1160
- }
1161
1260
  }
1162
1261
 
1163
1262
  const newData = prev.map((item) =>
@@ -1324,7 +1423,7 @@ export function createSuparismaHook<
1324
1423
  searchTimeoutRef.current = null;
1325
1424
  }
1326
1425
  };
1327
- }, [realtime, channelName, tableName, where, initialLoadRef]); // Added 'where' back so subscription updates when filter changes
1426
+ }, [realtime, channelName, tableName]); // NEVER include 'where' - subscription should persist
1328
1427
 
1329
1428
  // Create a memoized options object to prevent unnecessary re-renders
1330
1429
  const optionsRef = useRef({ where, orderBy, limit, offset });
@@ -1353,7 +1452,7 @@ export function createSuparismaHook<
1353
1452
  return false;
1354
1453
  }, [where, orderBy, limit, offset]);
1355
1454
 
1356
- // Load initial data based on options
1455
+ // Load initial data and refetch when options change (BUT NEVER TOUCH SUBSCRIPTION)
1357
1456
  useEffect(() => {
1358
1457
  // Skip if search is active
1359
1458
  if (isSearchingRef.current) return;
@@ -1362,7 +1461,7 @@ export function createSuparismaHook<
1362
1461
  if (initialLoadRef.current) {
1363
1462
  // Only reload if options have changed significantly
1364
1463
  if (optionsChanged()) {
1365
- console.log(\`Options changed for \${tableName}, reloading data\`);
1464
+ console.log(\`Options changed for \${tableName}, refetching data (subscription stays alive)\`);
1366
1465
  findMany({
1367
1466
  where,
1368
1467
  orderBy,
@@ -219,6 +219,7 @@ export type ${modelName}UpdateInput = Partial<${modelName}CreateInput>;
219
219
  /**
220
220
  * Filter type for querying ${modelName} records.
221
221
  * You can filter by any field in the model using equality or advanced filter operators.
222
+ * Supports OR and AND logical operations for complex queries.
222
223
  *
223
224
  * @example
224
225
  * // Basic filtering
@@ -250,6 +251,40 @@ ${withRelationsProps
250
251
  * });
251
252
  *
252
253
  * @example
254
+ * // OR conditions - match ANY condition
255
+ * ${modelName.toLowerCase()}.findMany({
256
+ * where: {
257
+ * OR: [
258
+ * { ${withRelationsProps.slice(0, 1).map(p => p.trim().split(':')[0].trim())[0] || 'field1'}: "value1" },
259
+ * { ${withRelationsProps.slice(1, 2).map(p => p.trim().split(':')[0].trim())[0] || 'field2'}: { contains: "value2" } }
260
+ * ]
261
+ * }
262
+ * });
263
+ *
264
+ * @example
265
+ * // AND conditions - match ALL conditions
266
+ * ${modelName.toLowerCase()}.findMany({
267
+ * where: {
268
+ * AND: [
269
+ * { ${withRelationsProps.slice(0, 1).map(p => p.trim().split(':')[0].trim())[0] || 'field1'}: "value1" },
270
+ * { ${withRelationsProps.slice(1, 2).map(p => p.trim().split(':')[0].trim())[0] || 'field2'}: { gt: 100 } }
271
+ * ]
272
+ * }
273
+ * });
274
+ *
275
+ * @example
276
+ * // Complex nested logic
277
+ * ${modelName.toLowerCase()}.findMany({
278
+ * where: {
279
+ * active: true, // Regular condition (implicit AND)
280
+ * OR: [
281
+ * { role: "admin" },
282
+ * { role: "moderator" }
283
+ * ]
284
+ * }
285
+ * });
286
+ *
287
+ * @example
253
288
  * // Array filtering (for array fields)
254
289
  * ${modelName.toLowerCase()}.findMany({
255
290
  * where: {
@@ -286,6 +321,11 @@ ${model.fields
286
321
  return '';
287
322
  }).filter(Boolean))
288
323
  .join('\n')}
324
+ } & {
325
+ /** Match ANY of the provided conditions */
326
+ OR?: ${modelName}WhereInput[];
327
+ /** Match ALL of the provided conditions */
328
+ AND?: ${modelName}WhereInput[];
289
329
  };
290
330
 
291
331
  /**
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "suparisma",
3
- "version": "1.1.0",
3
+ "version": "1.1.1",
4
4
  "description": "Opinionated typesafe React realtime CRUD hooks generator for all your Supabase tables, powered by Prisma.",
5
5
  "main": "dist/index.js",
6
6
  "repository": {