kastor0.2.0
Começar

GUIA 03Migrar do Prisma

Como migrar do Prisma para o kastor

O kastor mantém a API de consultas que o Prisma popularizou e elimina a etapa de geração. npx kastor import prisma transforma o schema.prisma em um schema TypeScript para o mesmo banco, npx kastor migrate baseline --from-prisma preserva o histórico de migrações e a maioria das chamadas só troca a primeira palavra.

O que muda e o que fica

Quase tudo em um projeto Prisma sobrevive à troca. O banco continua o mesmo, cada linha continua no lugar, o histórico de migrações é preservado e o estilo das consultas também: o kastor usa findMany, where, include, select, escritas aninhadas e $transaction com os mesmos formatos. O que muda é o que está em volta. O schema vira um módulo TypeScript em vez de um arquivo .prisma, os tipos são inferidos pelo compilador em vez de gerados, e as consultas passam pelo driver que você instala em vez do engine do Prisma.

Este guia segue a ordem que mantém a produção segura: converter o schema, adotar o banco sem alterá-lo, criar o cliente e só então migrar as chamadas, um módulo por vez, enquanto o Prisma continua atendendo o resto. A versão em inglês tem a tabela completa de equivalências e um exemplo maior de serviço antes e depois.

Passo 1: instalar o kastor e o driver

O kastor não traz driver embutido. Instale o que combina com o provider do seu datasource: pg para postgresql (inclusive Neon, Supabase e Vercel Postgres) e cockroachdb, mysql2 para mysql (inclusive PlanetScale), better-sqlite3 para sqlite, mssql para sqlserver e mongodb para mongodb.

Terminal
npm install kastor-orm pg

Passo 2: converter o schema.prisma com kastor import prisma

Rode o import na raiz do projeto. Ele encontra o schema pela flag --schema, pela chave prisma.schema do package.json, por prisma/schema.prisma ou por uma pasta prisma/schema, nessa ordem. --out define o arquivo de saída (padrão src/db/schema.ts) e --config também escreve o kastor.config.ts lendo a mesma variável de ambiente do url = env("...").

Terminal
npx kastor import prisma --schema prisma/schema.prisma --out src/db/schema.ts --config

O comando imprime um relatório. Itens em Not mapped exactly pedem uma decisão sua; itens em Good to know explicam diferenças de comportamento, como ids gerados por cuid() ou uuid(), que viram funções JavaScript no topo do arquivo gerado. O @updatedAt vira .updatedAt(), que o kastor preenche em cada create e update.

Saída
kastor / import prisma

  Prisma schema  prisma/schema.prisma
  Provider       postgresql (kastor-orm/postgres)

ok    Wrote src/db/schema.ts: 4 models, 1 join table, 1 enum, 6 relation fields.
ok    Wrote kastor.config.ts for postgres, reading DATABASE_URL.
ok    Every model, field and relation mapped exactly.

O arquivo gerado declara cada model como uma tabela com o nome que o Prisma usou no banco, e as relações ficam na função relations. Um trecho, para um schema com usuários e posts:

src/db/schema.ts
import { table, t, index, one, many } from 'kastor-orm';

export const Role = ['USER', 'ADMIN'] as const;

export const user = table('User', {
  id: t.id(),
  email: t.text().unique(),
  name: t.text().nullable(),
  role: t.enum(Role, 'Role').default('USER'),
  createdAt: t.datetime().nativeType('timestamp(3)').defaultNow(),
  updatedAt: t.datetime().nativeType('timestamp(3)').updatedAt(),
});

export const post = table('Post', {
  id: t.id(),
  title: t.string(200),
  published: t.boolean().default(false),
  views: t.int().default(0),
  authorId: t.int().references(() => user.id, { onDelete: 'cascade', onUpdate: 'cascade' }),
}, {
  indexes: (c) => [index(c.authorId)],
});

export const schema = { user, post };

export const relations = ({ user, post }: typeof schema) => ({
  user: { posts: many(post, { fields: ['id'], references: ['authorId'] }) },
  post: { author: one(user, { fields: ['authorId'], references: ['id'] }) },
});
schema.prismaschema do kastor
model Userexport const user = table('User', ...)
Int @id @default(autoincrement())t.id()
String?, @unique, @default(now()).nullable(), .unique(), .defaultNow()
@map, @@map, @db.VarChar(200).map(), o nome da tabela, t.string(200)
enum Rolet.enum(Role, 'Role')
@relation(fields, references), posts Post[]one() e many() em relations
muitos para muitos implícitoa tabela _AToB e many(..., { through })

Passo 3: preservar dados e histórico

O Prisma Migrate guarda arquivos SQL em prisma/migrations e registra cada um na tabela _prisma_migrations. O kastor guarda os dele em kastor/migrations e na tabela _kastor_migrations. O baseline conecta os dois mundos sem mexer nos dados nem nos registros do Prisma:

Terminal
npx kastor migrate baseline --from-prisma prisma/migrations
npx kastor migrate dev
npx kastor migrate status

Três coisas acontecem. Cada migração do Prisma marcada como concluída é registrada como histórico; as que nunca rodaram ou falharam aparecem no relatório em vez de serem registradas. Uma migração de baseline é escrita com o SQL que cria o schema atual e marcada como aplicada sem ser executada. E o kastor compara o schema importado com o banco: quando o migrate dev responde Already in sync, você tem a prova de que o import ficou exato.

Faça commit de kastor/migrations. Em cada outro ambiente (banco de um colega, staging, produção) rode o mesmo comando uma vez: ele encontra o baseline commitado e só o marca como aplicado. Em um banco novo e vazio, npx kastor migrate deploy executa o baseline e cria tudo.

Nota

