npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@lakutata/orm

v3.2.0

Published

The ORM of the lakutata framework, on TypeORM: the Database component and provider, the entity decorators and the generation of the migrations.

Readme

@lakutata/orm

English | 中文

lakutata 框架的 ORM,基于 TypeORM 1 构建。它提供 Database 组件(整个应用共享的连接)、Database 提供者(有自己的连接)、实体的装饰器、TypeORM 的 API(仓储、查询操作符、查询构造器、迁移),以及应用的迁移命令(MIGRATION_GENERATE、MIGRATION_RUN、MIGRATION_REVERT、MIGRATION_SHOW)。

lakutata 包重新导出了它:lakutata/com/database(组件)、lakutata/provider/database(提供者)、lakutata/decorator/orm(实体的装饰器)和 lakutata/orm(TypeORM 的 API)。

安装

npm install lakutata pg        # 框架,以及 PostgreSQL 驱动
npm install @lakutata/orm      # 或者只装 ORM,配合 @lakutata/core 使用

TypeORM 1.1.2(MIT)的源码包含在这个包中(见 THIRD_PARTY_NOTICES.md),所以不会安装 typeorm 包;基于 typeorm 包的库(它的 NestJS 模块、命名策略库等)也不会使用这份副本。数据库驱动不是这个包的依赖:请按连接选项的 type 安装对应的驱动。

| 数据库 | type | 需要安装 | |---|---|---| | PostgreSQL、CockroachDB | postgres、cockroachdb | pg | | MySQL、MariaDB | mysql、mariadb | mysql2(3.15.3 或更高) | | SQLite | better-sqlite3 | better-sqlite3(12) | | WebAssembly 版的 SQLite | sqljs | sql.js | | Microsoft SQL Server | mssql | mssql(12) | | Oracle | oracle | oracledb | | MongoDB | mongodb | mongodb(7) | | 嵌入进程内的 MongoDB | mongo-embedded | @lakutata/mongo-embedded(安装时编译) | | SAP HANA | sap | @sap/hana-client | | Google Cloud Spanner | spanner | @google-cloud/spanner(8) |

sqljs 驱动把整个数据库放在内存中。设置了 location 选项时,它在连接时从这个文件加载数据库;设置了 autoSave: true 时,每次变化后写回文件;调用它的实体管理器(SqljsEntityManager)的 saveDatabase() 时也会写回;exportDatabase() 以 Uint8Array 返回数据库。

mongo-embedded 是 lakutata 添加的:它让 TypeORM 对 MongoDB 的支持(带 @ObjectIdColumn 的实体、查询选项中的 MongoDB 操作符、MongoRepository)运行在进程内的数据库上,也就是 @lakutata/mongo-embedded 包。它用原生插件里的 SQLite 保存文档,行为与以单成员副本集运行的 MongoDB 8.0 相同(事务、变更流、全文索引和地理空间索引、排序规则)。它的插件在安装时编译,需要 Python 3 和支持 C++17 的编译器。database 选项是它的目录(默认 ':memory:');其他选项有 sync、syncInterval、cacheSize、mmapSize、oplogSize、writeConcern 和 transactionLifetimeLimitSeconds。transaction() 在数据库的事务中执行它的函数。和 mongodb 类型一样,不支持 TypeORM 的关系和查询构造器,迁移需要手写(通过 queryRunner.manager,它是一个 MongoEntityManager)。import {ObjectId} from 'lakutata/orm' 给出实体 id 的类,也是 @ObjectIdColumn() 属性的类型。详见指南嵌入式 MongoDB。

存在 Redis 中的查询结果缓存(cache: {type: 'redis'} 或 'ioredis')需要安装 redis 或 ioredis 包。

实体依赖装饰器及其元数据:应用所用的 TypeScript 选项("extends": "lakutata/tsconfig.json")开启了 experimentalDecorators 和 emitDecoratorMetadata。

用法

在应用中使用数据库

buildDatabaseOptions 用 TypeORM 的连接选项(DataSourceOptions,原样交给 TypeORM)注册一个 Database 组件。组件在创建时连接数据库,列在 bootstrap 里时,它在应用启动过程中创建;应用停止时,它会断开连接。连接失败会中止应用的启动。

import {Application, Component} from 'lakutata'
import {Inject} from 'lakutata/decorator/di'
import {buildDatabaseOptions, Database} from 'lakutata/com/database'
import {Column, CreateDateColumn, Entity, PrimaryGeneratedColumn} from 'lakutata/decorator/orm'

@Entity('users')
class User {
    @PrimaryGeneratedColumn()
    public id: number

    @Column({unique: true})
    public email: string

