nuxt-auto-crud 1.24.0 → 1.26.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -7,53 +7,23 @@ import { getTableColumns as getDrizzleTableColumns, getTableName } from 'drizzle
7
7
  import type { SQLiteTable } from 'drizzle-orm/sqlite-core'
8
8
  import { createError } from 'h3'
9
9
  import { useRuntimeConfig } from '#imports'
10
+ import { createInsertSchema } from 'drizzle-zod'
11
+ import type { z } from 'zod'
10
12
 
11
- /**
12
- * Fields that should never be updatable via PATCH requests
13
- */
14
- const PROTECTED_FIELDS = ['id', 'created_at', 'updated_at', 'createdAt', 'updatedAt']
15
-
16
- /**
17
- * Fields that should never be returned in API responses
18
- */
19
- const HIDDEN_FIELDS = ['password', 'secret', 'token']
20
-
21
- /**
22
- * Custom updatable fields configuration (optional)
23
- * Only define here if you want to override the auto-detection
24
- *
25
- * Example:
26
- * export const customUpdatableFields: Record<string, string[]> = {
27
- * users: ['name', 'avatar'], // Only these fields can be updated
28
- * }
29
- */
30
- export const customUpdatableFields: Record<string, string[]> = {
31
- // Add custom field restrictions here if needed
32
- // By default, all fields except PROTECTED_FIELDS are updatable
33
- }
13
+ import { PROTECTED_FIELDS, HIDDEN_FIELDS } from './constants'
34
14
 
35
- /**
36
- * Custom hidden fields configuration (optional)
37
- * Only define here if you want to override the default hidden fields
38
- */
39
- export const customHiddenFields: Record<string, string[]> = {
40
- // Add custom hidden fields here if needed
41
- }
15
+ export const customUpdatableFields: Record<string, string[]> = {}
16
+ export const customHiddenFields: Record<string, string[]> = {}
42
17
 
43
18
  /**
44
- * Automatically builds a map of all exported tables from the schema
45
- * No manual configuration needed!
19
+ * Builds a map of all exported Drizzle tables from the schema.
46
20
  */
