nuxt-auto-crud 1.21.0 โ†’ 1.22.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 CHANGED
@@ -1,6 +1,6 @@
1
1
  # Nuxt Auto CRUD
2
2
 
3
- > **Note:** This module is widely used in MVP development and is rapidly maturing. While currently in **Beta**, the core API (CRUD) is stable. Breaking changes may still occur in configuration or advanced features. Production use is encouraged with version pinning.
3
+ > **Note:** This module is production-ready and actively maintained. The core CRUD API is stable. Minor breaking changes may occasionally occur in advanced features or configuration. We recommend version pinning for production deployments.
4
4
 
5
5
  Auto-expose RESTful CRUD APIs for your **Nuxt** application based solely on your database schema. Minimal configuration required.
6
6
 
@@ -56,20 +56,13 @@ If you want to add `nuxt-auto-crud` to an existing project, follow these steps:
56
56
  #### Install dependencies
57
57
 
58
58
  ```bash
59
- # Install module and required dependencies
60
59
  npm install nuxt-auto-crud @nuxthub/core@^0.10.0 drizzle-orm
61
-
62
- # Optional: Install auth dependencies if using Session Auth (Recommended)
63
- npm install nuxt-auth-utils nuxt-authorization
64
-
60
+ npm install nuxt-auth-utils nuxt-authorization # Optional: for authentication
65
61
  npm install --save-dev wrangler drizzle-kit
66
-
67
- # Or using bun
68
- bun add nuxt-auto-crud @nuxthub/core@latest drizzle-orm
69
- bun add nuxt-auth-utils nuxt-authorization
70
- bun add --dev wrangler drizzle-kit
71
62
  ```
72
63
 
64
+ > You can also use `bun` or `pnpm` instead of `npm`.
65
+
73
66
  #### Configure Nuxt
74
67
 
75
68
  Add the modules to your `nuxt.config.ts`:
@@ -227,81 +220,18 @@ export default defineConfig({
227
220
  })
228
221
  ```
229
222
 
230
- ## ๐Ÿ” Authentication Configuration
231
-
232
- The module enables authentication by default. To test APIs without authentication, you can set `auth: false`.
233
-
234
- ### Session Auth (Default)
235
-
236
- Requires `nuxt-auth-utils` and `nuxt-authorization` to be installed in your project.
237
-
238
- ```bash
239
- npm install nuxt-auth-utils nuxt-authorization
240
- ```
241
-
242
- ```typescript
243
- export default defineNuxtConfig({
244
- modules: [
245
- 'nuxt-auth-utils',
246
- 'nuxt-authorization',
247
- 'nuxt-auto-crud'
248
- ],
249
- autoCrud: {
250
- auth: {
251
- type: 'session',
252
- authentication: true, // Enables requireUserSession() check
253
- authorization: true // Enables authorize(model, action) check
254
- }
255
- }
256
- })
257
- ```
258
-
259
- ### JWT Auth
223
+ ## ๐Ÿ” Authentication
260
224
 
261
- Useful for backend-only apps. Does **not** require `nuxt-auth-utils`.
225
+ Authentication is enabled by default using **Session Auth** (requires `nuxt-auth-utils` and `nuxt-authorization`).
262
226
 
227
+ To disable auth for testing:
263
228
  ```typescript
264
- export default defineNuxtConfig({
265
- autoCrud: {
266
- auth: {
267
- type: 'jwt',
268
- authentication: true,
269
- jwtSecret: process.env.JWT_SECRET,
270
- authorization: true
271
- }
272
- }
273
- })
229
+ autoCrud: { auth: false }
274
230
  ```
275
231
 
276
- ### Disabling Auth
232
+ For **JWT Auth** (backend-only apps) or advanced configuration, see the [Authentication docs](https://auto-crud.clifland.in/docs/configuration/authentication).
277
233
 
278
- You can disable authentication entirely for testing or public APIs.
279
234
 
280
- ```typescript
281
- export default defineNuxtConfig({
282
- autoCrud: {
283
- auth: {
284
- authentication: false,
285
- authorization: false
286
- }
287
- // OR simply:
288
- // auth: false
289
- }
290
- })
291
- ```
292
-
293
- ## ๐Ÿงช Testing
294
-
295
- This module is tested using **Vitest**.
296
-
297
- - **Unit Tests:** We cover utility functions and helpers.
298
- - **E2E Tests:** We verify the API endpoints using a real Nuxt server instance.
299
-
300
- To run the tests locally:
301
-
302
- ```bash
303
- npm run test
304
- ```
305
235
 
306
236
  ## ๐Ÿ›ก๏ธ Public View Configuration (Field Visibility)
307
237
 
@@ -346,12 +276,6 @@ export const posts = sqliteTable('posts', {
346
276
  })
347
277
  ```
