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

nest-drift

v0.1.0

Published

CLI and library to detect schema drift in NestJS projects

Readme

nest-drift

Detects schema drift in NestJS projects. Validates consistency between TypeORM entities and DTOs, snapshots your schema, diffs changes over time, and cross-checks LLM tool definitions against your actual codebase.

Available as both a CLI tool and a Node.js library.


The problem

Someone renames a field in a TypeORM entity. The DTO doesn't get updated. The LLM tool definition still references the old field name. Everything breaks silently — in production, or worse, inside an AI agent's tool call.

nest-drift catches this at the source.


Installation

npm install -D nest-drift

Usage: CLI

Run commands directly from the terminal or npm scripts. Ideal for CI pipelines and pre-commit hooks.

check — validate entities vs DTOs

Scans for @Entity classes and their related DTOs, then reports missing fields and type mismatches.

npx nest-drift check .
npx nest-drift check ./my-nestjs-project
nest-drift check
Scanning: .

Found 3 entities, 7 DTOs

OK   User <-> CreateUserDto
FAIL User <-> UpdateUserDto
     Field 'email': type mismatch — entity has 'string', UpdateUserDto has 'number'
     Field 'role' exists in User but is missing from UpdateUserDto

OK   Product <-> CreateProductDto

Issues found. Review the above.

Exits with code 1 if issues are found.


snapshot — capture the current schema

Serializes all entities and DTOs to a JSON file you can commit as a baseline.

npx nest-drift snapshot .
npx nest-drift snapshot . --output custom-snap.json

diff — compare against a snapshot

Compares the current state of your project against a previously generated snapshot.

npx nest-drift diff nest-drift.snapshot.json .
Entities
  ~ Entity: User
    ~ field 'email': string -> number (type changed)
    - field 'name': string (removed)
    + field 'phone': string (added)

DTOs
  + DTO: CreateUserDto (new)
  - DTO: UpdateUserDto (removed)

Schema has changed since last snapshot.

Exits with code 1 if changes are detected.


validate — cross-check LLM tool definitions

Reads your LLM tool definitions (JSON or YAML) and verifies that every property maps to a real field in your codebase with a compatible type.

npx nest-drift validate tools.json .
npx nest-drift validate tools.yaml .

Supports both Anthropic (input_schema) and OpenAI (parameters) tool formats.

{
  "tools": [
    {
      "name": "create_user",
      "input_schema": {
        "type": "object",
        "properties": {
          "email": { "type": "string" },
          "age":   { "type": "integer" }
        }
      }
    }
  ]
}
Found 2 tools

OK   create_user -> CreateUserDto (src/user/user.dto.ts)
FAIL update_user -> UpdateUserDto
     Tool 'update_user': property 'username' not found in codebase

Validation failed. Tool definitions are out of sync.

watch — re-run check on file changes

Watches the project for .ts file changes and re-runs check automatically. Clears the terminal and shows a fresh report on every save, with a 300ms debounce to avoid noise on rapid edits.

npx nest-drift watch .
nest-drift watch [10:42:31]

Found 3 entities, 7 DTOs

OK   User <-> CreateUserDto
FAIL User <-> UpdateUserDto
     Field 'role' exists in User but is missing from UpdateUserDto

OK   Product <-> CreateProductDto

Issues found.

Press Ctrl+C to stop.


CLI in package.json scripts

{
  "scripts": {
    "drift:check":    "nest-drift check .",
    "drift:snapshot": "nest-drift snapshot .",
    "drift:diff":     "nest-drift diff nest-drift.snapshot.json .",
    "drift:watch":    "nest-drift watch ."
  }
}

Pre-commit hook (Husky)

npx husky add .husky/pre-commit "nest-drift check ."

CI pipeline (GitHub Actions)

- name: Check schema drift
  run: npx nest-drift diff nest-drift.snapshot.json .

Usage: Library

Import the functions directly in your TypeScript/JavaScript code and work with the results programmatically. Ideal when you need custom reporting, alerting, or integration with other tools.

Installation

npm install nest-drift

check(path)

Returns an object describing the consistency state between entities and DTOs.

import { check } from 'nest-drift'

const report = check('./src')

console.log(`${report.entityCount} entities, ${report.dtoCount} DTOs`)

if (report.hasIssues) {
  for (const result of report.results) {
    if (!result.ok) {
      console.log(`[${result.entity}] → ${result.dto ?? 'no DTO found'}`)
      for (const issue of result.issues) {
        // issue.kind: "no_dto" | "field_missing" | "type_mismatch"
        console.log(`  ${issue.kind}: ${issue.message}`)
      }
    }
  }
}

snapshot(path)

