suparisma 1.0.4 → 1.0.5
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 +82 -1
- package/dist/generators/coreGenerator.js +148 -19
- package/dist/parser.js +16 -2
- package/package.json +1 -1
- package/prisma/schema.prisma +10 -0
package/README.md
CHANGED
|
@@ -21,6 +21,8 @@ A powerful, typesafe React hook generator for Supabase, driven by your Prisma sc
|
|
|
21
21
|
- [Basic CRUD Operations](#basic-crud-operations)
|
|
22
22
|
- [Realtime Updates](#realtime-updates)
|
|
23
23
|
- [Filtering Data](#filtering-data)
|
|
24
|
+
- [⚠️ IMPORTANT: Using Dynamic Filters with React](#️-important-using-dynamic-filters-with-react)
|
|
25
|
+
- [Array Filtering](#array-filtering)
|
|
24
26
|
- [Sorting Data](#sorting-data)
|
|
25
27
|
- [Pagination](#pagination)
|
|
26
28
|
- [Search Functionality](#search-functionality)
|
|
@@ -58,7 +60,7 @@ Suparisma bridges this gap by:
|
|
|
58
60
|
- 🚀 **Auto-generated React hooks** based on your Prisma schema
|
|
59
61
|
- 🔄 **Real-time updates by default** for all tables (with opt-out capability)
|
|
60
62
|
- 🔒 **Type-safe interfaces** for all database operations
|
|
61
|
-
- 🔍 **Full-text search** with configurable annotations
|
|
63
|
+
- 🔍 **Full-text search** with configurable annotations *(currently under maintenance)*
|
|
62
64
|
- 🔢 **Pagination and sorting** built into every hook
|
|
63
65
|
- 🧩 **Prisma-like API** that feels familiar if you already use Prisma
|
|
64
66
|
- 📱 **Works with any React framework** including Next.js, Remix, etc.
|
|
@@ -303,6 +305,83 @@ const { data } = useSuparisma.thing({
|
|
|
303
305
|
});
|
|
304
306
|
```
|
|
305
307
|
|
|
308
|
+
### ⚠️ IMPORTANT: Using Dynamic Filters with React
|
|
309
|
+
|
|
310
|
+
**You MUST use `useMemo` for dynamic where filters to prevent constant re-subscriptions!**
|
|
311
|
+
|
|
312
|
+
When creating `where` filters based on state variables, React will create a new object reference on every render, causing the realtime subscription to restart constantly and **breaking realtime updates**.
|
|
313
|
+
|
|
314
|
+
❌ **WRONG - This breaks realtime:**
|
|
315
|
+
```tsx
|
|
316
|
+
function MyComponent() {
|
|
317
|
+
const [filter, setFilter] = useState("active");
|
|
318
|
+
|
|
319
|
+
const { data } = useSuparisma.thing({
|
|
320
|
+
where: filter ? { status: filter } : undefined // ❌ New object every render!
|
|
321
|
+
});
|
|
322
|
+
}
|
|
323
|
+
```
|
|
324
|
+
|
|
325
|
+
✅ **CORRECT - Use useMemo:**
|
|
326
|
+
```tsx
|
|
327
|
+
import { useMemo } from 'react';
|
|
328
|
+
|
|
329
|
+
function MyComponent() {
|
|
330
|
+
const [filter, setFilter] = useState("active");
|
|
331
|
+
const [arrayFilter, setArrayFilter] = useState(["item1"]);
|
|
332
|
+
|
|
333
|
+
// Create stable object reference that only changes when dependencies change
|
|
334
|
+
const whereFilter = useMemo(() => {
|
|
335
|
+
if (filter) {
|
|
336
|
+
return { status: filter };
|
|
337
|
+
}
|
|
338
|
+
return undefined;
|
|
339
|
+
}, [filter]); // Only recreate when filter actually changes
|
|
340
|
+
|
|
341
|
+
const { data } = useSuparisma.thing({
|
|
342
|
+
where: whereFilter // ✅ Stable reference!
|
|
343
|
+
});
|
|
344
|
+
}
|
|
345
|
+
```
|
|
346
|
+
|
|
347
|
+
✅ **Complex example with multiple filters:**
|
|
348
|
+
```tsx
|
|
349
|
+
const whereFilter = useMemo(() => {
|
|
350
|
+
if (arrayFilterValue && arrayOperator) {
|
|
351
|
+
return {
|
|
352
|
+
tags: arrayOperator === 'has'
|
|
353
|
+
? { has: [arrayFilterValue] }
|
|
354
|
+
: arrayOperator === 'hasEvery'
|
|
355
|
+
? { hasEvery: ["required", "tag", arrayFilterValue] }
|
|
356
|
+
: { isEmpty: false }
|
|
357
|
+
};
|
|
358
|
+
} else if (statusFilter) {
|
|
359
|
+
return { status: statusFilter };
|
|
360
|
+
}
|
|
361
|
+
return undefined;
|
|
362
|
+
}, [arrayFilterValue, arrayOperator, statusFilter]); // Dependencies
|
|
363
|
+
|
|
364
|
+
const { data } = useSuparisma.thing({ where: whereFilter });
|
|
365
|
+
```
|
|
366
|
+
|
|
367
|
+
**Why this matters:**
|
|
368
|
+
- Without `useMemo`, the subscription restarts on EVERY render
|
|
369
|
+
- This causes realtime events to be lost during reconnection
|
|
370
|
+
- You'll see constant "Unsubscribing/Subscribing" messages in the console
|
|
371
|
+
- Realtime updates will appear to be broken
|
|
372
|
+
|
|
373
|
+
**The same applies to `orderBy` if it's dynamic:**
|
|
374
|
+
```tsx
|
|
375
|
+
const orderByConfig = useMemo(() => ({
|
|
376
|
+
[sortField]: sortDirection
|
|
377
|
+
}), [sortField, sortDirection]);
|
|
378
|
+
|
|
379
|
+
const { data } = useSuparisma.thing({
|
|
380
|
+
where: whereFilter,
|
|
381
|
+
orderBy: orderByConfig
|
|
382
|
+
});
|
|
383
|
+
```
|
|
384
|
+
|
|
306
385
|
### Array Filtering
|
|
307
386
|
|
|
308
387
|
Suparisma provides powerful operators for filtering array fields (e.g., `String[]`, `Int[]`, etc.):
|
|
@@ -543,6 +622,8 @@ const { data, count } = useSuparisma.thing();
|
|
|
543
622
|
|
|
544
623
|
### Search Functionality
|
|
545
624
|
|
|
625
|
+
> ⚠️ **MAINTENANCE NOTICE**: Search functionality is currently under maintenance and may not work as expected. We're working on improvements and will update the documentation once it's fully operational.
|
|
626
|
+
|
|
546
627
|
For fields annotated with `// @enableSearch`, you can use full-text search:
|
|
547
628
|
|
|
548
629
|
```tsx
|
|
@@ -912,24 +912,52 @@ export function createSuparismaHook<
|
|
|
912
912
|
|
|
913
913
|
const channelId = channelName || \`changes_to_\${tableName}_\${Math.random().toString(36).substring(2, 15)}\`;
|
|
914
914
|
|
|
915
|
-
//
|
|
916
|
-
|
|
917
|
-
|
|
918
|
-
|
|
919
|
-
|
|
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
|
|
932
|
+
let subscriptionConfig: any = {
|
|
933
|
+
event: '*',
|
|
934
|
+
schema: 'public',
|
|
935
|
+
table: tableName,
|
|
936
|
+
};
|
|
937
|
+
|
|
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
|
+
}
|
|
920
953
|
|
|
921
954
|
const channel = supabase
|
|
922
955
|
.channel(channelId)
|
|
923
956
|
.on(
|
|
924
957
|
'postgres_changes',
|
|
925
|
-
|
|
926
|
-
event: '*',
|
|
927
|
-
schema: 'public',
|
|
928
|
-
table: tableName,
|
|
929
|
-
filter: initialComputedFilter, // Subscription filter uses initial state
|
|
930
|
-
},
|
|
958
|
+
subscriptionConfig,
|
|
931
959
|
(payload) => {
|
|
932
|
-
console.log(
|
|
960
|
+
console.log(\`🔥 REALTIME EVENT RECEIVED for \${tableName}:\`, payload.eventType, payload);
|
|
933
961
|
|
|
934
962
|
// Access current options via refs inside the event handler
|
|
935
963
|
const currentWhere = whereRef.current;
|
|
@@ -938,7 +966,10 @@ export function createSuparismaHook<
|
|
|
938
966
|
const currentOffset = offsetRef.current; // Not directly used in handlers but good for consistency
|
|
939
967
|
|
|
940
968
|
// Skip realtime updates when search is active
|
|
941
|
-
if (isSearchingRef.current)
|
|
969
|
+
if (isSearchingRef.current) {
|
|
970
|
+
console.log('⏭️ Skipping realtime update - search is active');
|
|
971
|
+
return;
|
|
972
|
+
}
|
|
942
973
|
|
|
943
974
|
if (payload.eventType === 'INSERT') {
|
|
944
975
|
// Process insert event
|
|
@@ -947,14 +978,46 @@ export function createSuparismaHook<
|
|
|
947
978
|
const newRecord = payload.new as TWithRelations;
|
|
948
979
|
console.log(\`Processing INSERT for \${tableName}\`, { newRecord });
|
|
949
980
|
|
|
950
|
-
//
|
|
981
|
+
// ALWAYS check if this record matches our filter client-side
|
|
982
|
+
// This is especially important for complex array filters
|
|
951
983
|
if (currentWhere) { // Use ref value
|
|
952
984
|
let matchesFilter = true;
|
|
953
985
|
|
|
954
|
-
// Check each filter condition
|
|
986
|
+
// Check each filter condition client-side for complex filters
|
|
955
987
|
for (const [key, value] of Object.entries(currentWhere)) {
|
|
956
988
|
if (typeof value === 'object' && value !== null) {
|
|
957
|
-
//
|
|
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
|
|
958
1021
|
} else if (newRecord[key as keyof typeof newRecord] !== value) {
|
|
959
1022
|
matchesFilter = false;
|
|
960
1023
|
console.log(\`Filter mismatch on \${key}\`, { expected: value, actual: newRecord[key as keyof typeof newRecord] });
|
|
@@ -1035,12 +1098,68 @@ export function createSuparismaHook<
|
|
|
1035
1098
|
// Access current options via refs
|
|
1036
1099
|
const currentOrderBy = orderByRef.current;
|
|
1037
1100
|
const currentLimit = limitRef.current; // If needed for re-fetch logic on update
|
|
1101
|
+
const currentWhere = whereRef.current;
|
|
1038
1102
|
|
|
1039
1103
|
// Skip if search is active
|
|
1040
1104
|
if (isSearchingRef.current) {
|
|
1041
1105
|
return prev;
|
|
1042
1106
|
}
|
|
1043
1107
|
|
|
1108
|
+
const updatedRecord = payload.new as TWithRelations;
|
|
1109
|
+
|
|
1110
|
+
// 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) {
|
|
1155
|
+
console.log('Updated record no longer matches filter, removing from list');
|
|
1156
|
+
return prev.filter((item) =>
|
|
1157
|
+
// @ts-ignore: Supabase typing issue
|
|
1158
|
+
!('id' in item && 'id' in updatedRecord && item.id === updatedRecord.id)
|
|
1159
|
+
);
|
|
1160
|
+
}
|
|
1161
|
+
}
|
|
1162
|
+
|
|
1044
1163
|
const newData = prev.map((item) =>
|
|
1045
1164
|
// @ts-ignore: Supabase typing issue
|
|
1046
1165
|
'id' in item && 'id' in payload.new && item.id === payload.new.id
|
|
@@ -1092,7 +1211,10 @@ export function createSuparismaHook<
|
|
|
1092
1211
|
});
|
|
1093
1212
|
} else if (payload.eventType === 'DELETE') {
|
|
1094
1213
|
// Process delete event
|
|
1214
|
+
console.log('🗑️ Processing DELETE event for', tableName);
|
|
1095
1215
|
setData((prev) => {
|
|
1216
|
+
console.log('🗑️ DELETE: Current data before deletion:', prev.length, 'items');
|
|
1217
|
+
|
|
1096
1218
|
// Access current options via refs
|
|
1097
1219
|
const currentWhere = whereRef.current;
|
|
1098
1220
|
const currentOrderBy = orderByRef.current;
|
|
@@ -1101,6 +1223,7 @@ export function createSuparismaHook<
|
|
|
1101
1223
|
|
|
1102
1224
|
// Skip if search is active
|
|
1103
1225
|
if (isSearchingRef.current) {
|
|
1226
|
+
console.log('⏭️ DELETE: Skipping - search is active');
|
|
1104
1227
|
return prev;
|
|
1105
1228
|
}
|
|
1106
1229
|
|
|
@@ -1110,15 +1233,21 @@ export function createSuparismaHook<
|
|
|
1110
1233
|
// Filter out the deleted item
|
|
1111
1234
|
const filteredData = prev.filter((item) => {
|
|
1112
1235
|
// @ts-ignore: Supabase typing issue
|
|
1113
|
-
|
|
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;
|
|
1114
1241
|
});
|
|
1115
1242
|
|
|
1243
|
+
console.log('🗑️ DELETE: Data after deletion:', filteredData.length, 'items (was', currentSize, ')');
|
|
1244
|
+
|
|
1116
1245
|
// Fetch the updated count after the data changes
|
|
1117
1246
|
setTimeout(() => fetchTotalCount(), 0);
|
|
1118
1247
|
|
|
1119
1248
|
// If we need to maintain the size with a limit
|
|
1120
1249
|
if (currentLimit && currentLimit > 0 && filteredData.length < currentSize && currentSize === currentLimit) { // Use ref value
|
|
1121
|
-
console.log(
|
|
1250
|
+
console.log(\`🗑️ DELETE: Record deleted with limit \${currentLimit}, will fetch additional record to maintain size\`);
|
|
1122
1251
|
|
|
1123
1252
|
// Use setTimeout to ensure this state update completes first
|
|
1124
1253
|
setTimeout(() => {
|
|
@@ -1195,7 +1324,7 @@ export function createSuparismaHook<
|
|
|
1195
1324
|
searchTimeoutRef.current = null;
|
|
1196
1325
|
}
|
|
1197
1326
|
};
|
|
1198
|
-
}, [realtime, channelName, tableName, initialLoadRef]); //
|
|
1327
|
+
}, [realtime, channelName, tableName, where, initialLoadRef]); // Added 'where' back so subscription updates when filter changes
|
|
1199
1328
|
|
|
1200
1329
|
// Create a memoized options object to prevent unnecessary re-renders
|
|
1201
1330
|
const optionsRef = useRef({ where, orderBy, limit, offset });
|
package/dist/parser.js
CHANGED
|
@@ -12,6 +12,16 @@ function parsePrismaSchema(schemaPath) {
|
|
|
12
12
|
const schema = fs_1.default.readFileSync(schemaPath, 'utf-8');
|
|
13
13
|
const modelRegex = /model\s+(\w+)\s+{([^}]*)}/gs;
|
|
14
14
|
const models = [];
|
|
15
|
+
// Extract enum names from the schema
|
|
16
|
+
const enumRegex = /enum\s+(\w+)\s+{[^}]*}/gs;
|
|
17
|
+
const enumNames = [];
|
|
18
|
+
let enumMatch;
|
|
19
|
+
while ((enumMatch = enumRegex.exec(schema)) !== null) {
|
|
20
|
+
const enumName = enumMatch[1];
|
|
21
|
+
if (enumName) {
|
|
22
|
+
enumNames.push(enumName);
|
|
23
|
+
}
|
|
24
|
+
}
|
|
15
25
|
let match;
|
|
16
26
|
while ((match = modelRegex.exec(schema)) !== null) {
|
|
17
27
|
const modelName = match[1] || '';
|
|
@@ -45,7 +55,7 @@ function parsePrismaSchema(schemaPath) {
|
|
|
45
55
|
continue;
|
|
46
56
|
}
|
|
47
57
|
// Parse field definition - Updated to handle array types
|
|
48
|
-
const fieldMatch = line.match(/\s*(\w+)\s+(\w+)(\[\])
|
|
58
|
+
const fieldMatch = line.match(/\s*(\w+)\s+(\w+)(\[\])?(\?)?\s*(?:@[^)]+)?/);
|
|
49
59
|
if (fieldMatch) {
|
|
50
60
|
const fieldName = fieldMatch[1];
|
|
51
61
|
const baseFieldType = fieldMatch[2]; // e.g., "String" from "String[]"
|
|
@@ -67,9 +77,13 @@ function parsePrismaSchema(schemaPath) {
|
|
|
67
77
|
defaultValue = defaultMatch[1];
|
|
68
78
|
}
|
|
69
79
|
}
|
|
80
|
+
// Improved relation detection
|
|
81
|
+
const primitiveTypes = ['String', 'Int', 'Float', 'Boolean', 'DateTime', 'Json', 'Bytes', 'Decimal', 'BigInt'];
|
|
70
82
|
const isRelation = line.includes('@relation') ||
|
|
71
83
|
(!!fieldName &&
|
|
72
|
-
(fieldName.endsWith('_id') || fieldName === 'userId' || fieldName === 'user_id'))
|
|
84
|
+
(fieldName.endsWith('_id') || fieldName === 'userId' || fieldName === 'user_id')) ||
|
|
85
|
+
// Also detect relation fields by checking if the type is not a primitive type and not an enum
|
|
86
|
+
(!!baseFieldType && !primitiveTypes.includes(baseFieldType) && !enumNames.includes(baseFieldType));
|
|
73
87
|
// Check for inline @enableSearch comment
|
|
74
88
|
if (line.includes('// @enableSearch')) {
|
|
75
89
|
searchFields.push({
|
package/package.json
CHANGED
package/prisma/schema.prisma
CHANGED
|
@@ -19,6 +19,14 @@ enum SomeEnum {
|
|
|
19
19
|
THREE
|
|
20
20
|
}
|
|
21
21
|
|
|
22
|
+
model User {
|
|
23
|
+
id String @id @default(uuid())
|
|
24
|
+
name String
|
|
25
|
+
email String @unique
|
|
26
|
+
things Thing[] // One-to-many relation
|
|
27
|
+
createdAt DateTime @default(now())
|
|
28
|
+
updatedAt DateTime @updatedAt
|
|
29
|
+
}
|
|
22
30
|
|
|
23
31
|
model Thing {
|
|
24
32
|
id String @id @default(uuid())
|
|
@@ -27,6 +35,8 @@ model Thing {
|
|
|
27
35
|
stringArray String[]
|
|
28
36
|
someEnum SomeEnum @default(ONE)
|
|
29
37
|
someNumber Int?
|
|
38
|
+
userId String?
|
|
39
|
+
user User? @relation(fields: [userId], references: [id])
|
|
30
40
|
createdAt DateTime @default(now())
|
|
31
41
|
updatedAt DateTime @updatedAt
|
|
32
42
|
}
|