A partir daqui o kastor é dono do schema. Pare de rodar prisma migrate dev e prisma db push, que alterariam tabelas por fora do histórico do kastor. Mudanças novas são edições em src/db/schema.ts seguidas de npx kastor migrate dev --name o_que_mudou.

Passo 4: um único cliente no lugar do PrismaClient

src/db/index.ts
import pg from 'pg'
import { kastor } from 'kastor-orm'
import { postgres } from 'kastor-orm/postgres'
import { schema, relations } from './schema'

const create = () =>
  kastor({
    adapter: postgres(new pg.Pool({ connectionString: process.env.DATABASE_URL, max: 5 })),
    schema,
    relations,
  })

const g = globalThis as unknown as { db?: ReturnType<typeof create> }
export const db = g.db ?? create()
if (process.env.NODE_ENV !== 'production') g.db = db

O truque do globalThis é o mesmo que você usava com o Prisma: mantém um único pool entre recarregamentos em desenvolvimento. Enquanto os dois clientes convivem, cada um segura as próprias conexões, então reduza o connection_limit do Prisma e o max do kastor.

Passo 5: migrar as chamadas do Prisma Client

Como o import mantém os nomes dos models, na maioria das chamadas só a primeira palavra muda:

src/posts/service.ts
import { isKastorError } from 'kastor-orm'
import { db } from '../db'

// Antes: prisma.post.findMany({ where, include, orderBy, take })
export const feed = (authorId: number) =>
  db.post.findMany({
    where: { published: true, author: { role: 'ADMIN' }, authorId },
    include: { author: { select: { name: true } } },
    orderBy: { id: 'desc' },
    take: 20,
  })

// Antes: prisma.user.create com escrita aninhada
export const signUp = (email: string) =>
  db.user.create({ data: { email, posts: { create: [{ title: 'Olá' }] } }, include: { posts: true } })

// Antes: prisma.$queryRaw
export const topAuthors = () =>
  db.$sql<{ authorId: number; n: number }>`select "authorId", count(*)::int as n from "Post" group by 1 order by 2 desc limit ${5}`

// Antes: Prisma.PrismaClientKnownRequestError com código P2002
export async function register(email: string) {
  try {
    return await db.user.create({ data: { email } })
  } catch (e) {
    if (isKastorError(e) && e.code === 'UNIQUE_VIOLATION') return null
    throw e
  }
}
Prisma Clientkastor
findMany, findUnique, findFirst, ...OrThrowos mesmos nomes
where, include, select, orderBy, take, skip, cursoros mesmos formatos, mais findPage
escritas aninhadas, upsert, createMany, incrementos mesmos formatos
$transaction em array ou callbackdb.$transaction, com isolationLevel
$queryRaw, $executeRaw, Prisma.sql$sql, $execute, sql
erros P2002, P2003, P2025UNIQUE_VIOLATION, FOREIGN_KEY_VIOLATION, NOT_FOUND
$use e $extendsopção log, softDelete e funções comuns

Passo 6: ajustar o CI e remover o Prisma

Quando nenhum módulo importar mais @prisma/client, desinstale prisma e @prisma/client, tire o prisma generate do postinstall e do build, e troque o prisma migrate deploy do pipeline por npx kastor migrate deploy. Não existe etapa de geração: os tipos vêm do schema pelo compilador, então o tsc do CI já confere cada consulta.

Cuidados

  • updatedAt fora do kastor. O .updatedAt() é preenchido pelo kastor em cada escrita, como o Prisma Client fazia. Escritas por SQL puro ou por outros programas não atualizam a coluna; use um trigger se isso importar.
  • Ids gerados no cliente. cuid() e uuid() são gerados em JavaScript, não pelo banco. Linhas inseridas por outras ferramentas continuam precisando de id.
  • Tabelas antigas de muitos para muitos. Bancos criados com Prisma 5 ou anterior usam um índice único em vez de chave primária; siga a dica do relatório se o migrate dev propuser mudança.
  • Datas no SQLite. O Prisma grava DateTime como milissegundos e o kastor como texto ISO. O relatório traz o UPDATE que converte as linhas antigas.
  • Decimal. O kastor devolve t.decimal() como string exata, não como objeto Decimal.

Depois, conecte o cliente ao seu framework. A página de frameworks mostra a configuração de cada um.

Perguntas frequentes

Q1O kastor lê o schema.prisma?

Sim. npx kastor import prisma lê o schema.prisma (ou uma pasta com vários arquivos .prisma) e gera um schema TypeScript com os mesmos nomes de tabelas, colunas, índices e constraints que o Prisma Migrate criou, inclusive as tabelas implícitas de muitos para muitos como _PostToTag. Com --config ele também escreve o kastor.config.ts.

Q2Vou perder o histórico de migrações do Prisma?

Não. npx kastor migrate baseline --from-prisma prisma/migrations registra cada migração que a tabela _prisma_migrations marca como concluída, mais uma migração de baseline. Os arquivos do Prisma e a tabela _prisma_migrations não são alterados.

Q3Preciso renomear prisma.user?

Não. O import nomeia cada tabela pelo model em camelCase, então prisma.user.findMany vira db.user.findMany. Se preferir nomes no plural, renomeie os exports depois.

Q4O que substitui middlewares e client extensions?

O kastor não tem API de extensões. Log e tempo de consulta usam a opção log, soft delete é uma opção da tabela, campos calculados são funções sobre o tipo da linha e métodos customizados são funções em um módulo.

Q5Prisma e kastor podem rodar juntos?

Sim, no mesmo banco, enquanto você migra os módulos. Cada um tem seu pool de conexões, então reduza os dois, e deixe só uma ferramenta criando migrações: depois do baseline, o kastor.

Suggestions

Kas looked under every log. Nothing matches that yet.

Try a method name, a database or a framework.