348
278
 
349
- ## โš ๏ธ Known Issues
350
-
351
- - **Automatic Relation Expansion:** The module tries to automatically expand foreign keys (e.g., `user_id` -> `user: { name: ... }`). However, this relies on the foreign key column name matching the target table name (e.g., `user_id` for `users`).
352
- - **Limitation:** If you have custom FK names like `customer_id` or `author_id` pointing to `users`, the automatic expansion will not work yet.
353
- - **Workaround:** Ensure your FK columns follow the `tablename_id` convention where possible for now.
354
-
355
279
  ## ๐ŸŽฎ Try the Playground
356
280
 
357
281
  Want to see it in action? Clone this repo and try the playground:
@@ -421,51 +345,7 @@ await $fetch("/api/users/1", {
421
345
  > - **Fullstack App:** The module integrates with `nuxt-auth-utils`, so session cookies are handled automatically.
422
346
  > - **Backend-only App:** You must include the `Authorization: Bearer <token>` header in your requests.
423
347
 
424
- ## Configuration
425
-
426
- ### Module Options
427
-
428
- ```typescript
429
348
 
430
- export default defineNuxtConfig({
431
- autoCrud: {
432
- // Path to your database schema file (relative to project root)
433
- schemaPath: "server/db/schema", // default
434
-
435
- // Authentication configuration (see "Authentication Configuration" section)
436
- auth: {
437
- // ...
438
- },
439
-
440
- // Public Guest View Configuration (Field Visibility)
441
- resources: {
442
- users: ['id', 'name', 'avatar'],
443
- },
444
- },
445
- });
446
- ```
447
-
448
- ### Protected Fields
449
-
450
- By default, the following fields are protected from updates:
451
-
452
- - `id`
453
- - `createdAt`
454
- - `created_at`
455
- - `updatedAt`
456
- - `updated_at`
457
-
458
- You can customize updatable fields in your schema by modifying the `modelMapper.ts` utility.
459
-
460
- ### Hidden Fields
461
-
462
- By default, the following fields are hidden from API responses for security:
463
-
464
- - `password`
465
- - `secret`
466
- - `token`
467
-
468
- You can customize hidden fields by modifying the `modelMapper.ts` utility.
469
349
 
470
350
  ## ๐Ÿ”ง Requirements
471
351
 
@@ -473,18 +353,12 @@ You can customize hidden fields by modifying the `modelMapper.ts` utility.
473
353
  - Drizzle ORM (SQLite)
474
354
  - NuxtHub >= 0.10.0
475
355
 
476
- ## ๐Ÿ”— Other Helpful Links
477
-
478
- - **Template:** [https://github.com/clifordpereira/nuxt-auto-crud_template](https://github.com/clifordpereira/nuxt-auto-crud_template)
479
- - **Docs:** [https://auto-crud.clifland.in/docs/auto-crud](https://auto-crud.clifland.in/docs/auto-crud)
480
- - **Repo:** [https://github.com/clifordpereira/nuxt-auto-crud](https://github.com/clifordpereira/nuxt-auto-crud)
481
- - **YouTube (Installation):** [https://youtu.be/M9-koXmhB9k](https://youtu.be/M9-koXmhB9k)
482
- - **YouTube (Add Schemas):** [https://youtu.be/7gW0KW1KtN0](https://youtu.be/7gW0KW1KtN0)
483
- - **YouTube (Various Permissions):** [https://www.youtube.com/watch?v=Yty3OCYbwOo](https://www.youtube.com/watch?v=Yty3OCYbwOo)
484
- - **YouTube (Dynamic RBAC):** [https://www.youtube.com/watch?v=W0ju4grRC9M](https://www.youtube.com/watch?v=W0ju4grRC9M)
485
- - **npm:** [https://www.npmjs.com/package/nuxt-auto-crud](https://www.npmjs.com/package/nuxt-auto-crud)
486
- - **Github Discussions:** [https://github.com/clifordpereira/nuxt-auto-crud/discussions/1](https://github.com/clifordpereira/nuxt-auto-crud/discussions/1)
487
- - **Discord:** [https://discord.gg/hGgyEaGu](https://discord.gg/hGgyEaGu)
356
+ ## ๐Ÿ”— Links
357
+
358
+ - **๐Ÿ“š Documentation:** [auto-crud.clifland.in](https://auto-crud.clifland.in/docs/auto-crud)
359
+ - **๐ŸŽฌ Video Tutorials:** [YouTube Channel](https://www.youtube.com/@ClifordPereira)
360
+ - **๐Ÿ’ฌ Community:** [Discord](https://discord.gg/FBkQQfRFJM) โ€ข [GitHub Discussions](https://github.com/clifordpereira/nuxt-auto-crud/discussions/1)
361
+ - **๐Ÿ“ฆ npm:** [nuxt-auto-crud](https://www.npmjs.com/package/nuxt-auto-crud)
488
362
 
489
363
  ## ๐Ÿค Contributing
490
364
 
package/dist/module.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "nuxt-auto-crud",
3
3
  "configKey": "autoCrud",
4
- "version": "1.21.0",
4
+ "version": "1.22.1",
5
5
  "builder": {
6
6
  "@nuxt/module-builder": "1.0.2",
7
7
  "unbuild": "3.6.1"
@@ -1,8 +1,9 @@
1
- import { eventHandler, getRouterParams, createError } from "h3";
1
+ import { eventHandler, getRouterParams } from "h3";
2
2
  import { eq } from "drizzle-orm";
3
3
  import { getTableForModel, getModelSingularName } from "../../utils/modelMapper.js";
4
4
  import { db } from "hub:db";
5
5
  import { ensureResourceAccess, formatResourceResult } from "../../utils/handler.js";
6
+ import { RecordNotFoundError } from "../../exceptions.js";
6
7
  export default eventHandler(async (event) => {
7
8
  const { model, id } = getRouterParams(event);
8
9
  const isAdmin = await ensureResourceAccess(event, model, "delete", { id });
@@ -10,10 +11,7 @@ export default eventHandler(async (event) => {
10
11
  const singularName = getModelSingularName(model);
11
12
  const deletedRecord = await db.delete(table).where(eq(table.id, Number(id))).returning().get();
12
13
  if (!deletedRecord) {
13
- throw createError({
14
- statusCode: 404,
15
- message: `${singularName} not found`
16
- });
14
+ throw new RecordNotFoundError(`${singularName} not found`);
17
15
  }
18
16
  return formatResourceResult(model, deletedRecord, isAdmin);
19
17
  });
@@ -1,27 +1,22 @@
1
- import { eventHandler, getRouterParams, createError } from "h3";
1
+ import { eventHandler, getRouterParams } from "h3";
2
2
  import { eq } from "drizzle-orm";
3
3
  import { getTableForModel } from "../../utils/modelMapper.js";
4
4
  import { db } from "hub:db";
5
5
  import { ensureResourceAccess, formatResourceResult } from "../../utils/handler.js";
6
6
  import { checkAdminAccess } from "../../utils/auth.js";
7
+ import { RecordNotFoundError } from "../../exceptions.js";
7
8
  export default eventHandler(async (event) => {
8
9
  const { model, id } = getRouterParams(event);
9
10
  const isAdmin = await ensureResourceAccess(event, model, "read");
10
11
  const table = getTableForModel(model);
11
12
  const record = await db.select().from(table).where(eq(table.id, Number(id))).get();
12
13
  if (!record) {
13
- throw createError({
14
- statusCode: 404,
15
- message: "Record not found"
16
- });
14
+ throw new RecordNotFoundError();
17
15
  }
18
16
  if ("status" in record && record.status !== "active") {
19
17
  const canListAll = await checkAdminAccess(event, model, "list_all");
20
18
  if (!canListAll) {
21
- throw createError({
22
- statusCode: 404,
23
- message: "Record not found"
24
- });
19
+ throw new RecordNotFoundError();
25
20
  }
26
21
  }
27
22
  return formatResourceResult(model, record, isAdmin);
@@ -1,9 +1,10 @@
1
- import { eventHandler, getRouterParams, readBody, createError } from "h3";
1
+ import { eventHandler, getRouterParams, readBody } from "h3";
2
2
  import { getUserSession } from "#imports";
3
3
  import { eq } from "drizzle-orm";
4
4
  import { getTableForModel, filterUpdatableFields } from "../../utils/modelMapper.js";
5
5
  import { db } from "hub:db";
6
6
  import { ensureResourceAccess, formatResourceResult, hashPayloadFields } from "../../utils/handler.js";
7
+ import { RecordNotFoundError } from "../../exceptions.js";
7
8
  export default eventHandler(async (event) => {
8
9
  const { model, id } = getRouterParams(event);
9
10
  const isAdmin = await ensureResourceAccess(event, model, "update", { id });
@@ -25,10 +26,7 @@ export default eventHandler(async (event) => {
25
26
  }
26
27
  const updatedRecord = await db.update(table).set(payload).where(eq(table.id, Number(id))).returning().get();
27
28
  if (!updatedRecord) {
28
- throw createError({
29
- statusCode: 404,
30
- message: "Record not found"
31
- });
29
+ throw new RecordNotFoundError();
32
30
  }
33
31
  return formatResourceResult(model, updatedRecord, isAdmin);
34
32
  });
@@ -0,0 +1,9 @@
1
+ export declare class AutoCrudError extends Error {
2
+ statusCode: number;
3
+ statusMessage?: string;
4
+ constructor(message: string, statusCode: number);
5
+ toH3Error(): import("h3").H3Error<unknown>;
6
+ }
7
+ export declare class RecordNotFoundError extends AutoCrudError {
8
+ constructor(message?: string);
9
+ }
@@ -0,0 +1,21 @@
1
+ import { createError } from "h3";
2
+ export class AutoCrudError extends Error {
3
+ statusCode;
4
+ statusMessage;
5
+ constructor(message, statusCode) {
6
+ super(message);
7
+ this.statusCode = statusCode;
8
+ this.statusMessage = message;
9
+ }
10
+ toH3Error() {
11
+ return createError({
12
+ statusCode: this.statusCode,
13
+ message: this.message
14
+ });
15
+ }
16
+ }
17
+ export class RecordNotFoundError extends AutoCrudError {
18
+ constructor(message = "Record not found") {
19
+ super(message, 404);
20
+ }
21
+ }
@@ -19,12 +19,13 @@ export function drizzleTableToFields(table, resourceName) {
19
19
  const config = getTableConfig(table);
20
20
  config.foreignKeys.forEach((fk) => {
21
21
  const sourceColumnName = fk.reference().columns[0].name;
22
- const field = fields.find((f) => {
23
- return f.name === sourceColumnName;
24
- });
25
- if (field) {
26
- const targetTable = fk.reference().foreignTable[Symbol.for("drizzle:Name")];
27
- field.references = targetTable;
22
+ const propertyName = Object.entries(columns).find(([_, col]) => col.name === sourceColumnName)?.[0];
23
+ if (propertyName) {
24
+ const field = fields.find((f) => f.name === propertyName);
25
+ if (field) {
26
+ const targetTable = fk.reference().foreignTable[Symbol.for("drizzle:Name")];
27
+ field.references = targetTable;
28
+ }
28
29
  }
29
30
  });
30
31
  } catch {
@@ -51,6 +52,9 @@ function mapColumnType(column) {
51
52
  }
52
53
  return { type: "number" };
53
54
  }
55
+ if (["content", "description", "bio", "message"].includes(column.name)) {
56
+ return { type: "textarea" };
57
+ }
54
58
  return { type: "string" };
55
59
  }
56
60
  export async function getRelations() {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "nuxt-auto-crud",
3
- "version": "1.21.0",
3
+ "version": "1.22.1",
4
4
  "description": "Exposes RESTful CRUD APIs for your Nuxt app based solely on your database migrations.",
5
5
  "author": "Cliford Pereira",
6
6
  "license": "MIT",
@@ -35,8 +35,8 @@
35
35
  ],
36
36
  "scripts": {
37
37
  "prepack": "nuxt-module-build build",
38
- "dev": "npm run dev:prepare && nuxi dev playground",
39
- "dev:build": "nuxi build playground",
38
+ "dev": "bun run dev:prepare && nuxi dev --bun playground",
39
+ "dev:build": "nuxi build --bun playground",
40
40
  "dev:prepare": "nuxt-module-build build --stub && nuxt-module-build prepare && nuxi prepare playground",
41
41
  "release": "npm run lint && npm run test && npm run prepack && changelogen --release && npm publish && git push --follow-tags",
42
42
  "lint": "eslint .",
@@ -47,7 +47,7 @@
47
47
  "link": "npm link"
48
48
  },
49
49
  "dependencies": {
50
- "@nuxt/kit": "^4.2.1",
50
+ "@nuxt/kit": "^4.2.2",
51
51
  "@nuxt/scripts": "^0.13.0",
52
52
  "@types/pluralize": "^0.0.33",
53
53
  "c12": "^2.0.1",
@@ -78,6 +78,8 @@
78
78
  "drizzle-orm": "^0.38.3",
79
79
  "eslint": "^9.39.1",
80
80
  "nuxt": "^4.2.1",
81
+ "nuxt-auth-utils": "^0.5.26",
82
+ "nuxt-authorization": "^0.3.5",
81
83
  "typescript": "~5.9.3",
82
84
  "vitest": "^4.0.13",
83
85
  "vue-tsc": "^3.1.5",
@@ -1,11 +1,12 @@
1
1
  // server/api/[model]/[id].delete.ts
2
- import { eventHandler, getRouterParams, createError } from 'h3'
2
+ import { eventHandler, getRouterParams } from 'h3'
3
3
  import { eq } from 'drizzle-orm'
4
4
  import { getTableForModel, getModelSingularName } from '../../utils/modelMapper'
5
5
  import type { TableWithId } from '../../types'
6
6
  // @ts-expect-error - hub:db is a virtual alias
7
7
  import { db } from 'hub:db'
8
8
  import { ensureResourceAccess, formatResourceResult } from '../../utils/handler'
9
+ import { RecordNotFoundError } from '../../exceptions'
9
10
 
10
11
  export default eventHandler(async (event) => {
11
12
  const { model, id } = getRouterParams(event) as { model: string, id: string }
@@ -21,10 +22,7 @@ export default eventHandler(async (event) => {
21
22
  .get()
22
23
 
23
24
  if (!deletedRecord) {
24
- throw createError({
25
- statusCode: 404,
26
- message: `${singularName} not found`,
27
- })
25
+ throw new RecordNotFoundError(`${singularName} not found`)
28
26
  }
29
27
 
30
28
  return formatResourceResult(model, deletedRecord as Record<string, unknown>, isAdmin)
@@ -1,5 +1,5 @@
1
1
  // server/api/[model]/[id].get.ts
2
- import { eventHandler, getRouterParams, createError } from 'h3'
2
+ import { eventHandler, getRouterParams } from 'h3'
3
3
  import { eq } from 'drizzle-orm'
4
4
  import { getTableForModel } from '../../utils/modelMapper'
5
5
  import type { TableWithId } from '../../types'
@@ -7,6 +7,7 @@ import type { TableWithId } from '../../types'
7
7
  import { db } from 'hub:db'
8
8
  import { ensureResourceAccess, formatResourceResult } from '../../utils/handler'
9
9
  import { checkAdminAccess } from '../../utils/auth'
10
+ import { RecordNotFoundError } from '../../exceptions'
10
11
 
11
12
  export default eventHandler(async (event) => {
12
13
  const { model, id } = getRouterParams(event) as { model: string, id: string }
@@ -21,10 +22,7 @@ export default eventHandler(async (event) => {
21
22
  .get()
22
23
 
23
24
  if (!record) {
24
- throw createError({
25
- statusCode: 404,
26
- message: 'Record not found',
27
- })
25
+ throw new RecordNotFoundError()
28
26
  }
29
27
 
30
28
  // Filter inactive rows for non-admins (or those without list_all) if status field exists
@@ -32,10 +30,7 @@ export default eventHandler(async (event) => {
32
30
  if ('status' in record && (record as any).status !== 'active') {
33
31
  const canListAll = await checkAdminAccess(event, model, 'list_all')
34
32
  if (!canListAll) {
35
- throw createError({
36
- statusCode: 404,
37
- message: 'Record not found',
38
- })
33
+ throw new RecordNotFoundError()
39
34
  }
40
35
  }
41
36
 
@@ -1,5 +1,5 @@
1
1
  // server/api/[model]/[id].patch.ts
2
- import { eventHandler, getRouterParams, readBody, createError } from 'h3'
2
+ import { eventHandler, getRouterParams, readBody } from 'h3'
3
3
  import type { H3Event } from 'h3'
4
4
  import { getUserSession } from '#imports'
5
5
  import { eq } from 'drizzle-orm'
@@ -9,6 +9,7 @@ import type { TableWithId } from '../../types'
9
9
  // @ts-expect-error - hub:db is a virtual alias
10
10
  import { db } from 'hub:db'
11
11
  import { ensureResourceAccess, formatResourceResult, hashPayloadFields } from '../../utils/handler'
12
+ import { RecordNotFoundError } from '../../exceptions'
12
13
 
13
14
  export default eventHandler(async (event) => {
14
15
  const { model, id } = getRouterParams(event) as { model: string, id: string }
@@ -20,7 +21,6 @@ export default eventHandler(async (event) => {
20
21
  const body = await readBody(event)
21
22
  const payload = filterUpdatableFields(model, body)
22
23
 
23
- // Auto-hash fields based on config (default: ['password'])
24
24
  // Auto-hash fields based on config (default: ['password'])
25
25
  await hashPayloadFields(payload)
26
26
 
@@ -52,10 +52,7 @@ export default eventHandler(async (event) => {
52
52
  .get()
53
53
 
54
54
  if (!updatedRecord) {
55
- throw createError({
56
- statusCode: 404,
57
- message: 'Record not found',
58
- })
55
+ throw new RecordNotFoundError()
59
56
  }
60
57
 
61
58
  return formatResourceResult(model, updatedRecord as Record<string, unknown>, isAdmin)
@@ -0,0 +1,25 @@
1
+ import { createError } from 'h3'
2
+
3
+ export class AutoCrudError extends Error {
4
+ statusCode: number
5
+ statusMessage?: string
6
+
7
+ constructor(message: string, statusCode: number) {
8
+ super(message)
9
+ this.statusCode = statusCode
10
+ this.statusMessage = message
11
+ }
12
+
13
+ toH3Error() {
14
+ return createError({
15
+ statusCode: this.statusCode,
16
+ message: this.message,
17
+ })
18
+ }
19
+ }
20
+
21
+ export class RecordNotFoundError extends AutoCrudError {
22
+ constructor(message: string = 'Record not found') {
23
+ super(message, 404)
24
+ }
25
+ }
@@ -36,18 +36,18 @@ export function drizzleTableToFields(table: any, resourceName: string) {
36
36
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
37
37
  config.foreignKeys.forEach((fk: any) => {
38
38
  const sourceColumnName = fk.reference().columns[0].name
39
- // Find the field that matches this column
40
- const field = fields.find((f) => {
41
- // In simple cases, field.name matches column name.
42
- // If camelCase mapping handles it differently, we might need adjustments,
43
- // but typically Drizzle key = field name.
44
- return f.name === sourceColumnName
45
- })
46
-
47
- if (field) {
48
- // Get target table name
49
- const targetTable = fk.reference().foreignTable[Symbol.for('drizzle:Name')] as string
50
- field.references = targetTable
39
+
40
+ // Find the TS property name (key) that corresponds to this SQL column name
41
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
42
+ const propertyName = Object.entries(columns).find(([_, col]: [string, any]) => col.name === sourceColumnName)?.[0]
43
+
44
+ if (propertyName) {
45
+ const field = fields.find(f => f.name === propertyName)
46
+ if (field) {
47
+ // Get target table name
48
+ const targetTable = fk.reference().foreignTable[Symbol.for('drizzle:Name')] as string
49
+ field.references = targetTable
50
+ }
51
51
  }
52
52
  })
53
53
  }
@@ -86,6 +86,10 @@ function mapColumnType(column: any): { type: string, selectOptions?: string[] }
86
86
  return { type: 'number' }
87
87
  }
88
88
 
89
+ if (['content', 'description', 'bio', 'message'].includes(column.name)) {
90
+ return { type: 'textarea' }
91
+ }
92
+
89
93
  return { type: 'string' }
90
94
  }
91
95