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.
npm install kastor-orm pgPasso 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("...").
npx kastor import prisma --schema prisma/schema.prisma --out src/db/schema.ts --configO 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.
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:
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.prisma | schema do kastor |
|---|---|
model User | export 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 Role | t.enum(Role, 'Role') |
@relation(fields, references), posts Post[] | one() e many() em relations |
| muitos para muitos implícito | a 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:
npx kastor migrate baseline --from-prisma prisma/migrations
npx kastor migrate dev
npx kastor migrate statusTrê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
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 = dbO 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:
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 Client | kastor |
|---|---|
findMany, findUnique, findFirst, ...OrThrow | os mesmos nomes |
where, include, select, orderBy, take, skip, cursor | os mesmos formatos, mais findPage |
escritas aninhadas, upsert, createMany, increment | os mesmos formatos |
$transaction em array ou callback | db.$transaction, com isolationLevel |
$queryRaw, $executeRaw, Prisma.sql | $sql, $execute, sql |
erros P2002, P2003, P2025 | UNIQUE_VIOLATION, FOREIGN_KEY_VIOLATION, NOT_FOUND |
$use e $extends | opçã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()euuid()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 devpropuser mudança. - Datas no SQLite. O Prisma grava
DateTimecomo milissegundos e o kastor como texto ISO. O relatório traz oUPDATEque converte as linhas antigas. - Decimal. O kastor devolve
t.decimal()como string exata, não como objetoDecimal.
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.