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 🙏

© 2025 – Pkg Stats / Ryan Hefner

json-to-sql-migration

v2.1.2

Published

A TypeScript library that converts JSON-based data schemas into SQL migrations

Readme

JSON to SQL Migration Generator

A TypeScript library that converts JSON-based data schemas into SQL migrations with support for PostgreSQL and SQLite. This library provides a type-safe way to generate database migrations from declarative JSON schemas, including support for Row Level Security (RLS) policies in PostgreSQL.

License: MIT TypeScript Node.js

Features

  • 🔄 Schema Diffing: Compare two data models and generate incremental migrations
  • 🗄️ Multi-Database Support: PostgreSQL, SQLite (with and without extensions)
  • 🔒 Access Control: Built-in Row Level Security (RLS) support for PostgreSQL
  • 📊 Relationships: Foreign key constraints with cascade options
  • 🎯 Type Safety: Full TypeScript support with Zod schema validation
  • Fast: Built with Bun runtime for optimal performance
  • 🧪 Well Tested: Comprehensive test suite with integration tests

Installation

npm install json-to-sql-migration
# or
pnpm add json-to-sql-migration
# or
bun add json-to-sql-migration

Quick Start

import {
  type DataModel,
  generateDatabaseDiff,
  generateInitialMigration,
  generateMigrationFromDiff,
} from "json-to-sql-migration";
import { Dialect } from "json-to-sql-parser";

// Define your data model
const dataModel: DataModel = {
  tables: [
    {
      name: "users",
      fields: [
        {
          name: "id",
          type: "uuid",
          nonNullable: true,
          primaryKey: true,
        },
        {
          name: "email",
          type: "string",
          nonNullable: true,
        },
        {
          name: "created_at",
          type: "datetime",
          nonNullable: true,
          default: { $func: { now: [] } },
        },
      ],
      accessControl: {
        read: true,
        create: true,
        update: { $eq: [{ $field: "id" }, { $var: "user_id" }] },
        delete: false,
      },
    },
  ],
};

// Generate initial migration
const migration = generateInitialMigration(dataModel, Dialect.POSTGRESQL);
console.log(migration.sql);

Schema Definition

Data Model Structure

type DataModel = {
  tables: Array<{
    name: string;
    fields: Array<{
      name: string;
      type:
        | "string"
        | "number"
        | "boolean"
        | "object"
        | "date"
        | "datetime"
        | "uuid";
      nonNullable?: boolean;
      primaryKey?: boolean;
      default?: any; // JSON expression
      foreignKey?: {
        table: string;
        field: string;
        onDelete?: "cascade" | "restrict" | "set_null";
        onUpdate?: "cascade" | "restrict" | "set_null";
      };
    }>;
    accessControl: {
      read: Condition;
      create: Condition;
      update: Condition;
      delete: Condition;
    };
  }>;
};

Access Control Conditions

Access control conditions support complex expressions:

// Simple boolean
const condition1 = true;

// Field comparison
const condition2 = {
  $eq: [{ $field: "user_id" }, { $var: "current_user_id" }],
};

// Complex conditions
const condition3 = {
  $and: [
    { $eq: [{ $field: "status" }, "active"] },
    {
      $or: [
        { $eq: [{ $field: "owner_id" }, { $var: "user_id" }] },
        { $eq: [{ $field: "public" }, true] },
      ],
    },
  ],
};

API Reference

Core Functions

generateInitialMigration(model, dialect)

Generates a complete SQL migration from a data model.

const migration = generateInitialMigration(dataModel, Dialect.POSTGRESQL);
// Returns: { sql: string, accessControlDiff: AccessControlDiff }

generateDatabaseDiff(oldModel, newModel)

Compares two data models and returns a diff object.

const diff = generateDatabaseDiff(oldModel, newModel);
// Returns: DatabaseDiff

generateMigrationFromDiff(diff, targetModel, dialect)

Generates SQL migration from a database diff.

const migration = generateMigrationFromDiff(diff, newModel, Dialect.POSTGRESQL);
// Returns: { sql: string, accessControlDiff: AccessControlDiff }

Supported Dialects

  • Dialect.POSTGRESQL - Full PostgreSQL support with RLS
  • Dialect.SQLITE_MINIMAL - Basic SQLite support
  • Dialect.SQLITE_EXTENSIONS - SQLite with JSON extensions