    @CreateDateColumn({type: 'timestamptz'})
    public createdAt: Date
}

class Users extends Component {
    //注册为 db 的组件
    @Inject('db')
    protected readonly db: Database

    public async register(email: string): Promise<User> {
        return await this.db.getRepository(User).save({email: email})
    }

    public async find(email: string): Promise<User | null> {
        return await this.db.getRepository(User).findOneBy({email: email})
    }
}

Application
    .run(() => ({
        id: 'com.example.users',
        name: 'Users',
        components: {
            db: buildDatabaseOptions({
                type: 'postgres',
                host: process.env.DB_HOST ?? 'localhost',
                port: 5432,
                username: 'postgres',
                password: process.env.DB_PASSWORD,
                database: 'users',
                entities: [User],
                //只用于开发:表结构跟随实体变化;生产环境中由迁移变更表结构
                synchronize: true
            }),
            users: {class: Users}
        },
        bootstrap: ['db']
    }))
    .onLaunched(async (app: Application): Promise<void> => {
        const users: Users = await app.getObject('users')
        await users.register('[email protected]')
    })

常用的连接选项(各驱动的选项见 TypeORM 的文档):

| 选项 | 作用 | |---|---| | type | 驱动:'postgres'、'mysql'、'better-sqlite3'…… | | url 或 host、port、username、password、database | 服务器和数据库 | | schema | 表所在的 PostgreSQL schema(默认取服务器的 search_path,通常是 public) | | entities | 实体类、EntitySchema,或者编译后文件的 glob('build/entities/*.js') | | synchronize | 每次连接时按实体更新表结构(默认 false):只用于开发 | | migrations、migrationsTableName、migrationsRun、migrationsTransactionMode | 迁移、记录迁移的表(默认 migrations)、是否在每次连接时执行迁移(默认 false)、迁移的事务(默认 'all') | | logging、logger | TypeORM 记录什么(true、['error', 'schema']……),以及记录到哪里 | | poolSize、extra | 连接池的大小;传给驱动的选项(pg 用 extra: {max: 20}) | | subscribers、namingStrategy、cache | 实体订阅者、表和列的命名方式、查询结果的缓存 |

框架改变了一个选项的默认值:invalidWhereValuesBehavior 默认为 {null: 'ignore', undefined: 'ignore'},即 TypeORM 0.3 的行为(TypeORM 1 默认抛出错误)。where 条件中值为 null 或 undefined 的属性会从条件中去掉。你给出的每个键会替换它的默认值(invalidWhereValuesBehavior: {undefined: 'throw'} 遇到 undefined 值时抛出错误)。在默认设置下,findOneBy({id: undefined}) 会查到第一行,所以用请求中的值查询之前,要先检查它们。

如果用 TypeORM 0.3 创建的数据库中,表名或列名含有非 ASCII 字符,TypeORM 1 给它们的索引和约束起的名字会不同,生成的迁移就会重命名它们。lakutata/orm 中的 NamingStrategyV03(namingStrategy: new NamingStrategyV03())可以保持 TypeORM 0.3 的命名。

实体

lakutata/decorator/orm 中的装饰器就是 TypeORM 的装饰器:@Entity、@ViewEntity,列(@Column、@PrimaryColumn、@PrimaryGeneratedColumn、@CreateDateColumn、@UpdateDateColumn、@DeleteDateColumn、@VersionColumn、@VirtualColumn、@ViewColumn、@ObjectIdColumn、@Generated),关系(@OneToOne、@OneToMany、@ManyToOne、@ManyToMany、@JoinColumn、@JoinTable、@RelationId),约束(@Index、@Unique、@Check、@Exclusion),树(@Tree、@TreeParent、@TreeChildren、@TreeLevelColumn),继承(@TableInheritance、@ChildEntity),实体监听器(@BeforeInsert、@AfterLoad……),以及 @EventSubscriber。

import {
    BeforeInsert, Column, CreateDateColumn, DeleteDateColumn, Entity, Index, JoinColumn, ManyToOne, OneToMany,
    PrimaryGeneratedColumn, UpdateDateColumn, VersionColumn
} from 'lakutata/decorator/orm'

@Entity('orders')
export class Order {
    @PrimaryGeneratedColumn('uuid')
    public id: string

    @Index()
    @Column()
    public customer: string

    //pg 把 numeric 列读成字符串:由 transformer 转换成数字
    @Column('numeric', {precision: 12, scale: 2, transformer: {to: (value: number): number => value, from: (value: string): number => Number(value)}})
    public total: number

    @Column('jsonb', {nullable: true})
    public metadata: Record<string, unknown> | null

