Uma biblioteca de persistência de dados para Node.js e TypeScript que implementa o padrão Active Record, permitindo uma interação fluida, assíncrona e intuitiva com o banco de dados MySQL.
O aMySQL foi projetado para alta performance e facilidade de uso, utilizando o pool de conexões do mysql2
, um Query Builder fluente e um sistema de migração automática de schema, que elimina a necessidade de escrever SQL para a maioria das operações de CRUD e gerenciamento de tabelas.
- Padrão Active Record: Manipule registros do banco de dados como objetos, chamando métodos como
.save()
e.delete()
diretamente nas instâncias do modelo. - Pool de Conexões de Alta Performance: Utiliza o pool de conexões do
mysql2
para um gerenciamento de conexões rápido e eficiente. - Operações Assíncronas: Todos os métodos de I/O com o banco (
save()
,delete()
,find()
) retornamPromises
, ideais para o ambienteasync/await
do Node.js. - Query Builder Fluente: Construa consultas
SELECT
de forma segura e legível encadeando métodos como.where()
,.orderBy()
e.first()
. - Gerenciamento Automático de Schema: As tabelas e colunas são criadas e atualizadas automaticamente com base nos seus modelos ao executar o
SchemaManager
. - Mapeamento Customizado com
ColumnAdapter
: Converta tipos complexos (comoDate
ou outros objetos) para tipos que o banco de dados entende (comoTIMESTAMP
oustring
). - Suporte a Relacionamentos: Mapeie relacionamentos entre objetos de forma declarativa usando o decorator
@Column
. - Decorators Poderosos: Configure tabelas, colunas, valores únicos, limites de
VARCHAR
e nulidade com decorators intuitivos do TypeScript.
Para utilizar o aMySQL, instale o pacote via npm e certifique-se de ter os pacotes mysql2
e reflect-metadata
instalados.
npm install @aceitadev/amysql mysql2 reflect-metadata
Importante: Você precisa importar o
reflect-metadata
uma única vez no ponto de entrada da sua aplicação para que os decorators funcionem corretamente.
// no topo do seu arquivo principal (ex: index.ts)
import "reflect-metadata";
O ciclo de vida básico de um objeto no banco de dados.
Configure as credenciais do banco, registre suas classes de modelo e execute o migrador de schema.
// src/index.ts
import "reflect-metadata";
import { init } from "./MySQL";
import { SchemaManager } from "./SchemaManager";
import { Player } from "./models/Player";
import { PlayerProfile } from "./models/PlayerProfile";
async function main() {
// 1. Configurar as credenciais
await init({
host: "localhost",
port: 3306,
database: "meu_banco",
user: "root",
password: "password"
});
// 2. Registrar modelos e migrar o schema
const schema = new SchemaManager([Player, PlayerProfile]);
await schema.migrate();
console.log("Banco de dados inicializado e migrado!");
// ... aqui começa a lógica da sua aplicação
}
main();
Crie sua classe estendendo ActiveRecord
. Todos os campos a serem persistidos devem ser anotados com @Column
ou @Id
.
// src/models/Player.ts
import { ActiveRecord } from "../ActiveRecord";
import { Table } from "../decorators/Table";
import { Id } from "../decorators/Id";
import { Column } from "../decorators/Column";
@Table("players")
export class Player extends ActiveRecord {
@Id()
id!: number;
@Column({ unique: true }) // Garante que cada nome de usuário será único
username!: string;
@Column({ name: "last_seen" }) // Coluna 'last_seen' com tipo TIMESTAMP
lastSeen!: Date;
constructor(username?: string, lastSeen?: Date) {
super();
if (username) this.username = username;
if (lastSeen) this.lastSeen = lastSeen;
}
}
Use async/await
para interagir com o banco.
async function runExample() {
// Salvar
const player = new Player("Steve", new Date());
await player.save();
console.log(`Jogador salvo com o ID: ${player.id}`);
// Buscar
const foundPlayer = await Player.find<Player>()
.where("username", "=", "Steve")
.first();
// Atualizar
if (foundPlayer) {
foundPlayer.username = "Steve 2.0";
await foundPlayer.save();
console.log("Jogador atualizado!");
}
// Deletar
if (foundPlayer) {
await foundPlayer.delete();
console.log("Jogador deletado!");
}
}
Para criar um relacionamento, anote o campo com @Column
. O aMySQL detectará que o tipo do campo (ex: Player
) é outra entidade com @Table
e criará uma coluna de chave estrangeira (ex: player_id
) automaticamente.
// src/models/PlayerProfile.ts
import { ActiveRecord } from "../ActiveRecord";
import { Table } from "../decorators/Table";
import { Id } from "../decorators/Id";
import { Column } from "../decorators/Column";
import { Player } from "./Player";
@Table("player_profiles")
export class PlayerProfile extends ActiveRecord {
@Id()
id!: number;
// É obrigatório usar @Column para que o campo seja persistido.
// aMySQL detecta o relacionamento e cria a coluna 'player_id'.
@Column()
player!: Player;
@Column()
bio!: string;
}
Como usar:
const player = await Player.find<Player>().where("username", "=", "Steve 2.0").first();
if (player) {
const profile = new PlayerProfile();
profile.player = player;
profile.bio = "Apenas um jogador comum.";
await profile.save();
// Ao buscar o perfil, o objeto 'player' virá preenchido (apenas com o ID)
const foundProfile = await PlayerProfile.find<PlayerProfile>()
.where("player", "=", player)
.first();
if (foundProfile) {
// Para carregar o objeto completo, você precisaria de uma busca adicional
console.log(`Bio do jogador com ID: ${foundProfile.player.id}`);
}
}
O ColumnAdapter
permite que você defina como serializar e desserializar um campo com um tipo que não é nativo do MySQL.
1. Crie seu ColumnAdapter
// src/adapters/DateToTimestampAdapter.ts
import { ColumnAdapter } from "../ColumnAdapter";
// Converte um objeto Date do JS para um número (timestamp UNIX) para o DB e vice-versa
export class DateToTimestampAdapter implements ColumnAdapter<Date, number> {
serialize(date: Date): number {
return date ? Math.floor(date.getTime() / 1000) : null;
}
deserialize(timestamp: number): Date {
return timestamp ? new Date(timestamp * 1000) : null;
}
}
2. Use o Adapter no seu Modelo
// Em uma classe de modelo...
import { DateToTimestampAdapter } from "../adapters/DateToTimestampAdapter";
// ...
@Column({ adapter: DateToTimestampAdapter })
creationDate!: Date; // Será armazenado como INT (timestamp) no banco
@Table("table_name")
: (Obrigatório) Define a classe como uma entidade e especifica o nome da tabela.@Id()
: (Obrigatório) Marca uma propriedade como a chave primária (INT
,PRIMARY KEY
,AUTO_INCREMENT
).@Nullable()
: (Opcional) Permite que a coluna correspondente sejaNULL
. Por padrão, as colunas sãoNOT NULL
.@Column(options?)
: (Obrigatório para campos persistíveis) Mapeia uma propriedade para uma coluna no banco de dados.name: "column_name"
: Define um nome customizado para a coluna. Se omitido, usa o nome da propriedade.unique: true
: Adiciona uma restriçãoUNIQUE
à coluna.limit: 255
: Define o tamanho para colunasVARCHAR
.adapter: MyAdapter
: Especifica umColumnAdapter
para serialização/desserialização customizada.