47
21
  function buildModelTableMap(): Record<string, unknown> {
48
22
  const tableMap: Record<string, unknown> = {}
49
23
 
50
- // Iterate through all exports from schema
51
24
  for (const [key, value] of Object.entries(schema)) {
52
- // Check if it's a Drizzle table
53
25
  if (value && typeof value === 'object') {
54
26
  try {
55
- // getTableName returns the table name for valid tables, and undefined/null for others (like relations)
56
- // This is a more robust check than checking for properties
57
27
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
58
28
  const tableName = getTableName(value as any)
59
29
  if (tableName) {
@@ -61,7 +31,7 @@ function buildModelTableMap(): Record<string, unknown> {
61
31
  }
62
32
  }
63
33
  catch {
64
- // Ignore if it throws (not a table)
34
+ // Not a table
65
35
  }
66
36
  }
67
37
  }
@@ -69,17 +39,10 @@ function buildModelTableMap(): Record<string, unknown> {
69
39
  return tableMap
70
40
  }
71
41
 
72
- /**
73
- * Auto-generated model table map
74
- * Automatically includes all tables from schema
75
- */
76
42
  export const modelTableMap = buildModelTableMap()
77
43
 
78
44
  /**
79
- * Gets the table for a given model name
80
- * @param modelName - The name of the model (e.g., 'users', 'products')
81
- * @returns The corresponding database table
82
- * @throws Error if model is not found
45
+ * @throws 404 if modelName is not found in tableMap.
83
46
  */
84
47
  export function getTableForModel(modelName: string): SQLiteTable {
85
48
  const table = modelTableMap[modelName]
@@ -95,10 +58,6 @@ export function getTableForModel(modelName: string): SQLiteTable {
95
58
  return table as SQLiteTable
96
59
  }
97
60
 
98
- /**
99
- * Auto-detects updatable fields for a table
100
- * Returns all fields except protected ones (id, createdAt, etc.)
101
- */
102
61
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
103
62
  function getTableColumns(table: any): string[] {
104
63
  try {
@@ -111,33 +70,20 @@ function getTableColumns(table: any): string[] {
111
70
  }
112
71
  }
113
72
 
114
- /**
115
- * Gets the updatable fields for a model
116
- * @param modelName - The name of the model
117
- * @returns Array of field names that can be updated
118
- */
119
73
  export function getUpdatableFields(modelName: string): string[] {
120
- // Check if custom fields are defined for this model
121
74
  if (customUpdatableFields[modelName]) {
122
75
  return customUpdatableFields[modelName]
123
76
  }
124
77
 
125
- // Auto-detect from table schema
126
78
  const table = modelTableMap[modelName]
127
-
128
79
  if (!table) return []
129
80
 
130
81
  const allColumns = getTableColumns(table)
131
-
132
- // Filter out protected fields
133
- return allColumns.filter(col => !PROTECTED_FIELDS.includes(col))
82
+ return allColumns.filter(col => !PROTECTED_FIELDS.includes(col) && !HIDDEN_FIELDS.includes(col))
134
83
  }
135
84
 
136
85
  /**
137
- * Filters an object to only include updatable fields for a model
138
- * @param modelName - The name of the model
139
- * @param data - The data object to filter
140
- * @returns Filtered object with only updatable fields
86
+ * Filters and coerces data for updates, handling timestamp conversion.
141
87
  */
142
88
  export function filterUpdatableFields(modelName: string, data: Record<string, unknown>): Record<string, unknown> {
143
89
  const allowedFields = getUpdatableFields(modelName)
@@ -151,7 +97,6 @@ export function filterUpdatableFields(modelName: string, data: Record<string, un
151
97
  let value = data[field]
152
98
  const column = columns[field]
153
99
 
154
- // Coerce timestamp fields to Date objects if they are strings
155
100
  if (column && column.mode === 'timestamp' && typeof value === 'string') {
156
101
  value = new Date(value)
157
102
  }
@@ -163,79 +108,43 @@ export function filterUpdatableFields(modelName: string, data: Record<string, un
163
108
  return filtered
164
109
  }
165
110
 
166
- /**
167
- * Gets the singular name for a model (for error messages)
168
- * Uses pluralize library for accurate singular/plural conversion
169
- * @param modelName - The plural model name
170
- * @returns The singular name in PascalCase
171
- */
172
111
  export function getModelSingularName(modelName: string): string {
173
112
  const singular = pluralize.singular(modelName)
174
113
  return pascalCase(singular)
175
114
  }
176
115
 
177
- /**
178
- * Gets the plural name for a model
179
- * @param modelName - The model name (singular or plural)
180
- * @returns The plural name
181
- * @return The plural name
182
- */
183
116
  export function getModelPluralName(modelName: string): string {
184
117
  return pluralize.plural(modelName)
185
118
  }
186
119
 
187
- /**
188
- * Lists all available models
189
- * @returns Array of model names
190
- */
191
120
  export function getAvailableModels(): string[] {
192
121
  return Object.keys(modelTableMap)
193
122
  }
194
123
 
195
- /**
196
- * Gets the hidden fields for a model
197
- * @param modelName - The name of the model
198
- * @returns Array of field names that should be hidden
199
- */
200
124
  export function getHiddenFields(modelName: string): string[] {
201
- // Check if custom hidden fields are defined for this model
202
- if (customHiddenFields[modelName]) {
203
- return customHiddenFields[modelName]
204
- }
205
-
206
- return HIDDEN_FIELDS
125
+ return customHiddenFields[modelName] ?? HIDDEN_FIELDS
207
126
  }
208
127
 
209
- /**
210
- * Gets the public columns for a model
211
- * @param modelName - The name of the model
212
- * @returns Array of field names that are public (or undefined if all are public)
213
- */
214
128
  export function getPublicColumns(modelName: string): string[] | undefined {
215
129
  const { resources } = useRuntimeConfig().autoCrud
216
- // Runtime config structure now matches simple key-value
217
130
  return resources?.[modelName]
218
131
  }
219
132
 
220
133
  /**
221
- * Filters an object to only include public columns (if configured)
222
- * @param modelName - The name of the model
223
- * @param data - The data object to filter
224
- * @returns Filtered object
134
+ * Restricts payload to runtimeConfig resource whitelist and filters hidden fields.
225
135
  */
226
136
  export function filterPublicColumns(modelName: string, data: Record<string, unknown>): Record<string, unknown> {
227
137
  const publicColumns = getPublicColumns(modelName)
228
138
 
229
- // If no public columns configured, return all (except hidden)
230
139
  if (!publicColumns) {
231
140
  return filterHiddenFields(modelName, data)
232
141
  }
233
142
 
234
143
  const filtered: Record<string, unknown> = {}
144
+ const hidden = getHiddenFields(modelName)
235
145
 
236
146
  for (const [key, value] of Object.entries(data)) {
237
- // Must be in publicColumns AND not in hidden fields (double safety)
238
- if (publicColumns.includes(key) && !getHiddenFields(modelName).includes(key)) {
147
+ if (publicColumns.includes(key) && !hidden.includes(key)) {
239
148
  filtered[key] = value
240
149
  }
241
150
  }
@@ -243,12 +152,6 @@ export function filterPublicColumns(modelName: string, data: Record<string, unkn
243
152
  return filtered
244
153
  }
245
154
 
246
- /**
247
- * Filters an object to exclude hidden fields
248
- * @param modelName - The name of the model
249
- * @param data - The data object to filter
250
- * @returns Filtered object without hidden fields
251
- */
252
155
  export function filterHiddenFields(modelName: string, data: Record<string, unknown>): Record<string, unknown> {
253
156
  const hiddenFields = getHiddenFields(modelName)
254
157
  const filtered: Record<string, unknown> = {}
@@ -261,3 +164,34 @@ export function filterHiddenFields(modelName: string, data: Record<string, unkno
261
164
 
262
165
  return filtered
263
166
  }
167
+
168
+ /**
169
+ * Generates Zod schema via drizzle-zod, omitting server-managed and protected fields.
170
+ */
171
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
172
+ export function getZodSchema(modelName: string, type: 'insert' | 'patch' = 'insert'): z.ZodObject<any, any> {
173
+ const table = getTableForModel(modelName)
174
+ const schema = createInsertSchema(table)
175
+
176
+ if (type === 'patch') {
177
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
178
+ return schema.partial() as z.ZodObject<any, any>
179
+ }
180
+
181
+ const OMIT_ON_CREATE = [
182
+ ...PROTECTED_FIELDS,
183
+ ...HIDDEN_FIELDS,
184
+ ]
185
+
186
+ const columns = getDrizzleTableColumns(table)
187
+ const fieldsToOmit: Record<string, true> = {}
188
+
189
+ OMIT_ON_CREATE.forEach((field) => {
190
+ if (columns[field]) {
191
+ fieldsToOmit[field] = true
192
+ }
193
+ })
194
+
195
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
196
+ return (schema as any).omit(fieldsToOmit)
197
+ }
@@ -30,6 +30,13 @@ export function drizzleTableToFields(table: any, resourceName: string) {
30
30
  })
31
31
  }
32
32
 
33
+ // Clifland Heuristic: Auto-detect the primary label for the resource
34
+ const fieldNames = fields.map(f => f.name)
35
+ const labelField = fieldNames.find(n => n === 'name')
36
+ || fieldNames.find(n => n === 'title')
37
+ || fieldNames.find(n => n === 'email')
38
+ || 'id'
39
+
33
40
  try {
34
41
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
35
42
  const config = getTableConfig(table as any)
@@ -57,6 +64,7 @@ export function drizzleTableToFields(table: any, resourceName: string) {
57
64
 
58
65
  return {
59
66
  resource: resourceName,
67
+ labelField, // metadata point
60
68
  fields,
61
69
  }
62
70
  }