    //随订单一起保存
    @OneToMany(() => OrderLine, (line: OrderLine) => line.order, {cascade: true})
    public lines: OrderLine[]

    @CreateDateColumn({type: 'timestamptz'})
    public createdAt: Date

    @UpdateDateColumn({type: 'timestamptz'})
    public updatedAt: Date

    //由 softDelete() 设置:除非设置了 withDeleted,查询会跳过这个订单
    @DeleteDateColumn({type: 'timestamptz'})
    public deletedAt: Date | null

    //每次保存加一
    @VersionColumn()
    public version: number

    @BeforeInsert()
    protected normalize(): void {
        this.customer = this.customer.trim()
    }
}

@Entity('order_lines')
export class OrderLine {
    @PrimaryGeneratedColumn()
    public id: number

    @Column()
    public product: string

    @Column('integer')
    public quantity: number

    @ManyToOne(() => Order, (order: Order) => order.lines, {onDelete: 'CASCADE'})
    @JoinColumn({name: 'order_id'})
    public order: Order
}

实体也可以继承 BaseEntity(来自 lakutata/orm),以 Active Record 的方式操作(User.findOneBy()、user.save());这些操作使用最后一个注册了该实体的连接。

查询

lakutata/orm 导出 TypeORM 的 API:仓储、查询操作符(In、ILike、Between、MoreThan、IsNull、Not、Raw、ArrayContains、JsonContains……)、查询构造器、实体管理器和错误。查询选项使用对象:relations: {author: true}、select: {id: true, title: true}(TypeORM 1 去掉了名称数组的写法)。

import {Component} from 'lakutata'
import {Inject} from 'lakutata/decorator/di'
import {Database} from 'lakutata/com/database'
import {Column, Entity, PrimaryGeneratedColumn} from 'lakutata/decorator/orm'
import {Between, ILike, In, type Repository} from 'lakutata/orm'

@Entity('orders')
class Order {
    @PrimaryGeneratedColumn()
    public id: number

    @Column()
    public customer: string

    @Column()
    public status: string

    @Column('integer')
    public total: number

    @Column({type: 'timestamptz'})
    public createdAt: Date
}

class Orders extends Component {
    @Inject('db')
    protected readonly db: Database

    protected get orders(): Repository<Order> {
        return this.db.getRepository(Order)
    }

    //分页查询某段时间内的订单,同时返回总数
    public async search(customer: string, from: Date, to: Date, page: number): Promise<[Order[], number]> {
        return await this.orders.findAndCount({
            where: {customer: ILike(`%${customer}%`), status: In(['paid', 'shipped']), createdAt: Between(from, to)},
            order: {createdAt: 'DESC'},
            skip: (page - 1) * 20,
            take: 20
        })
    }

    //用查询构造器做聚合
    public async totals(): Promise<{ customer: string, total: string }[]> {
        return await this.db.createQueryBuilder(Order, 'order')
            .select('order.customer', 'customer')
            .addSelect('SUM(order.total)', 'total')
            .groupBy('order.customer')
            .getRawMany()
    }

    //原生 SQL:PostgreSQL 的占位符是 $1、$2……
    public async count(status: string): Promise<number> {
        const rows: { count: string }[] = await this.db.query('SELECT COUNT(*) AS count FROM orders WHERE status = $1', [status])
        return Number(rows[0].count)
    }
}

事务

transaction() 在事务中执行一个函数:函数成功(resolve)时提交,失败(reject)时回滚,并重新抛出错误。事务中的操作要通过传给函数的实体管理器执行;通过 db.manager 或 db.getRepository() 执行的操作不在事务中。用查询运行器(query runner)可以手动控制事务。

import {Component} from 'lakutata'
import {Inject} from 'lakutata/decorator/di'
import {Database} from 'lakutata/com/database'
import {Column, Entity, PrimaryGeneratedColumn} from 'lakutata/decorator/orm'
import {type EntityManager, type QueryRunner} from 'lakutata/orm'

@Entity('accounts')
class Account {
    @PrimaryGeneratedColumn()
    public id: number

    @Column('integer', {default: 0})
    public balance: number
}

class Bank extends Component {
    @Inject('db')
    protected readonly db: Database

    //两个更新要么都完成,要么都不完成
    public async transfer(from: number, to: number, amount: number): Promise<void> {
        await this.db.transaction('SERIALIZABLE', async (manager: EntityManager): Promise<void> => {
            const source: Account = await manager.findOneByOrFail(Account, {id: from})
            if (source.balance < amount) throw new Error('Insufficient balance')
            await manager.decrement(Account, {id: from}, 'balance', amount)
            await manager.increment(Account, {id: to}, 'balance', amount)
        })
    }

