npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

nuxt-auto-crud

v3.1.1

Published

Dynamic RESTful CRUD APIs for Nuxt without code generation, fully schema-driven.

Readme

nuxt-auto-crud (nac 3.x)

A Nuxt.js module providing dynamic RESTful CRUD APIs derived directly from your Drizzle schemas, without writing any code for CRUD operations.


🚀 Core Features

  • Zero-Codegen Dynamic RESTful CRUD APIs: nuxt-auto-crud leverages Drizzle ORM, Zod, Nuxt, and Nitro to eliminate the need for manual CRUD coding.
  • Single Source of Truth (SSOT): Your Drizzle schemas (server/db/schema) define the entire API structure and validation.
  • Constant Bundle Size: Since no code is generated, the bundle size remains virtually identical whether you have one table or one hundred (scaling only with your schema definitions).

⚠️ Upgrading to 3.0.0 — Breaking Change

GET list endpoints (/api/_nac/:model) no longer return a bare array. They now return:

interface NacPaginatedResponse<T> {
  data: T[]
  meta: {
    mode: 'offset' | 'simple' | 'cursor'
    perPage: number
    total?: number       // only present when the request includes ?total=true
    page?: number         // present in 'offset' and 'simple' modes
    nextCursor?: string   // present in 'cursor' mode, when more results exist
    hasMore: boolean
  }
}

Migration: anywhere your frontend does const items = await $fetch('/api/_nac/products'), change it to:

const { data: items } = await $fetch('/api/_nac/products')

This does not affect GET /:model/:id, POST, PATCH, or DELETE — those still return a single record object directly, unchanged.

Why: a bare array gave clients no way to know if more results existed without over-fetching, and no way to page reliably.

Fixes:

  • CRUD authorization (create/read/update/delete, and their _own variants) was previously enforced only on list operations — nacGetRow/nacCreateRow/nacUpdateRow/nacDeleteRow now consistently check permissions too.
  • The list_active permission code has been renamed to list, matching the intended vocabulary (list_all / list / list_own) — update any resourcePermissions arrays your app supplies.

See 🛡 Filtering & Performance Optimization for the full CRUD permission model, and 📄 Pagination & Filtering for the new response shape's fields.


Supported Databases

  • SQLite (libSQL)
  • MySQL

Installation Guide (SQLite)

Option A: Starter Template

npx nuxi init -t gh:clifordpereira/nac-starter my-app
cd my-app
nuxt db generate
nuxt dev

Option B: Manual Installation

bun create nuxt@latest my-app
cd my-app
npx nuxi module add hub
bun add drizzle-orm@rc @libsql/client nuxt-auto-crud
bun add -D drizzle-kit@rc typescript

Configuration

Update nuxt.config.ts:

export default defineNuxtConfig({
  modules: [
    '@nuxthub/core',
    'nuxt-auto-crud'
  ],
  hub: {
    db: 'sqlite'
  }
})

Schema Definition

Define your schema in server/db/schema.ts:

import { snakeCase, text, integer, numeric } from 'drizzle-orm/sqlite-core'

export const products = snakeCase.table('products', {
  id: integer('id').primaryKey({ autoIncrement: true }),
  name: text('name').notNull(),
  sku: text('sku').notNull(),
  price: numeric('price', { mode: 'number' }).notNull(),
  stock: integer('stock').notNull(),
  createdAt: integer({ mode: 'timestamp' }).notNull().$defaultFn(() => new Date()),
  updatedAt: integer({ mode: 'timestamp' }).notNull().$onUpdate(() => new Date()),
})

Generate Migrations and Start Dev Server

nuxt db generate
nuxt dev

For MySQL installation instructions, visit INSTALLATION.md.

Authentication & Sensitive Fields

NAC automatically filters out password fields across all dynamic endpoints, as encryption and authentication logic are deferred to the implementing application.

Consequently, defining a password field as notNull() in your schema will trigger a database NOT NULL constraint failed error during record creation via NAC.

  • Production: Handle authentication and password persistence in your application-level endpoints, keeping the field outside of NAC's automated scope.
  • Testing/Checkouts: If you are testing the core module directly against a schema containing a password, mark the field as optional (nullable). NAC will then leave the column blank without throwing constraint errors.