Returns the parsed schema as a plain object — no file is written.

import { snapshot } from 'nest-drift'

const schema = snapshot('./src')

console.log(schema.entities) // EntitySchema[]
console.log(schema.dtos)     // DtoSchema[]

// Each schema has: { name, file, fields: [{ name, fieldType, optional }] }

If you want to save it to disk:

import { snapshot } from 'nest-drift'
import { writeFileSync } from 'fs'

const schema = snapshot('./src')
writeFileSync('nest-drift.snapshot.json', JSON.stringify(schema, null, 2))

diff(snapshotPath, path)

Compares a snapshot file against the current state of the project.

import { diff } from 'nest-drift'

const report = diff('nest-drift.snapshot.json', './src')

if (report.hasChanges) {
  for (const change of report.changes) {
    // change.kind: "added" | "removed" | "modified"
    // change.schemaType: "entity" | "dto"
    console.log(`${change.kind} ${change.schemaType}: ${change.name}`)

    for (const fc of change.fieldChanges) {
      // fc.kind: "added" | "removed" | "type_changed" | "optionality_changed"
      console.log(`  ${fc.kind}: ${fc.field} (${fc.before} → ${fc.after})`)
    }
  }

  // Custom alerting
  await sendSlackAlert(report.changes)
}

Throws if the snapshot file doesn't exist or is invalid.


watch(path, callback)

Watches the project for .ts file changes and calls callback with a fresh CheckReport on every change (debounced 300ms). Returns a stop function.

import { watch } from 'nest-drift'

const stop = watch('./src', (report) => {
  if (report.hasIssues) {
    for (const result of report.results.filter(r => !r.ok)) {
      console.log(`[${result.entity}] has issues`)
    }

    // Custom alerting — Slack, webhook, whatever you need
    await sendSlackAlert(report.results)
  }
})

// Stop watching when done
process.on('SIGINT', stop)

Keeps the process alive while watching. Call stop() to shut down the watcher and release resources.


validate(toolsPath, path)

Validates LLM tool definitions against the codebase.

import { validate } from 'nest-drift'

const report = validate('./tools.json', './src')

for (const result of report.results) {
  if (result.ok) {
    console.log(`✓ ${result.tool} → ${result.matchedSchema}`)
  } else {
    console.log(`✗ ${result.tool}`)
    for (const issue of result.issues) {
      // issue.kind: "no_match" | "property_missing" | "type_mismatch" | "no_schema"
      console.log(`  ${issue.message}`)
    }
  }
}

Full example: custom CI script

import { check, diff } from 'nest-drift'
import { execSync } from 'child_process'

const checkReport = check('./src')
const diffReport  = diff('nest-drift.snapshot.json', './src')

if (checkReport.hasIssues || diffReport.hasChanges) {
  const summary = {
    checkIssues:  checkReport.results.filter(r => !r.ok).map(r => r.entity),
    schemaChanges: diffReport.changes.map(c => `${c.kind} ${c.name}`),
  }

  // Post to Slack, save to DB, open a GitHub issue — whatever you need
  await notifyTeam(summary)
  process.exit(1)
}

Return types

// check()
interface CheckReport {
  entityCount: number
  dtoCount: number
  hasIssues: boolean
  results: EntityCheckResult[]
}
interface EntityCheckResult {
  entity: string
  dto: string | null
  ok: boolean
  issues: { kind: 'no_dto' | 'field_missing' | 'type_mismatch', message: string }[]
}

// snapshot()
interface ProjectSnapshot {
  entities: EntitySchema[]
  dtos: DtoSchema[]
}
interface EntitySchema {
  name: string
  file: string
  fields: { name: string, fieldType: string, optional: boolean }[]
}

// diff()
interface DiffReport {
  hasChanges: boolean
  changes: SchemaChange[]
}
interface SchemaChange {
  kind: 'added' | 'removed' | 'modified'
  schemaType: 'entity' | 'dto'
  name: string
  fieldChanges: FieldChange[]
}
interface FieldChange {
  kind: 'added' | 'removed' | 'type_changed' | 'optionality_changed'
  field: string
  before: string | null
  after: string | null
}

// watch()
type StopFn = () => void
function watch(path: string, callback: (report: CheckReport) => void): StopFn

// validate()
interface ValidateReport {
  toolCount: number
  hasIssues: boolean
  results: ToolValidateResult[]
}
interface ToolValidateResult {
  tool: string
  matchedSchema: string | null
  matchedFile: string | null
  ok: boolean
  issues: { kind: 'no_match' | 'property_missing' | 'type_mismatch' | 'no_schema', message: string }[]
}

Built with


License

MIT