Field Types

| Type | PostgreSQL | SQLite | SQLite Extended | | ---------- | -------------------------- | --------- | --------------- | | string | TEXT | TEXT | TEXT | | number | NUMERIC | REAL | REAL | | boolean | BOOLEAN | INTEGER | INTEGER | | object | JSONB | TEXT | JSON | | date | DATE | TEXT | TEXT | | datetime | TIMESTAMP WITH TIME ZONE | TEXT | TEXT | | uuid | UUID | TEXT | TEXT |

Examples

Creating Tables with Relationships

const blogModel: DataModel = {
  tables: [
    {
      name: "users",
      fields: [
        { name: "id", type: "uuid", nonNullable: true, primaryKey: true },
        { name: "email", type: "string", nonNullable: true },
        { name: "name", type: "string", nonNullable: false },
      ],
      accessControl: {
        read: true,
        create: true,
        update: { $eq: [{ $field: "id" }, { $var: "user_id" }] },
        delete: false,
      },
    },
    {
      name: "posts",
      fields: [
        { name: "id", type: "uuid", nonNullable: true, primaryKey: true },
        { name: "title", type: "string", nonNullable: true },
        { name: "content", type: "string", nonNullable: true },
        {
          name: "author_id",
          type: "uuid",
          nonNullable: true,
          foreignKey: {
            table: "users",
            field: "id",
            onDelete: "cascade",
          },
        },
        { name: "published_at", type: "datetime", nonNullable: false },
      ],
      accessControl: {
        read: { $eq: [{ $field: "published_at" }, null] }, // Only published posts
        create: { $eq: [{ $field: "author_id" }, { $var: "user_id" }] },
        update: { $eq: [{ $field: "author_id" }, { $var: "user_id" }] },
        delete: { $eq: [{ $field: "author_id" }, { $var: "user_id" }] },
      },
    },
  ],
};

const migration = generateInitialMigration(blogModel, Dialect.POSTGRESQL);

Incremental Migrations

// Initial model
const v1Model: DataModel = {
  tables: [
    {
      name: "users",
      fields: [
        { name: "id", type: "uuid", nonNullable: true, primaryKey: true },
        { name: "email", type: "string", nonNullable: true },
      ],
      accessControl: { read: true, create: true, update: true, delete: true },
    },
  ],
};

// Updated model with new field
const v2Model: DataModel = {
  tables: [
    {
      name: "users",
      fields: [
        { name: "id", type: "uuid", nonNullable: true, primaryKey: true },
        { name: "email", type: "string", nonNullable: true },
        {
          name: "created_at",
          type: "datetime",
          nonNullable: true,
          default: { $func: { now: [] } },
        },
      ],
      accessControl: { read: true, create: true, update: true, delete: true },
    },
  ],
};

// Generate incremental migration
const diff = generateDatabaseDiff(v1Model, v2Model);
const migration = generateMigrationFromDiff(diff, v2Model, Dialect.POSTGRESQL);

Development

Prerequisites

  • Bun (recommended) or Node.js 18+
  • Docker (for integration tests)

Setup

# Clone the repository
git clone https://github.com/iv-stpn/json-to-sql-migration.git
cd json-to-sql-migration

# Install dependencies
bun install

# Start PostgreSQL for testing
docker-compose up -d

# Run tests
bun test

# Build the library
bun run build

Scripts

  • bun run build - Build the library
  • bun run test - Run unit tests
  • bun run test:integration - Run integration tests with PostgreSQL
  • bun run test:all - Run all tests
  • bun run lint - Lint code with Biome
  • bun run format - Format code with Biome
  • bun run typecheck - Type check with TypeScript

Contributing

Contributions are welcome! Please read the contributing guidelines and follow the code of conduct.

Project Structure

├── src/
│   ├── index.ts          # Main library exports
│   └── schemas.ts        # Zod schemas for validation
├── test/
│   ├── migration.test.ts # Unit tests
│   ├── integration.test.ts # Integration tests
│   └── _init.sql        # Test database initialization
├── scripts/
│   └── check-postgres.ts # PostgreSQL health check
└── docker-compose.yml   # PostgreSQL test environment

License

This project is licensed under the MIT License - see the LICENSE file for details.

Dependencies

Related Projects


Made with ❤️ by Ivan Stepanian