Conventions

NAC relies on specific naming conventions to enable zero-config relations and dynamic rendering:

  • Field Naming Case: All database columns must use snake_case.
  • Dynamic Field Labels: For parent-child relationship resolution, NAC dynamically falls back through a specific priority chain to resolve the text label of a record: name || title || num || id.
  • Custom Identifiers (e.g., Orders): While most parent tables naturally expose a name or title column, tables utilizing tracking numbers (like orders) often use order_number. To leverage NAC's automatic label mapping for these schemas, name the column exactly num instead of order_number.

🌐 Data APIs (Dynamic RESTful CRUD)

Note: All endpoints follow the pattern ${apiBase}/:model. By default, this is /api/_nac/:model.

| Method | Endpoint | Action | | --- | --- | --- | | GET | /api/_nac/:model | List records | | POST | /api/_nac/:model | Create record with Zod validation | | GET | /api/_nac/:model/:id | Fetch single record | | PATCH | /api/_nac/:model/:id | Partial update with validation | | DELETE | /api/_nac/:model/:id | Delete record |

Example (products table):

| Action | HTTP Method | Endpoint | Example Result | | --- | --- | --- | --- | | Fetch All | GET | /api/_nac/products | List of all products | | Create | POST | /api/_nac/products | New product record added | | Fetch One | GET | /api/_nac/products/1 | Details of product with id: 1 | | Update | PATCH | /api/_nac/products/1 | Partial update to product 1 | | Delete | DELETE | /api/_nac/products/1 | Product 1 removed from DB |


📄 Pagination & Filtering

GET /api/_nac/:model accepts query-string params for pagination and simple equality filtering.

Pagination modes

| Mode | Triggered by | meta fields | | --- | --- | --- | | simple | No pagination params at all | perPage, hasMore, page | | offset | ?page= or ?offset= present | perPage, hasMore, page | | cursor | ?cursor= present (and the table has an id column) | perPage, hasMore, nextCursor |

Examples: GET /api/_nac/products?limit=20 → mode: 'simple' GET /api/_nac/products?page=2&limit=20 → mode: 'offset' GET /api/_nac/products?offset=40&limit=20 → mode: 'offset' GET /api/_nac/products?cursor=118&limit=20 → mode: 'cursor'

Fields:

  • limit — defaults to 50, capped at 200.
  • hasMore — always present; computed by fetching one row past limit, at no extra query cost.
  • total — a COUNT(*) query, so it's opt-in only: add ?total=true to any request, regardless of mode, to include it.
  • Cursor pagination returns id-descending results by default and only supports that default ordering — a resource whose nacTableQueryConfig sets a custom orderBy should use offset/page instead, since a cursor built against a different sort order would return a mismatched page.
  • Cursor mode currently returns nextCursor only (forward paging). Backward navigation is left to the client keeping its own history of visited cursors — the same pattern used by Stripe and GitHub's REST APIs.

Equality filtering

Any other query param matching a visible column name is applied as an equality filter, e.g. ?status=active. Filters are resolved only against fields the caller can actually see (post hidden-field / public-resource narrowing) — an unrecognized or hidden key is silently ignored, never partially honored, so filtering can't be used to infer a hidden field's value.


🛠 Introspection & Metadata APIs

Use these endpoints to build dynamic UI components (like menus and forms) or provide context to AI agents. These use the _schemas and _meta reserved paths.

1. Discovery Endpoints

  • List Resource Names: GET /api/_nac/_schemas
  • Returns an array of all available table names. Useful for generating dynamic navigation menus.
  • Resource Metadata: GET /api/_nac/_schemas/:resource
  • Returns field definitions, validation rules, and readonly status for a specific table.
  • Example: GET /api/_nac/_schemas/products returns the schema for the products table.

Schema Interface

export interface Field {
  name: string
  type: string
  required?: boolean
  selectOptions?: string[]
  references?: string
  readonly?: boolean
}

export interface SchemaDefinition {
  resource: string
  labelField: string
  fields: Field[]
}

Example Response

GET /api/_nac/_schemas/products