    //同样的事务,手动控制
    public async deposit(id: number, amount: number): Promise<void> {
        const runner: QueryRunner = this.db.createQueryRunner()
        await runner.connect()
        await runner.startTransaction()
        try {
            await runner.manager.increment(Account, {id: id}, 'balance', amount)
            await runner.commitTransaction()
        } catch (error) {
            await runner.rollbackTransaction()
            throw error
        } finally {
            //连接回到连接池
            await runner.release()
        }
    }
}

Database 提供者

lakutata/provider/database 中的提供者与组件的 API 相同,但它的生命周期是 @Transient:每次解析(@Inject、getObject())都会创建一个新实例,打开自己的连接池;实例随创建它的对象或容器一起销毁时,连接池也会关闭。它适合短时的任务(一个作业、一个脚本、一个租户的数据库),而应用处理请求时共用组件。它的 entities 选项会加到连接选项的实体中。

import {Application} from 'lakutata'
import {buildDatabaseOptions, Database} from 'lakutata/provider/database'
import {Column, Entity, PrimaryGeneratedColumn} from 'lakutata/decorator/orm'

@Entity('reports')
class Report {
    @PrimaryGeneratedColumn()
    public id: number

    @Column('text')
    public content: string
}

Application
    .run(() => ({
        id: 'com.example.reports',
        name: 'Reports',
        providers: {
            reports: {
                ...buildDatabaseOptions({type: 'postgres', url: process.env.REPORTS_URL}),
                entities: [Report]
            }
        }
    }))
    .onLaunched(async (app: Application): Promise<void> => {
        //在这里打开连接,随应用一起关闭
        const database: Database = await app.getObject('reports')
        console.log(await database.getRepository(Report).count())
    })

多个数据库

每个数据库都是一个有自己名字的组件,按这个名字注入。迁移命令作用于所有数据库,或者 MIGRATION_DATABASE 指定的那一个。

components: {
    db: buildDatabaseOptions({type: 'postgres', url: process.env.DATABASE_URL, entities: [User, Order]}),
    analytics: buildDatabaseOptions({type: 'postgres', url: process.env.ANALYTICS_URL, entities: [Event]})
}

迁移

迁移是实现了 MigrationInterface 的类:up() 变更表结构,down() 撤销这个变更。连接选项 migrations 列出迁移(迁移类,或者编译后文件的 glob),migrations 表记录已经执行的迁移。

import {type MigrationInterface, type QueryRunner} from 'lakutata/orm'

export class Migrations1760000000000 implements MigrationInterface {
    public name: string = 'Migrations1760000000000'

    public async up(queryRunner: QueryRunner): Promise<void> {
        await queryRunner.query('ALTER TABLE "users" ADD "name" character varying')
    }

    public async down(queryRunner: QueryRunner): Promise<void> {
        await queryRunner.query('ALTER TABLE "users" DROP COLUMN "name"')
    }
}

迁移可以在数据库连接时执行(设置 migrationsRun: true),可以通过 Database 组件或提供者的 runMigrations() 和 undoLastMigration() 执行,也可以通过应用的命令执行。

这些命令是由应用自己读取的环境变量;lakutata migration(来自 @lakutata/cli)会带着它们启动应用。带着其中某个变量启动时,应用读取自己的选项,然后不执行 bootstrap 和 onLaunched 回调,而是对选项中的每个 Database 组件和提供者执行命令,最后退出。每个数据库连接时,synchronize、migrationsRun 和 dropSchema 都被强制设为 false。变量名不区分大小写,开关变量的值为 1、true 或 yes 时生效。

| 变量 | 命令 | |---|---| | MIGRATION_GENERATE=<directory> | 比较实体与数据库的表结构,把差异的 SQL 写成一个迁移:<directory>/<timestamp>-<database name>.ts(目录相对于工作目录,需要时会创建),类名为 <Directory><timestamp>(src/migrations 得到 Migrations1760000000000);项目依赖 @lakutata/orm 时,迁移从它导入类型,否则从 lakutata/orm 导入 | | MIGRATION_RUN=1 | 执行待执行的迁移,执行时逐个输出 | | MIGRATION_REVERT=1 | 撤销最后执行的迁移(应用有多个数据库时需要 MIGRATION_DATABASE) | | MIGRATION_SHOW=1 | 输出每个迁移,已执行的标为 [X],待执行的标为 [ ],并输出待执行的数量 | | MIGRATION_DATABASE=<name> | 命令针对的数据库:它的组件或提供者的名字(默认所有数据库) | | MIGRATION_TRANSACTION=all\|each\|none | MIGRATION_RUN 和 MIGRATION_REVERT 使用的事务(默认取数据库的 migrationsTransactionMode 选项,没有时为 all) | | MIGRATION_FAKE=1 | 只把迁移记录为已执行(或已撤销),不真正执行 |

