技能创建器
skill-creator
创建、改进和评估自定义技能,把专业流程转化为可重复使用的 AI 能力。
mikro-orm【数据与研究】MikroORM is a TypeScript ORM for Node.js built on the Data Mapper, Unit of Work, and Identity Map patterns, supporting PostgreSQL, MySQL, MariaDB, SQLite, and MongoDB. Use when a user asks to define decorator-based entities, set up automatic change tracking and batched flushes, write query-builder queries, generate or run migrations, seed a database,…
独立 GitHub 快照 · 自动分类 · 以原作者文档为准
MikroORM is a TypeScript ORM for Node.js based on the Data Mapper, Unit of Work, and Identity Map patterns (inspired by Doctrine and Hibernate). Entities are plain classes decorated with @Entity()/@Property(); the EntityManager tracks changes in memory and batches them into minimal SQL on em.flush(). It supports PostgreSQL, MySQL/MariaDB, SQLite, and MongoDB.
npm install @mikro-orm/core @mikro-orm/postgresql @mikro-orm/cli reflect-metadata
# or @mikro-orm/mysql, @mikro-orm/sqlite, @mikro-orm/mongodb
Each driver package re-exports MikroORM and defineConfig pre-wired to that driver — import from the driver package, not from @mikro-orm/core, so driver-specific features (like QueryBuilder on SQL drivers) are typed correctly. There is no type: "postgresql" config key in current MikroORM; the driver import itself selects the backend.
import { Entity, PrimaryKey, Property, ManyToOne, OneToMany, Collection,
Enum, Index, Unique, Embeddable, Embedded, Filter } from "@mikro-orm/core";
import { v4 } from "uuid";
@Embeddable()
class Address {
@Property()
street!: string;
@Property()
city!: string;
@Property()
country!: string;
}
enum UserRole { USER = "user", ADMIN = "admin" }
@Entity()
@Filter({ name: "active", cond: { deletedAt: null }, default: true })
export class User {
@PrimaryKey()
id: string = v4();
@Property()
name!: string;
@Index()
@Unique()
@Property()
email!: string;
@Enum(() => UserRole)
role: UserRole = UserRole.USER;
@Embedded(() => Address, { nullable: true })
address?: Address;
@OneToMany(() => Post, (post) => post.author)
posts = new Collection<Post>(this);
@Property()
createdAt: Date = new Date();
@Property({ onUpdate: () => new Date() })
updatedAt: Date = new Date();
@Property({ nullable: true })
deletedAt?: Date;
}
@Entity()
export class Post {
@PrimaryKey()
id: string = v4();
@Property()
title!: string;
@Property({ type: "text" })
body!: string;
@Property()
published: boolean = false;
@ManyToOne(() => User)
author!: User;
@Property()
createdAt: Date = new Date();
}
import { MikroORM, defineConfig } from "@mikro-orm/postgresql";
import { RequestContext } from "@mikro-orm/core";
const orm = await MikroORM.init(defineConfig({
entities: [User, Post],
dbName: "orders_app",
debug: process.env.NODE_ENV === "development",
}));
// Express middleware — one EntityManager fork per request, so concurrent
// requests don't share Identity Map state
app.use((req, res, next) => {
RequestContext.create(orm.em, next);
});
app.put("/users/:id", async (req, res) => {
const em = orm.em;
const user = await em.findOneOrFail(User, req.params.id);
user.name = req.body.name; // just modify the entity
user.email = req.body.email;
await em.flush(); // MikroORM diffs changes, generates UPDATE
res.json(user);
});
// Identity Map — same entity loaded twice in one context returns the same reference
const user1 = await orm.em.findOne(User, "7f5b2e10-...-aa31");
const user2 = await orm.em.findOne(User, "7f5b2e10-...-aa31");
console.log(user1 === user2); // true
// QueryBuilder (available because MikroORM was imported from the driver package)
const topAuthors = await orm.em.createQueryBuilder(User, "u")
.select(["u.*", "count(p.id) as post_count"])
.leftJoin("u.posts", "p")
.where({ role: UserRole.ADMIN })
.groupBy("u.id")
.orderBy({ post_count: "DESC" })
.limit(10)
.getResultList();
npx mikro-orm migration:create # generate from entity/schema diff
npx mikro-orm migration:create --initial
npx mikro-orm migration:up # apply pending migrations
npx mikro-orm migration:down # revert the last one
npx mikro-orm migration:pending # list what hasn't run
npx mikro-orm seeder:run --class=UserSeeder
Review the generated SQL before running migration:up against a shared or production database.
User request: "Our Express routes are sharing one EntityManager and getting stale data between requests — fix it."
Agent workflow:
RequestContext.create(orm.em, next) so every request gets its own forked EntityManager from orm.em instead of reusing one global instance.orm.em (MikroORM resolves it to the per-request fork via RequestContext) rather than importing a shared em constant.PUT /users/:id requests no longer see each other's unflushed changes.Output: Each HTTP request has an isolated Identity Map and Unit of Work; no more cross-request state leaks.
User request: "Add soft deletes to our User entity, and store the address as street/city/country without a separate table."
Agent workflow:
deletedAt: Date property to User and an @Filter({ name: "active", cond: { deletedAt: null }, default: true }) class decorator, so normal queries automatically exclude soft-deleted rows.@Embeddable() Address class with street/city/country properties, and an @Embedded(() => Address, { nullable: true }) field on User — this stores the fields inline on the user table, not in a separate table.npx mikro-orm migration:create) and review the SQL: it should add deleted_at plus the three address_* columns, no new table.Output: Soft-deleted users disappear from default queries without extra WHERE clauses in application code, and addresses live as typed value objects with no join.
em.flush() once per logical unit of work after modifying entities; MikroORM batches all pending changes into the minimum number of SQL statements instead of one query per set.em.find(User, {}, { populate: ["posts"] })) or you'll get an unitialized Collection/reference.RequestContext.create() per request (web frameworks) so each request gets its own forked EntityManager and Identity Map; sharing one global em across concurrent requests causes the stale/cross-request bugs this skill's first example fixes.@Filter for soft deletes or multi-tenancy instead of repeating a where clause everywhere; filters apply automatically unless explicitly disabled per query.@Embedded/@Embeddable for value objects (address, money) that should live inline on the owning table rather than a joined table.migration:fresh drops the database and is for local/dev use only.MikroORM/defineConfig from the driver package (@mikro-orm/postgresql, @mikro-orm/sqlite, etc.), not @mikro-orm/core, to get driver-specific typing such as QueryBuilder.