{
  "resource": "products",
  "labelField": "name",
  "fields": [
    { "name": "id", "type": "number", "required": true, "readonly": true },
    { "name": "name", "type": "string", "required": true, "readonly": false },
    { "name": "sku", "type": "string", "required": true, "readonly": false },
    { "name": "price", "type": "number", "required": true, "readonly": false },
    { "name": "stock", "type": "number", "required": true, "readonly": false }
  ]
}

_schemas endpoints are always accessible, regardless of auth.authentication — they return field metadata (names, types, required/readonly flags), never row data. _meta is separately gated by its own agenticToken, independent of session auth.

2. Agentic Discovery

  • Manifest: GET /api/_nac/_meta?format=md
  • Returns a token-efficient Markdown manifest for LLM context injection.
  • Security: Requires NUXT_AUTO_CRUD_AGENTIC_TOKEN (min 16 characters) in your .env.

🛡 Security & Configuration

Enabling authentication in the autoCrud config protects all nac routes (/api/_nac/*), except those explicitly defined in publicResources.

🔒 Access Control & Data Safety

  • apiHiddenFields: Globally hides sensitive columns from all API responses (read). Default: ['password', 'secret', 'token', 'resetToken', 'resetExpires', 'githubId', 'googleId'].
  • apiWriteProtectedFields: Server-enforced — fields a client can never set via POST/PATCH body, regardless of the frontend (mass-assignment protection). auth.ownerKey is always protected automatically, even if not listed explicitly.
  • formHiddenFields: Columns excluded from the _schemas metadata response, as a UI hint only. This does not block writes — use apiWriteProtectedFields for that.
  • formReadOnlyFields (deprecated): Was intended as a UI-only hint and was never enforced server-side. Manage read-only state in your frontend instead (e.g. nuxt-crud-table).

⚙️ Configuration Reference

| Key | Default | Description | | --- | --- | --- | | statusFiltering | false | Enables/disables automatic filtering of records based on the status column. | | realtime | false | Enables real-time broadcasting of all Create, Update, and Delete (CUD) operations via SSE. | | auth.authentication | false | Requires a valid session for all NAC routes. | | auth.authorization | false | Enables role/owner-based access checks. | | auth.ownerKey | 'createdBy' | The column name used to identify the record creator. | | auth.useNacSchema | false | Injects NAC's built-in authz schema (users, roles, resources, permissions, role_resource_permissions) into your database automatically. See 🔐 Authorization Schema & Seeding. | | db.dialect | 'sqlite' | Target database dialect. Supported: 'sqlite' (libSQL) and 'mysql'. | | publicResources | {} | Defines tables and specific columns accessible without auth. | | apiHiddenFields | NAC_API_HIDDEN_FIELDS | Arrays of keys to exclude from all API responses. | | apiWriteProtectedFields | NAC_API_WRITE_PROTECTED_FIELDS | Server-enforced fields a client can never set on create/update. auth.ownerKey is always included automatically. | | formHiddenFields | NAC_FORM_HIDDEN_FIELDS | Arrays of keys to exclude from dynamic forms. | | formReadOnlyFields | NAC_FORM_READ_ONLY_FIELDS | @deprecated — unenforced server-side. Configure in your frontend instead. | | agenticToken | '' | Secret key used to secure the /_meta endpoint, preventing unauthorized AI agents from introspecting your schema. | | nacEndpointPrefix | '/api/_nac' | @deprecated Use apiBase instead. | | apiBase | '/api/_nac' | The base path for NAC routes. Access via useRuntimeConfig().public.autoCrud. | | schemaPath | 'server/db/schema' | Location of your Drizzle schema files. |

Example nuxt.config.ts

autoCrud: {
  statusFiltering: false,
  realtime: false,
  auth: {
    authentication: false,
    authorization: false,
    ownerKey: 'createdBy', // change it if you want to use another column eg: ownerId
    useNacSchema: false, // set true to let NAC provide the roles/permissions tables for you
  },
  db: {
    dialect: 'sqlite', // 'sqlite' | 'mysql' (default is 'sqlite')
  },
  publicResources: {
    // guest users can access these tables and fields without authentication, even when auth.athentication is true
    products: ['id', 'name', 'sku', 'price'],
  },
  apiHiddenFields: ['createdBy'], // these fields are hidden from all API responses globally
  apiWriteProtectedFields: ['createdBy', 'updatedBy'], // mass-assignment protection; ownerKey is always included automatically
  formHiddenFields: ['createdAt', 'updatedAt'], // @deprecated: instead configure at frontend
  formReadOnlyFields: ['sku'], // @deprecated: instead configure at frontend
  agenticToken: '', // OPTIONAL. required to secure the /_meta endpoint
  nacEndpointPrefix: '/api/_nac', // @deprecated: instead use apiBase
  apiBase: '/api/_nac',
  schemaPath: 'server/db/schema',
}

Note: Modify apiBase or schemaPath only if the Nuxt/Nitro conventions change.

Per-Resource Field Overrides

apiHiddenFields, formHiddenFields, and apiWriteProtectedFields each accept either a flat array (applies to every table) or a scoped object for per-table control:

apiWriteProtectedFields: {
  default: ['createdBy', 'updatedBy'], // replaces the built-in default list; omit to keep it
  resources: {
    users: ['roleId'], // additionally protected, users table only
  },
},

Casing flexibility

Both the resources object's keys and its field-name values accept either casing:

  • Table keys — the physical snake_case table name (e.g. role_resource_permissions) or the camelCase schema export name (e.g. roleResourcePermissions) both resolve to the same table.
  • Field names — snake_case (e.g. role_id) or camelCase (e.g. roleId) both resolve to the same column, regardless of how that column happens to be declared in your schema.

Both of these are equally valid — different casing, same table, same fields:

apiWriteProtectedFields: {
  resources: {
    role_resource_permissions: ['roleId', 'resourceId'],
  },
},

// or, identically:
apiWriteProtectedFields: {
  resources: {
    roleResourcePermissions: ['role_id', 'resource_id'],
  },
},

publicResources follows the same rule for both its keys and values.


🛡 Filtering & Performance Optimization

CRUD Authorization

When auth.authorization is enabled, every operation is gated by event.context.nac.resourcePermissions — a flat array of codes scoped to the current resource, supplied by your app's own middleware (see Authorization Middleware below).

| Code | Grants | | --- | --- | | create | Create records | | read / read_own | Read any record / only records you own | | update / update_own | Update any record / only records you own | | delete / delete_own | Delete any record / only records you own | | list_all | List all records, full bypass | | list | List records, respecting statusFiltering if enabled | | list_own | List only records you own |

A caller with neither the full nor _own code for an operation is rejected with 403. For _own-scoped read/update/delete, a request against a record the caller doesn't own returns 404, not 403 — this is deliberate: it never confirms a not-owned record's existence to a caller who isn't authorized to see it.

Automatic Status Filtering

If statusFiltering is enabled, nac applies global visibility constraints. When a status column exists, queries are automatically restricted to active records. When both statusFiltering and the list permission apply together, hybrid OR logic is used: a caller sees all active records OR any record they own, regardless of its status.

Authorization Middleware

Populate event.context.nac from your own app's session/permission logic, before NAC's own guard runs. nacGetModelFromPath (exported from nuxt-auto-crud) resolves the :model a request targets, so your middleware never needs to re-implement NAC's route parsing:

// server/middleware/00.nac-permissions.ts
export default defineEventHandler(async (event) => {
  const model = nacGetModelFromPath(event.path)
  if (!model) return // not a NAC route

  const user = await resolveSessionUser(event) // your own session logic, eg: getUserSession() from nuxt-auth-utils
  if (!user) return

  // Merge, don't overwrite — NAC's own guard may already have set
  // event.context.nac.isPublic before or after this runs.
  event.context.nac = {
    ...event.context.nac,
    userId: user.id,
    resourcePermissions: await resolvePermissionsForModel(user, model), // eg: user.permissions[model] (store permissions in user on login)
  }
})

Optimization: Skip Redundant Fetches

If your middleware has already fetched the record, pass it to event.context.nac.record — nac will use this object instead of executing an additional database query.


🔐 Authorization Schema & Seeding

NAC can also provide the IAM (Identity and Access Management) tables your app's own authorization middleware relies on, plus helpers to fetch a user's permissions and to seed roles/permissions/users in one go.

1. Built-in Authz Schema (auth.useNacSchema)

Set auth.useNacSchema: true and NAC injects its own roles, resources, permissions, and role_resource_permissions tables straight into your Drizzle schema — you don't need to hand-write them (the users table must be provided by you and it should have a role_id column):

autoCrud: {
  auth: {
    authentication: true,
    authorization: true,
    useNacSchema: true,
  },
}

Leave this false if you already manage your own authz tables — NAC's authz helpers (below) work with any schema shaped like NacAuthzSchema (users, roles, resources, permissions, role_resource_permissions), whether NAC provided them or you defined them yourself.

If you use relations, add your relations path in relationsPath. Then add your relations and nacAuthzTableQueryConfig as shown below:

// server/db/relations.ts
import { defineRelations } from 'drizzle-orm'
import { nacAuthzRelationsConfig, nacAuthzTableQueryConfig } from '#nac/authz-relations'

export const relations = defineRelations(schema, r => ({
  ...nacAuthzRelationsConfig(r),
  // your app's own tables, added alongside
}))

export const nacTableQueryConfig = {
  ...nacAuthzTableQueryConfig,
  // your app's own tableQueryConfig, added alongside
}

Please note that you may use schemas (schema.ts) as usual.

2. Fetching a User's Permissions (nacGetPermissionsForUser)

Your Authorization Middleware just needs event.context.nac.resourcePermissions populated for the current user — how you resolve that is entirely up to you. nacGetPermissionsForUser is an auto-imported convenience helper for a quick start.

// server/api/login.post.ts
export default defineEventHandler(async (event) => {
  const { email, password } = await readBody(event)
  const user = await verifyCredentials(email, password) // your own logic

  const permissions = await nacGetPermissionsForUser(db, schema, user.id) // Then you can pass permissions[model] to event.context.nac.resourcePermissions in your auth middleware

  await setUserSession(event, { user, permissions })
  return { ok: true }
})

permissions resolves to a map of resource name → permission codes, e.g.:

{
  "products": ["list", "read"],
  "orders": ["list_own", "read_own", "update_own"]
}

3. Seeding Roles, Permissions & Users (defineAuthzSeed + nacSeedAuthz)

Declare your app's roles, resources, presets, and the users to seed with the auto-imported defineAuthzSeed helper — it's fully type-safe, so your editor will flag unknown roles or malformed permission codes as you write:

// server/config/authz-seed.ts - you can choose where this config lives, just import it in seed.ts
import { defineAuthzSeed } from 'nuxt-auto-crud'

export default defineAuthzSeed({
  usersToSeed: [
    { name: 'Admin User', email: '[email protected]', password: '$1Password', role: 'admin' },
    { name: 'Customer User', email: '[email protected]', password: '$1Password', role: 'user' },
  ],

  // Additional app tables beyond NAC's own authz tables (users, roles, resources, permissions, role_resource_permissions)
  resources: ['posts', 'comments'],

  // Optional shorthand groups, expanded into concrete codes below
  presets: {
    readOnly: ['list', 'read'],
  },

  roles: {
    admin: {
      permissions: 'all', // full access to every resource/permission combination
    },
    user: {
      permissions: {
        posts: ['readOnly'],
        comments: ['create', 'read_own', 'update_own'],
      },
    },
  },
})

Then run it from a Nitro task — nacSeedAuthz is auto-imported, so no extra import is needed (requires nitro.experimental.tasks: true in nuxt.config.ts):

// server/tasks/seed.ts
import seedConfig from '../config/authz-seed' // import from where you defined the config

export default defineTask({
  meta: {
    name: 'db:seed',
    description: 'Seed database with initial data',
  },
  async run() {
    console.log('Seeding database...')

    await nacSeedAuthz({
      db,
      schema,
      config: seedConfig,
      hashPassword, // your own hashing function, eg: hashPassword() from nuxt-auth-utils
    })

    return { result: 'Database seeded successfully' }
  },
})

nacSeedAuthz seeds permissions, resources, roles, one row per entry in usersToSeed, and then expands roles into the full role_resource_permissions grid — resolving any presets shorthand into concrete permission codes along the way.

nacSeedAuthz writes directly to users, roles, resources, permissions, and role_resource_permissions, so your schema needs those five tables — either from auth.useNacSchema: true or your own equivalent definitions.


📡 Real-time Synchronization (SSE)

When realtime is enabled, all create, update, and delete operations are automatically broadcasted:

if (realtime) {
  void broadcast({
    table: model,
    action: 'create',
    primaryKey: newRecord.id,
    data: newRecord,
  })
}

Frontend Usage

NAC provides a useNacAutoCrudSSE composable to listen for these changes in your frontend:

useNacAutoCrudSSE(({ table, action, data: sseData, primaryKey }) => {
  // Optional: Filter by specific table
  if (table !== 'products') return

  if (action === 'update') {
    // updateRow(primaryKey, sseData)
  }

  if (action === 'create') {
    // addRow(sseData)
  }

  if (action === 'delete') {
    // removeRow(primaryKey)
  }
})

🔗 Relations Support

nuxt-auto-crud natively supports Drizzle ORM relations, allowing you to fetch nested relational graphs, exclude or pick specific columns, and apply relational ordering automatically.

1. Update nuxt.config.ts

Define the path to your relations file in the module configuration options:

export default defineNuxtConfig({
  modules: ['nuxt-auto-crud'],
  autoCrud: {
    relationsPath: 'server/db/relations', // Specify the path to your relations configuration
  },
})

2. Define Relations & Query Configurations

Create your relations file (e.g., server/db/relations.ts).

⚠️ Important Notes:

  • nuxt-auto-crud utilizes the Drizzle RC API, so you must define relations using defineRelations() instead of the older relations() wrapper.
  • Your relations config map and query setup must be strictly exported as relations and nacTableQueryConfig respectively.
  • Any valid Drizzle DBQueryConfig parameter is supported in nacTableQueryConfig.

example for nacTableQueryConfig

// in relations.ts after your normal relations code
export const nacTableQueryConfig: Record<string, DBQueryConfig> = {
  users: {
    with: {
      role: { columns: { name: true } },
    },
  },
  roleResourcePermissions: {
    columns: {
      roleId: false,
      resourceId: false,
      permissionId: false,
    },
    with: {
      role: { columns: { name: true } },
      resource: { columns: { name: true } },
      permission: { columns: { code: true } },
    },
  },
}

A full example of relations.ts

⚠️ Casing note: nacTableQueryConfig keys are matched primarily by the camelCase schema export name (e.g. roleResourcePermissions), matching Drizzle's db.query[...] API — but NAC also falls back to the physical snake_case table name (e.g. role_resource_permissions) if no camelCase entry is found, so either form works here too. This matches the same casing flexibility as apiHiddenFields/apiWriteProtectedFields's resources keys (see Per-Resource Field Overrides above) — you no longer need to track which casing rule applies to which config surface.


Troubleshooting

Known Issues

drizzle-kit's RC releases (^1.0.0-rc.4) can resolve to an internal drizzle-orm snapshot pairing that's incompatible with the independently-resolved drizzle-orm version packet manager (eg: bun) installs for your app — this shows up as nuxt db generate throwing SQLiteSyncDialect is not a constructor, or an untagged rc.4 throwing event.req.text is not a function at runtime instead. nac-migrate-fresh (below) works around this by temporarily switching to the migration-capable build to generate migrations, then reinstalling to restore the app-compatible pairing.

For apps using nuxt-auto-crud

If you hit the same drizzle-kit/drizzle-orm version mismatch in your own project, a bun-based fixer script ships with this package, exposed via its package.json bin field:

"bin": {
  "nac-migrate-fresh": "./bin/migrate-fresh.sh"
}

Run it from your app's root (where your bun.lock lives):

npx nac-migrate-fresh

For more details — see the script's own header comment (node_modules/.bin/nac-migrate-fresh after install, or view it on GitHub)

🎨 Companion Frontend: nuxt-crud-table (nct)

nuxt-crud-table is a Nuxt UI component that renders CRUD tables and forms from an API's schema metadata. It is backend-agnostic: it reads whatever _schemas-shaped response the API provides and builds the table/form from that, so it works with NAC, Laravel, FastAPI, or any backend that returns metadata matching the expected format (see Schema Interface above).

Installing both nuxt-auto-crud and nuxt-crud-table in the same Nuxt project gives you generated CRUD APIs on the backend and a matching CRUD UI on the frontend, without hand-writing either.