同时设置了多个变量时,只执行一个命令,优先级依次是 MIGRATION_GENERATE、MIGRATION_RUN、MIGRATION_REVERT 和 MIGRATION_SHOW。

# 用编译后的应用生成迁移(数据库必须能连上,并且已经执行了已有的迁移)
MIGRATION_GENERATE=src/migrations node build/App.js
# 编译这个迁移,然后
MIGRATION_SHOW=1 node build/App.js
MIGRATION_RUN=1 node build/App.js
MIGRATION_REVERT=1 MIGRATION_DATABASE=db node build/App.js

生成的文件遵循 TypeORM 的代码风格(双引号、分号):请格式化它,或者让 linter 跳过迁移文件。命令完成后,进程以退出码 0 退出;失败时以退出码 1 退出,失败的情况包括:无效的 MIGRATION_TRANSACTION、未知的 MIGRATION_DATABASE、没有数据库(MIGRATION_GENERATE 除外,这时以 0 退出)、有多个数据库时使用 MIGRATION_REVERT 却没有指定 MIGRATION_DATABASE、数据库连接不上,或者迁移失败(之后的数据库会被跳过)。MIGRATION_GENERATE 在所有数据库中都没有发现变化时,以 0 退出。

数据库的实体就是它连接选项中的实体;对于 Database 提供者,还要加上它 entities 选项中的实体。

API

| 导出 | 子路径 | 说明 | |---|---|---| | Database、buildDatabaseOptions | lakutata/com/database | 数据库组件(共享的连接)及其注册函数 | | Database、buildDatabaseOptions | lakutata/provider/database | 数据库提供者(每次解析一个连接)及其注册函数 | | MigrationRunOptions | lakutata/com/database、lakutata/provider/database | runMigrations() 和 undoLastMigration() 的选项 | | Entity、Column、PrimaryGeneratedColumn、OneToMany、ManyToOne、Index、BeforeInsert…… | lakutata/decorator/orm | TypeORM 的装饰器 | | Repository、EntityManager、QueryRunner、SelectQueryBuilder、DataSource、BaseEntity…… | lakutata/orm | TypeORM 的类和类型 | | In、ILike、Between、MoreThan、IsNull、Not、Raw…… | lakutata/orm | TypeORM 的查询操作符 | | MigrationInterface、Migration、MigrationExecutor | lakutata/orm | 迁移 | | DataSourceOptions、PostgresDataSourceOptions、MysqlDataSourceOptions…… | lakutata/orm | 各驱动的连接选项(它们在 TypeORM 0.3 中的名字 PostgresConnectionOptions 等已弃用) | | QueryFailedError、EntityNotFoundError、TypeORMError…… | lakutata/orm | TypeORM 的错误 |

lakutata 的声明在类型声明中附有说明和示例;TypeORM 的声明见 TypeORM 的文档。

错误

这个包抛出驱动的错误和 TypeORM 的错误,后者由 lakutata/orm 导出:

| 错误 | 什么时候抛出 | |---|---| | 驱动的错误(pg 的 DatabaseError、ECONNREFUSED……) | Database 组件或提供者无法连接:它的创建会失败(reject),组件列在 bootstrap 中时,应用启动失败 | | QueryFailedError | 数据库拒绝了查询(违反约束、语法错误);它的 driverError 是驱动的错误(PostgreSQL 违反唯一约束时 code 为 23505) | | EntityNotFoundError | findOneOrFail()、findOneByOrFail() 没有找到实体 | | EntityMetadataNotFoundError | 实体不在连接的 entities 中 | | OptimisticLockVersionMismatchError | 使用 lock: {mode: 'optimistic', version} 查询时,读到了另一个版本 | | CannotExecuteNotConnectedError | 在已关闭的连接上执行 synchronize() 或迁移 | | TypeORMError | TypeORM 错误的基类,本身也会被抛出(在其他数据库上调用了 MongoDB 的方法、找不到关系……) |

另见

  • 网站上的指南:数据库、实体、查询与事务、迁移。
  • lakutata 包中的指南(doc/zh):Database.md、DatabaseDrivers.md、MongoEmbedded.md、Entities.md、Migrations.md。
  • TypeORM 的文档:typeorm.io(实体、关系、查询选项、查询构造器、迁移)。
  • @lakutata/core:应用、组件和提供者、依赖注入。