├── .gitignore ├── LICENSE ├── README.md ├── example ├── .env ├── README.md ├── migrations │ ├── 001.do.sql │ ├── 001.undo.sql │ ├── 002.do.sql │ ├── 002.undo.sql │ ├── 003.do.create-profile-table.sql │ ├── 003.undo.create-profile-table.sql │ ├── 004.do.sql │ └── 004.undo.sql ├── package-lock.json ├── package.json ├── platformatic.db.json └── prisma │ └── schema.prisma ├── index.mjs ├── package-lock.json └── package.json /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2022 Kishan Gajera 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Platformatic Prisma 2 | 3 | [![npm version](https://badge.fury.io/js/platformatic-prisma.svg)](https://www.npmjs.com/package/platformatic-prisma) 4 | 5 | Remove the friction to create migrations for your [Platformatic DB](https://oss.platformatic.dev/docs/reference/db/introduction) by generating them using a [Prisma schema](https://www.prisma.io/docs/concepts/components/prisma-schema). 6 | 7 | - Use Prisma schema to manage your data models 8 | - Generate Platformatic DB compatible up and down [migrations](https://oss.platformatic.dev/docs/reference/db/migrations) when changes are made to your Prisma schema 9 | - Migrations will be [run](https://oss.platformatic.dev/docs/reference/db/migrations#how-to-run-migrations) by Platformatic DB 10 | 11 | View the [example project](./example) to see it in action. 12 | 13 | ## Getting Started 14 | 15 | Install `platformatic-prisma` and `prisma`: 16 | 17 | ``` 18 | npm install prisma platformatic-prisma -D 19 | ``` 20 | 21 | Create a `./prisma/schema.prisma` file with the following contents: 22 | 23 | ```prisma 24 | datasource db { 25 | // Any provider supported by Platformatic 26 | // https://oss.platformatic.dev/docs/reference/db/introduction#supported-databases 27 | provider = "postgresql" 28 | url = env("DATABASE_URL") 29 | } 30 | 31 | // Table used by Platformatic/Postgrator to manage migrations 32 | model Version { 33 | version BigInt @id 34 | name String? 35 | md5 String? 36 | run_at DateTime? @db.Timestamptz(6) 37 | 38 | @@map("versions") 39 | @@ignore 40 | } 41 | ``` 42 | 43 | Create an [`.env` file](https://www.prisma.io/docs/guides/development-environment/environment-variables#using-env-files) containing the database connection URL to connect to your database: 44 | 45 | ``` 46 | DATABASE_URL="postgresql://postgres:@localhost:5432/platformatic-prisma?schema=public" 47 | ``` 48 | 49 | ## Generating Migrations 50 | 51 | > **Note** 52 | > If you are just starting with a new Platformatic DB project, run `npx platformatic db migrate` before generating your first migration. This is needed to allow Platformatic DB to initialize the database with it's migrations table. 53 | 54 | When changes are made to your `schema.prisma` file, run `npx platformatic-prisma` to generate migrations. This will generate up and down migration files in the migrations directory that is specified in your Platformatic [configuration file](https://oss.platformatic.dev/docs/reference/db/configuration#configuration-file). 55 | 56 | To run migrations, refer to [Platformatic's documentation](https://oss.platformatic.dev/docs/reference/db/migrations). 57 | 58 | ### Options 59 | 60 | | Option | Required | Description | Default | 61 | | --------------- | -------- | ----------------------------------------------------------------- | ---------------------- | 62 | | `--description` | No | Label to include in migration filename. Must not contain periods. | | 63 | | `--no-down` | No | Prevents generation of the down migration file. | `false` | 64 | | `--no-up` | No | Prevents generation of the up migration file. | `false` | 65 | | `--schema` | No | Specifies the path to the `schema.prisma` file. | `prisma/schema.prisma` | 66 | -------------------------------------------------------------------------------- /example/.env: -------------------------------------------------------------------------------- 1 | # Environment variables declared in this file are automatically made available to Prisma. 2 | # See the documentation for more detail: https://pris.ly/d/prisma-schema#accessing-environment-variables-from-the-schema 3 | 4 | # Prisma supports the native connection string format for PostgreSQL, MySQL, SQLite, SQL Server, MongoDB and CockroachDB. 5 | # See the documentation for all the connection string options: https://pris.ly/d/connection-strings 6 | 7 | DATABASE_URL="postgresql://postgres:@localhost:5432/platformatic-prisma-example?schema=public" -------------------------------------------------------------------------------- /example/README.md: -------------------------------------------------------------------------------- 1 | # Platformatic Prisma Example Project 2 | 3 | ## Getting Started 4 | 5 | 1. Set the `DATABASE_URL` environment variable in [`.env`](/example/.env) with the connection string to connect to your database. The [`prisma/schema.prisma`](/example/prisma/schema.prisma#L5) file is configured to connect to a Postgres database. 6 | 1. Run `npm install` to install dependencies. 7 | 1. Run `npx platformatic db migrate` to let Platformatic DB initialize the database with it's migrations table. 8 | 9 | ## Starting the API Server 10 | 11 | Run `npm start` to start the Platformatic DB API server. 12 | 13 | ## Generating Migrations 14 | 15 | Run `npm run migrate` to generate a migration file. Run this when you make changes to the [`prisma/schema.prisma`](/example/prisma/schema.prisma) file. 16 | 17 | ## Running Migrations 18 | 19 | Migrations are automatically applied when starting the API server as configured in the [`platformatic.db.json`](/example/platformatic.db.json#L12) file. 20 | -------------------------------------------------------------------------------- /example/migrations/001.do.sql: -------------------------------------------------------------------------------- 1 | -- CreateTable 2 | CREATE TABLE "User" ( 3 | "id" SERIAL NOT NULL, 4 | "email" TEXT NOT NULL, 5 | "firstName" TEXT NOT NULL, 6 | "lastName" TEXT NOT NULL, 7 | 8 | CONSTRAINT "User_pkey" PRIMARY KEY ("id") 9 | ); 10 | 11 | -- CreateIndex 12 | CREATE UNIQUE INDEX "User_email_key" ON "User"("email"); 13 | -------------------------------------------------------------------------------- /example/migrations/001.undo.sql: -------------------------------------------------------------------------------- 1 | -- DropTable 2 | DROP TABLE "User"; 3 | -------------------------------------------------------------------------------- /example/migrations/002.do.sql: -------------------------------------------------------------------------------- 1 | -- AlterTable 2 | ALTER TABLE "User" DROP COLUMN "firstName", 3 | DROP COLUMN "lastName", 4 | ADD COLUMN "name" TEXT NOT NULL; 5 | -------------------------------------------------------------------------------- /example/migrations/002.undo.sql: -------------------------------------------------------------------------------- 1 | -- AlterTable 2 | ALTER TABLE "User" DROP COLUMN "name", 3 | ADD COLUMN "firstName" TEXT NOT NULL, 4 | ADD COLUMN "lastName" TEXT NOT NULL; 5 | -------------------------------------------------------------------------------- /example/migrations/003.do.create-profile-table.sql: -------------------------------------------------------------------------------- 1 | -- CreateTable 2 | CREATE TABLE "Profile" ( 3 | "id" SERIAL NOT NULL, 4 | "bio" TEXT, 5 | "userId" INTEGER NOT NULL, 6 | 7 | CONSTRAINT "Profile_pkey" PRIMARY KEY ("id") 8 | ); 9 | 10 | -- CreateIndex 11 | CREATE UNIQUE INDEX "Profile_userId_key" ON "Profile"("userId"); 12 | 13 | -- AddForeignKey 14 | ALTER TABLE "Profile" ADD CONSTRAINT "Profile_userId_fkey" FOREIGN KEY ("userId") REFERENCES "User"("id") ON DELETE RESTRICT ON UPDATE CASCADE; 15 | -------------------------------------------------------------------------------- /example/migrations/003.undo.create-profile-table.sql: -------------------------------------------------------------------------------- 1 | -- DropForeignKey 2 | ALTER TABLE "Profile" DROP CONSTRAINT "Profile_userId_fkey"; 3 | 4 | -- DropTable 5 | DROP TABLE "Profile"; 6 | -------------------------------------------------------------------------------- /example/migrations/004.do.sql: -------------------------------------------------------------------------------- 1 | -- CreateTable 2 | CREATE TABLE "Post" ( 3 | "id" SERIAL NOT NULL, 4 | "createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, 5 | "updatedAt" TIMESTAMP(3) NOT NULL, 6 | "title" VARCHAR(255) NOT NULL, 7 | "content" TEXT, 8 | "published" BOOLEAN NOT NULL DEFAULT false, 9 | "authorId" INTEGER NOT NULL, 10 | 11 | CONSTRAINT "Post_pkey" PRIMARY KEY ("id") 12 | ); 13 | 14 | -- AddForeignKey 15 | ALTER TABLE "Post" ADD CONSTRAINT "Post_authorId_fkey" FOREIGN KEY ("authorId") REFERENCES "User"("id") ON DELETE RESTRICT ON UPDATE CASCADE; 16 | -------------------------------------------------------------------------------- /example/migrations/004.undo.sql: -------------------------------------------------------------------------------- 1 | -- DropForeignKey 2 | ALTER TABLE "Post" DROP CONSTRAINT "Post_authorId_fkey"; 3 | 4 | -- DropTable 5 | DROP TABLE "Post"; 6 | -------------------------------------------------------------------------------- /example/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "platformatic-prisma-example", 3 | "version": "0.0.1", 4 | "private": true, 5 | "engines": { 6 | "node": ">=18.0.0" 7 | }, 8 | "scripts": { 9 | "start": "platformatic db start", 10 | "migrate": "platformatic-prisma" 11 | }, 12 | "dependencies": { 13 | "platformatic": "^1.19.0" 14 | }, 15 | "devDependencies": { 16 | "platformatic-prisma": "file:..", 17 | "prisma": "^4.16.1" 18 | } 19 | } -------------------------------------------------------------------------------- /example/platformatic.db.json: -------------------------------------------------------------------------------- 1 | { 2 | "server": { 3 | "hostname": "127.0.0.1", 4 | "port": "3042" 5 | }, 6 | "db": { 7 | "connectionString": "{DATABASE_URL}" 8 | }, 9 | "migrations": { 10 | "dir": "./migrations", 11 | "autoApply": true 12 | } 13 | } -------------------------------------------------------------------------------- /example/prisma/schema.prisma: -------------------------------------------------------------------------------- 1 | // This is your Prisma schema file, 2 | // learn more about it in the docs: https://pris.ly/d/prisma-schema 3 | 4 | datasource db { 5 | provider = "postgresql" 6 | url = env("DATABASE_URL") 7 | } 8 | 9 | model User { 10 | id Int @id @default(autoincrement()) 11 | email String @unique 12 | name String 13 | profile Profile? 14 | Post Post[] 15 | } 16 | 17 | model Profile { 18 | id Int @id @default(autoincrement()) 19 | bio String? 20 | user User @relation(fields: [userId], references: [id]) 21 | userId Int @unique 22 | } 23 | 24 | model Post { 25 | id Int @id @default(autoincrement()) 26 | createdAt DateTime @default(now()) 27 | updatedAt DateTime @updatedAt 28 | title String @db.VarChar(255) 29 | content String? 30 | published Boolean @default(false) 31 | author User @relation(fields: [authorId], references: [id]) 32 | authorId Int 33 | } 34 | 35 | // Table used by Platformatic/Postgrator to manage migrations 36 | model Version { 37 | version BigInt @id 38 | name String? 39 | md5 String? 40 | run_at DateTime? @db.Timestamptz(6) 41 | 42 | @@map("versions") 43 | @@ignore 44 | } 45 | -------------------------------------------------------------------------------- /index.mjs: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | import { loadConfig } from "@platformatic/config"; 4 | import { platformaticDB } from "@platformatic/db"; 5 | import { Migrator } from "@platformatic/db/lib/migrator.mjs"; 6 | import chalk from "chalk"; 7 | import { execaCommand } from "execa"; 8 | import fs from "fs"; 9 | import path from "path"; 10 | import parser from "yargs-parser"; 11 | 12 | try { 13 | const argv = parser(process.argv.slice(2), { 14 | boolean: ["down", "up"], 15 | default: { 16 | description: "", 17 | down: true, 18 | schema: path.resolve("prisma", "schema.prisma"), 19 | up: true, 20 | }, 21 | }); 22 | 23 | // Create migration scripts 24 | const [upMigration, downMigration] = await Promise.all([ 25 | execaCommand( 26 | `npx prisma migrate diff --from-schema-datasource ${argv.schema} --to-schema-datamodel ${argv.schema} --script` 27 | ), 28 | argv.down 29 | ? execaCommand( 30 | `npx prisma migrate diff --from-schema-datamodel ${argv.schema} --to-schema-datasource ${argv.schema} --script` 31 | ) 32 | : Promise.resolve(null), 33 | ]); 34 | 35 | if (upMigration.stdout.trim() === "-- This is an empty migration.") { 36 | console.log(chalk.green("No migrations are needed")); 37 | } else { 38 | const { configManager } = await loadConfig({}, "", platformaticDB); 39 | const config = configManager.current; 40 | 41 | if (config.migrations === undefined) { 42 | throw new Error( 43 | 'Missing "migrations" property in your Platformatic configuration file' 44 | ); 45 | } 46 | 47 | const migrator = new Migrator(config.migrations, config.db, { 48 | ...console, 49 | debug: () => undefined, 50 | trace: () => undefined, 51 | }); 52 | 53 | await migrator.checkMigrationsDirectoryExists(); 54 | 55 | const nextMigrationVersion = await migrator.getNextMigrationVersion(); 56 | const nextMigrationVersionStr = 57 | migrator.convertVersionToStr(nextMigrationVersion); 58 | 59 | const writeMigrationFile = async (content, action = "do") => { 60 | const migrationFilename = getMigrationFilename( 61 | nextMigrationVersionStr, 62 | action, 63 | argv.description 64 | ); 65 | const migrationPath = path.resolve( 66 | migrator.migrationDir, 67 | migrationFilename 68 | ); 69 | await fs.promises.writeFile(migrationPath, content); 70 | console.log(`\t${chalk.underline(migrationPath)}`); 71 | }; 72 | 73 | console.log(chalk.green("Generated migration files:")); 74 | 75 | // Save migration files 76 | await Promise.all([ 77 | migrator.close(), 78 | argv.up ? writeMigrationFile(upMigration.stdout) : Promise.resolve(null), 79 | argv.down 80 | ? writeMigrationFile(downMigration.stdout, "undo") 81 | : Promise.resolve(null), 82 | ]); 83 | } 84 | } catch (error) { 85 | console.log(chalk.red(error.message)); 86 | process.exitCode = 1; 87 | } 88 | 89 | /** 90 | * Returns a valid Postgrator migration filename 91 | * https://github.com/rickbergfalk/postgrator#usage 92 | */ 93 | function getMigrationFilename(version, action = "do", description = "") { 94 | return `${version}.${action}.${ 95 | description?.length ? `${description}.` : "" 96 | }sql`; 97 | } 98 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "platformatic-prisma", 3 | "version": "1.2.0", 4 | "description": "Generate migrations for your Platformatic DB using a Prisma schema", 5 | "bin": "index.mjs", 6 | "scripts": {}, 7 | "keywords": [ 8 | "platformatic", 9 | "prisma" 10 | ], 11 | "author": { 12 | "name": "Kishan Gajera", 13 | "url": "http://github.com/kgajera" 14 | }, 15 | "repository": { 16 | "type": "git", 17 | "url": "https://github.com/kgajera/platformatic-prisma.git" 18 | }, 19 | "bugs": { 20 | "url": "https://github.com/kgajera/platformatic-prisma/issues" 21 | }, 22 | "license": "MIT", 23 | "files": [ 24 | "index.mjs" 25 | ], 26 | "dependencies": { 27 | "@platformatic/config": "^1.19.0", 28 | "@platformatic/db": "^1.19.0", 29 | "chalk": "^5.3.0", 30 | "execa": "^8.0.1", 31 | "yargs-parser": "^21.1.1" 32 | }, 33 | "peerDependencies": { 34 | "prisma": ">=3.0.0" 35 | }, 36 | "devDependencies": { 37 | "prettier": "^3.2.4" 38 | } 39 | } --------------------------------------------------------------------------------