suparisma 1.1.1 → 1.2.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +439 -14
- package/SEARCH_FEATURES.md +430 -0
- package/dist/generators/coreGenerator.js +242 -70
- package/dist/generators/typeGenerator.js +5 -22
- package/dist/index.js +206 -15
- package/dist/parser.js +47 -37
- package/package.json +1 -1
- package/prisma/schema.prisma +6 -1
package/dist/index.js
CHANGED
|
@@ -105,20 +105,45 @@ function analyzePrismaSchema(schemaPath) {
|
|
|
105
105
|
: modelName;
|
|
106
106
|
const enableRealtime = !modelBodyWithComments.includes('// @disableRealtime');
|
|
107
107
|
const searchFields = [];
|
|
108
|
-
// Split model body into lines to check
|
|
108
|
+
// Split model body into lines to check for @enableSearch directives
|
|
109
109
|
const bodyLines = modelBody.trim().split('\n');
|
|
110
|
+
let nextFieldShouldBeSearchable = false;
|
|
110
111
|
for (let i = 0; i < bodyLines.length; i++) {
|
|
111
|
-
const currentLine = bodyLines[i]
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
112
|
+
const currentLine = bodyLines[i]?.trim() || '';
|
|
113
|
+
// Skip blank lines and non-field lines
|
|
114
|
+
if (!currentLine || currentLine.startsWith('@@')) {
|
|
115
|
+
continue;
|
|
116
|
+
}
|
|
117
|
+
// Check for /// @enableSearch directive (applies to NEXT field)
|
|
118
|
+
if (currentLine === '/// @enableSearch') {
|
|
119
|
+
nextFieldShouldBeSearchable = true;
|
|
120
|
+
continue;
|
|
121
|
+
}
|
|
122
|
+
// Check for standalone // @enableSearch comment (applies to NEXT field)
|
|
123
|
+
if (currentLine === '// @enableSearch') {
|
|
124
|
+
nextFieldShouldBeSearchable = true;
|
|
125
|
+
continue;
|
|
126
|
+
}
|
|
127
|
+
// Check if line is a comment - SKIP ALL TYPES of comments but keep search flag
|
|
128
|
+
if (currentLine.startsWith('///') || currentLine.startsWith('//')) {
|
|
129
|
+
continue;
|
|
130
|
+
}
|
|
131
|
+
// Parse field definition - Updated to handle array types and inline comments
|
|
132
|
+
const fieldMatch = currentLine.match(/^\s*(\w+)\s+(\w+)(\[\])?(\?)?\s*/);
|
|
133
|
+
if (fieldMatch) {
|
|
134
|
+
const fieldName = fieldMatch[1];
|
|
135
|
+
const fieldType = fieldMatch[2];
|
|
136
|
+
// Check if this field should be searchable due to @enableSearch directive
|
|
137
|
+
if (nextFieldShouldBeSearchable && fieldName && fieldType) {
|
|
138
|
+
searchFields.push({
|
|
139
|
+
name: fieldName,
|
|
140
|
+
type: fieldType,
|
|
141
|
+
});
|
|
142
|
+
nextFieldShouldBeSearchable = false; // Reset flag
|
|
143
|
+
}
|
|
144
|
+
// Check for inline // @enableSearch comment
|
|
145
|
+
if (currentLine.includes('// @enableSearch')) {
|
|
146
|
+
if (fieldName && fieldType && !searchFields.some(f => f.name === fieldName)) {
|
|
122
147
|
searchFields.push({
|
|
123
148
|
name: fieldName,
|
|
124
149
|
type: fieldType,
|
|
@@ -195,6 +220,7 @@ async function configurePrismaTablesForSuparisma(schemaPath) {
|
|
|
195
220
|
if (model.searchFields.length > 0) {
|
|
196
221
|
console.log(` 🔍 Setting up full-text search for model ${model.name}:`);
|
|
197
222
|
const { rows: columns } = await pool.query(`SELECT column_name FROM information_schema.columns WHERE table_schema = 'public' AND table_name = $1`, [actualTableName]);
|
|
223
|
+
// Create individual field search functions
|
|
198
224
|
for (const searchField of model.searchFields) {
|
|
199
225
|
const matchingColumn = columns.find((c) => c.column_name.toLowerCase() === searchField.name.toLowerCase());
|
|
200
226
|
if (!matchingColumn) {
|
|
@@ -206,16 +232,73 @@ async function configurePrismaTablesForSuparisma(schemaPath) {
|
|
|
206
232
|
const indexName = `idx_gin_search_${actualTableName.toLowerCase()}_${actualColumnName.toLowerCase()}`;
|
|
207
233
|
console.log(` ➡️ Configuring field "${actualColumnName}":`);
|
|
208
234
|
try {
|
|
209
|
-
// Create search function
|
|
235
|
+
// Create search function with improved partial search and error handling
|
|
210
236
|
const createFunctionQuery = `
|
|
211
237
|
CREATE OR REPLACE FUNCTION "public"."${functionName}"(search_prefix text)
|
|
212
238
|
RETURNS SETOF "public"."${actualTableName}" AS $$
|
|
239
|
+
DECLARE
|
|
240
|
+
clean_prefix text;
|
|
241
|
+
words text[];
|
|
242
|
+
word text;
|
|
243
|
+
tsquery_str text := '';
|
|
213
244
|
BEGIN
|
|
245
|
+
-- Handle empty or null search terms
|
|
246
|
+
IF search_prefix IS NULL OR trim(search_prefix) = '' THEN
|
|
247
|
+
RETURN;
|
|
248
|
+
END IF;
|
|
249
|
+
|
|
250
|
+
-- Clean the search prefix: remove special characters, normalize spaces
|
|
251
|
+
clean_prefix := regexp_replace(trim(search_prefix), '[^a-zA-Z0-9\\s]', ' ', 'g');
|
|
252
|
+
clean_prefix := regexp_replace(clean_prefix, '\\s+', ' ', 'g');
|
|
253
|
+
clean_prefix := trim(clean_prefix);
|
|
254
|
+
|
|
255
|
+
-- Handle empty string after cleaning
|
|
256
|
+
IF clean_prefix = '' THEN
|
|
257
|
+
RETURN;
|
|
258
|
+
END IF;
|
|
259
|
+
|
|
260
|
+
-- Split into words and build partial search query
|
|
261
|
+
words := string_to_array(clean_prefix, ' ');
|
|
262
|
+
|
|
263
|
+
-- Build tsquery for partial matching
|
|
264
|
+
FOR i IN 1..array_length(words, 1) LOOP
|
|
265
|
+
word := words[i];
|
|
266
|
+
IF word != '' THEN
|
|
267
|
+
IF tsquery_str != '' THEN
|
|
268
|
+
tsquery_str := tsquery_str || ' & ';
|
|
269
|
+
END IF;
|
|
270
|
+
-- Add prefix matching for each word
|
|
271
|
+
tsquery_str := tsquery_str || word || ':*';
|
|
272
|
+
END IF;
|
|
273
|
+
END LOOP;
|
|
274
|
+
|
|
275
|
+
-- Return query with proper error handling
|
|
214
276
|
RETURN QUERY
|
|
215
277
|
SELECT * FROM "public"."${actualTableName}"
|
|
216
|
-
WHERE
|
|
278
|
+
WHERE
|
|
279
|
+
"${actualColumnName}" IS NOT NULL
|
|
280
|
+
AND "${actualColumnName}" != ''
|
|
281
|
+
AND (
|
|
282
|
+
-- Use the built tsquery for structured search
|
|
283
|
+
to_tsvector('english', "${actualColumnName}") @@ to_tsquery('english', tsquery_str)
|
|
284
|
+
OR
|
|
285
|
+
-- Fallback to simple text matching for very partial matches
|
|
286
|
+
"${actualColumnName}" ILIKE '%' || search_prefix || '%'
|
|
287
|
+
);
|
|
288
|
+
EXCEPTION
|
|
289
|
+
WHEN others THEN
|
|
290
|
+
-- Log error and return empty result set instead of failing
|
|
291
|
+
RAISE NOTICE 'Search function error: %, falling back to simple ILIKE search', SQLERRM;
|
|
292
|
+
-- Fallback to simple pattern matching
|
|
293
|
+
RETURN QUERY
|
|
294
|
+
SELECT * FROM "public"."${actualTableName}"
|
|
295
|
+
WHERE
|
|
296
|
+
"${actualColumnName}" IS NOT NULL
|
|
297
|
+
AND "${actualColumnName}" != ''
|
|
298
|
+
AND "${actualColumnName}" ILIKE '%' || search_prefix || '%';
|
|
299
|
+
RETURN;
|
|
217
300
|
END;
|
|
218
|
-
$$ LANGUAGE plpgsql STABLE;`; // Added STABLE for
|
|
301
|
+
$$ LANGUAGE plpgsql STABLE;`; // Added STABLE for performance
|
|
219
302
|
await pool.query(createFunctionQuery);
|
|
220
303
|
console.log(` ✅ Created/Replaced RPC function: "${functionName}"(search_prefix text)`);
|
|
221
304
|
// Create GIN index
|
|
@@ -250,6 +333,114 @@ async function configurePrismaTablesForSuparisma(schemaPath) {
|
|
|
250
333
|
console.error(` ❌ Failed to set up search for "${actualTableName}"."${actualColumnName}": ${err.message}`);
|
|
251
334
|
}
|
|
252
335
|
}
|
|
336
|
+
// Create multi-field search function if there are multiple searchable fields
|
|
337
|
+
if (model.searchFields.length > 1) {
|
|
338
|
+
console.log(` ➡️ Creating multi-field search function:`);
|
|
339
|
+
try {
|
|
340
|
+
const validSearchFields = model.searchFields.filter(field => columns.find(c => c.column_name.toLowerCase() === field.name.toLowerCase()));
|
|
341
|
+
if (validSearchFields.length > 1) {
|
|
342
|
+
const multiFieldFunctionName = `search_${actualTableName.toLowerCase()}_multi_field`;
|
|
343
|
+
const multiFieldIndexName = `idx_gin_search_${actualTableName.toLowerCase()}_multi_field`;
|
|
344
|
+
// Get actual column names
|
|
345
|
+
const actualColumnNames = validSearchFields.map(field => {
|
|
346
|
+
const matchingColumn = columns.find(c => c.column_name.toLowerCase() === field.name.toLowerCase());
|
|
347
|
+
return matchingColumn.column_name;
|
|
348
|
+
});
|
|
349
|
+
// Create multi-field search function with improved partial search
|
|
350
|
+
const createMultiFieldFunctionQuery = `
|
|
351
|
+
CREATE OR REPLACE FUNCTION "public"."${multiFieldFunctionName}"(search_prefix text)
|
|
352
|
+
RETURNS SETOF "public"."${actualTableName}" AS $$
|
|
353
|
+
DECLARE
|
|
354
|
+
clean_prefix text;
|
|
355
|
+
words text[];
|
|
356
|
+
word text;
|
|
357
|
+
tsquery_str text := '';
|
|
358
|
+
combined_text text;
|
|
359
|
+
BEGIN
|
|
360
|
+
-- Handle empty or null search terms
|
|
361
|
+
IF search_prefix IS NULL OR trim(search_prefix) = '' THEN
|
|
362
|
+
RETURN;
|
|
363
|
+
END IF;
|
|
364
|
+
|
|
365
|
+
-- Clean the search prefix: remove special characters, normalize spaces
|
|
366
|
+
clean_prefix := regexp_replace(trim(search_prefix), '[^a-zA-Z0-9\\s]', ' ', 'g');
|
|
367
|
+
clean_prefix := regexp_replace(clean_prefix, '\\s+', ' ', 'g');
|
|
368
|
+
clean_prefix := trim(clean_prefix);
|
|
369
|
+
|
|
370
|
+
-- Handle empty string after cleaning
|
|
371
|
+
IF clean_prefix = '' THEN
|
|
372
|
+
RETURN;
|
|
373
|
+
END IF;
|
|
374
|
+
|
|
375
|
+
-- Split into words and build partial search query
|
|
376
|
+
words := string_to_array(clean_prefix, ' ');
|
|
377
|
+
|
|
378
|
+
-- Build tsquery for partial matching
|
|
379
|
+
FOR i IN 1..array_length(words, 1) LOOP
|
|
380
|
+
word := words[i];
|
|
381
|
+
IF word != '' THEN
|
|
382
|
+
IF tsquery_str != '' THEN
|
|
383
|
+
tsquery_str := tsquery_str || ' & ';
|
|
384
|
+
END IF;
|
|
385
|
+
-- Add prefix matching for each word
|
|
386
|
+
tsquery_str := tsquery_str || word || ':*';
|
|
387
|
+
END IF;
|
|
388
|
+
END LOOP;
|
|
389
|
+
|
|
390
|
+
-- Return query searching across all searchable fields
|
|
391
|
+
RETURN QUERY
|
|
392
|
+
SELECT * FROM "public"."${actualTableName}"
|
|
393
|
+
WHERE
|
|
394
|
+
(
|
|
395
|
+
-- Use the built tsquery for structured search
|
|
396
|
+
to_tsvector('english',
|
|
397
|
+
COALESCE("${actualColumnNames.join('", \'\') || \' \' || COALESCE("')}", '')
|
|
398
|
+
) @@ to_tsquery('english', tsquery_str)
|
|
399
|
+
OR
|
|
400
|
+
-- Fallback to simple text matching across all fields
|
|
401
|
+
(${actualColumnNames.map(col => `"${col}" ILIKE '%' || search_prefix || '%'`).join(' OR ')})
|
|
402
|
+
);
|
|
403
|
+
EXCEPTION
|
|
404
|
+
WHEN others THEN
|
|
405
|
+
-- Log error and return empty result set instead of failing
|
|
406
|
+
RAISE NOTICE 'Multi-field search function error: %, falling back to simple ILIKE search', SQLERRM;
|
|
407
|
+
-- Fallback to simple pattern matching across all fields
|
|
408
|
+
RETURN QUERY
|
|
409
|
+
SELECT * FROM "public"."${actualTableName}"
|
|
410
|
+
WHERE
|
|
411
|
+
(${actualColumnNames.map(col => `"${col}" ILIKE '%' || search_prefix || '%'`).join(' OR ')});
|
|
412
|
+
RETURN;
|
|
413
|
+
END;
|
|
414
|
+
$$ LANGUAGE plpgsql STABLE;`;
|
|
415
|
+
await pool.query(createMultiFieldFunctionQuery);
|
|
416
|
+
console.log(` ✅ Created multi-field search function: "${multiFieldFunctionName}"(search_prefix text)`);
|
|
417
|
+
// Create multi-field GIN index
|
|
418
|
+
const createMultiFieldIndexQuery = `
|
|
419
|
+
DO $$
|
|
420
|
+
BEGIN
|
|
421
|
+
IF NOT EXISTS (
|
|
422
|
+
SELECT 1 FROM pg_indexes
|
|
423
|
+
WHERE schemaname = 'public'
|
|
424
|
+
AND tablename = '${actualTableName}'
|
|
425
|
+
AND indexname = '${multiFieldIndexName}'
|
|
426
|
+
) THEN
|
|
427
|
+
CREATE INDEX "${multiFieldIndexName}" ON "public"."${actualTableName}"
|
|
428
|
+
USING GIN (to_tsvector('english',
|
|
429
|
+
COALESCE("${actualColumnNames.join('", \'\') || \' \' || COALESCE("')}", '')
|
|
430
|
+
));
|
|
431
|
+
RAISE NOTICE ' ✅ Created multi-field GIN index: "${multiFieldIndexName}"';
|
|
432
|
+
ELSE
|
|
433
|
+
RAISE NOTICE ' ℹ️ Multi-field GIN index "${multiFieldIndexName}" already exists.';
|
|
434
|
+
END IF;
|
|
435
|
+
END;
|
|
436
|
+
$$;`;
|
|
437
|
+
await pool.query(createMultiFieldIndexQuery);
|
|
438
|
+
}
|
|
439
|
+
}
|
|
440
|
+
catch (err) {
|
|
441
|
+
console.error(` ❌ Failed to set up multi-field search for "${actualTableName}": ${err.message}`);
|
|
442
|
+
}
|
|
443
|
+
}
|
|
253
444
|
}
|
|
254
445
|
else {
|
|
255
446
|
console.log(` ℹ️ No fields marked with // @enableSearch for model ${model.name}.`);
|
package/dist/parser.js
CHANGED
|
@@ -70,10 +70,10 @@ function parseZodImport(comment) {
|
|
|
70
70
|
*/
|
|
71
71
|
function parsePrismaSchema(schemaPath) {
|
|
72
72
|
const schema = fs_1.default.readFileSync(schemaPath, 'utf-8');
|
|
73
|
-
const modelRegex = /model\s+(\w+)\s+{([^}]*)}/
|
|
73
|
+
const modelRegex = /model\s+(\w+)\s+{([^}]*)}/g;
|
|
74
74
|
const models = [];
|
|
75
75
|
// Extract enum names from the schema
|
|
76
|
-
const enumRegex = /enum\s+(\w+)\s+{[^}]*}/
|
|
76
|
+
const enumRegex = /enum\s+(\w+)\s+{[^}]*}/g;
|
|
77
77
|
const enumNames = [];
|
|
78
78
|
let enumMatch;
|
|
79
79
|
while ((enumMatch = enumRegex.exec(schema)) !== null) {
|
|
@@ -95,77 +95,87 @@ function parsePrismaSchema(schemaPath) {
|
|
|
95
95
|
const searchFields = [];
|
|
96
96
|
// Track zod imports at model level
|
|
97
97
|
const zodImports = [];
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
let
|
|
98
|
+
// Use EXACT same logic as analyzePrismaSchema for consistency
|
|
99
|
+
const bodyLines = modelBody.trim().split('\n');
|
|
100
|
+
let nextFieldShouldBeSearchable = false;
|
|
101
101
|
let pendingZodDirective;
|
|
102
|
-
for (let i = 0; i <
|
|
103
|
-
const
|
|
102
|
+
for (let i = 0; i < bodyLines.length; i++) {
|
|
103
|
+
const currentLine = bodyLines[i]?.trim();
|
|
104
104
|
// Skip blank lines and non-field lines
|
|
105
|
-
if (!
|
|
105
|
+
if (!currentLine || currentLine.startsWith('@@')) {
|
|
106
106
|
continue;
|
|
107
107
|
}
|
|
108
|
-
// Check for
|
|
109
|
-
if (
|
|
110
|
-
|
|
111
|
-
name: lastFieldName,
|
|
112
|
-
type: lastFieldType,
|
|
113
|
-
});
|
|
108
|
+
// Check for /// @enableSearch directive (applies to NEXT field)
|
|
109
|
+
if (currentLine === '/// @enableSearch') {
|
|
110
|
+
nextFieldShouldBeSearchable = true;
|
|
114
111
|
continue;
|
|
115
112
|
}
|
|
116
|
-
// Check
|
|
117
|
-
if (
|
|
113
|
+
// Check for standalone // @enableSearch comment (applies to NEXT field)
|
|
114
|
+
if (currentLine === '// @enableSearch') {
|
|
115
|
+
nextFieldShouldBeSearchable = true;
|
|
116
|
+
continue;
|
|
117
|
+
}
|
|
118
|
+
// Check if line is a comment - SKIP ALL TYPES of comments but keep search flag
|
|
119
|
+
if (currentLine.startsWith('///') || currentLine.startsWith('//')) {
|
|
118
120
|
// Parse zod directives from comments
|
|
119
|
-
const zodDirective = parseZodDirective(
|
|
121
|
+
const zodDirective = parseZodDirective(currentLine);
|
|
120
122
|
if (zodDirective) {
|
|
121
123
|
pendingZodDirective = zodDirective;
|
|
122
124
|
}
|
|
123
125
|
// Parse zod imports from comments
|
|
124
|
-
const zodImportInfos = parseZodImport(
|
|
126
|
+
const zodImportInfos = parseZodImport(currentLine);
|
|
125
127
|
zodImports.push(...zodImportInfos);
|
|
126
128
|
continue;
|
|
127
129
|
}
|
|
128
|
-
// Parse field definition - Updated to handle array types
|
|
129
|
-
const fieldMatch =
|
|
130
|
+
// Parse field definition - Updated to handle array types and inline comments
|
|
131
|
+
const fieldMatch = currentLine.match(/^\s*(\w+)\s+(\w+)(\[\])?(\?)?\s*/);
|
|
130
132
|
if (fieldMatch) {
|
|
131
133
|
const fieldName = fieldMatch[1];
|
|
132
|
-
const baseFieldType = fieldMatch[2];
|
|
134
|
+
const baseFieldType = fieldMatch[2];
|
|
135
|
+
// Check if this field should be searchable due to @enableSearch directive
|
|
136
|
+
if (nextFieldShouldBeSearchable && fieldName && baseFieldType) {
|
|
137
|
+
searchFields.push({
|
|
138
|
+
name: fieldName,
|
|
139
|
+
type: baseFieldType,
|
|
140
|
+
});
|
|
141
|
+
nextFieldShouldBeSearchable = false; // Reset flag
|
|
142
|
+
}
|
|
143
|
+
// Check for inline // @enableSearch comment
|
|
144
|
+
if (currentLine.includes('// @enableSearch')) {
|
|
145
|
+
if (fieldName && baseFieldType && !searchFields.some(f => f.name === fieldName)) {
|
|
146
|
+
searchFields.push({
|
|
147
|
+
name: fieldName,
|
|
148
|
+
type: baseFieldType,
|
|
149
|
+
});
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
// Continue with field processing for the fields array
|
|
133
153
|
const isArray = !!fieldMatch[3]; // [] makes it an array
|
|
134
154
|
const isOptional = !!fieldMatch[4]; // ? makes it optional
|
|
135
|
-
// Store for potential standalone @enableSearch comment
|
|
136
|
-
lastFieldName = fieldName || '';
|
|
137
|
-
lastFieldType = baseFieldType || '';
|
|
138
155
|
// Detect special fields
|
|
139
|
-
const isId =
|
|
156
|
+
const isId = currentLine.includes('@id');
|
|
140
157
|
const isCreatedAt = fieldName === 'created_at' || fieldName === 'createdAt';
|
|
141
158
|
const isUpdatedAt = fieldName === 'updated_at' || fieldName === 'updatedAt';
|
|
142
|
-
const hasDefaultValue =
|
|
159
|
+
const hasDefaultValue = currentLine.includes('@default');
|
|
143
160
|
// Extract default value if present
|
|
144
161
|
let defaultValue;
|
|
145
162
|
if (hasDefaultValue) {
|
|
146
|
-
const defaultMatch =
|
|
163
|
+
const defaultMatch = currentLine.match(/@default\(\s*(.+?)\s*\)/);
|
|
147
164
|
if (defaultMatch) {
|
|
148
165
|
defaultValue = defaultMatch[1];
|
|
149
166
|
}
|
|
150
167
|
}
|
|
151
168
|
// Improved relation detection
|
|
152
169
|
const primitiveTypes = ['String', 'Int', 'Float', 'Boolean', 'DateTime', 'Json', 'Bytes', 'Decimal', 'BigInt'];
|
|
153
|
-
const isRelation =
|
|
170
|
+
const isRelation = currentLine.includes('@relation') ||
|
|
154
171
|
(!!fieldName &&
|
|
155
172
|
(fieldName.endsWith('_id') || fieldName === 'userId' || fieldName === 'user_id')) ||
|
|
156
173
|
// Also detect relation fields by checking if the type is not a primitive type and not an enum
|
|
157
174
|
(!!baseFieldType && !primitiveTypes.includes(baseFieldType) && !enumNames.includes(baseFieldType));
|
|
158
|
-
// Check for inline @enableSearch comment
|
|
159
|
-
if (line.includes('// @enableSearch')) {
|
|
160
|
-
searchFields.push({
|
|
161
|
-
name: fieldName || '',
|
|
162
|
-
type: baseFieldType || '',
|
|
163
|
-
});
|
|
164
|
-
}
|
|
165
175
|
// Check for inline zod directive
|
|
166
176
|
let fieldZodDirective = pendingZodDirective;
|
|
167
|
-
if (
|
|
168
|
-
const inlineZodDirective = parseZodDirective(
|
|
177
|
+
if (currentLine.includes('/// @zod.')) {
|
|
178
|
+
const inlineZodDirective = parseZodDirective(currentLine);
|
|
169
179
|
if (inlineZodDirective) {
|
|
170
180
|
fieldZodDirective = inlineZodDirective;
|
|
171
181
|
}
|
package/package.json
CHANGED
package/prisma/schema.prisma
CHANGED
|
@@ -21,7 +21,9 @@ enum SomeEnum {
|
|
|
21
21
|
|
|
22
22
|
model User {
|
|
23
23
|
id String @id @default(uuid())
|
|
24
|
+
// @enableSearch
|
|
24
25
|
name String
|
|
26
|
+
// @enableSearch
|
|
25
27
|
email String @unique
|
|
26
28
|
things Thing[] // One-to-many relation
|
|
27
29
|
createdAt DateTime @default(now())
|
|
@@ -33,11 +35,14 @@ model Thing {
|
|
|
33
35
|
id String @id @default(uuid())
|
|
34
36
|
// @enableSearch
|
|
35
37
|
name String?
|
|
38
|
+
// @enableSearch
|
|
39
|
+
/// Somehting here
|
|
40
|
+
description String? /// @thing
|
|
36
41
|
stringArray String[]
|
|
37
42
|
someEnum SomeEnum @default(ONE)
|
|
38
43
|
someNumber Int?
|
|
39
44
|
|
|
40
|
-
|
|
45
|
+
// @enableSearch
|
|
41
46
|
someJson Json? /// @zod.custom.use(z.array(LLMNodeSchema).nullable())
|
|
42
47
|
userId String?
|
|
43
48
|
user User? @relation(fields: [userId], references: [id])
|