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

sql-petra-orm

v1.0.7

Published

SQLite数据库ORM框架

Readme

SQLPetra

SQLPetra 是一个基于 TypeScript 装饰器和 SQLite 的轻量级本地 ORM。底层使用 better-sqlite3,对外通过 Db 提供数据库初始化、自动建表、查询、新增、修改、删除、事务、备份和原生 SQL 执行能力。

适用范围

适合:

  • Electron 主进程中的本地 SQLite 数据访问。
  • 小型桌面工具、离线应用、本地配置库、缓存库。
  • 不想引入完整 ORM,但希望少写重复 CRUD SQL 的项目。
  • 希望源码简单、可控、方便按业务修改的项目。
  • 如果集成与electron程序的话在主进程是可以直接使用的,但是渲染进程要使用的话则需要查看扩展方法中集成Electron程序中有说明

暂不适合:

  • 强依赖复杂关系映射的系统。
  • 需要完整迁移系统、跨数据库兼容、连接池的大型服务端项目。
  • 需要复杂 Join、级联、实体生命周期钩子的完整 ORM 场景。

注意:

electron渲染进程目前不可使用该依赖,不然运行就会报错,后期会处理针对于渲染进程使用的方法

Db 方法总览

| 方法 | 数据库中做什么 | 是什么意思 / 干嘛用 | 返回 | | --------------------------------- | ------------------------------------------------------------ | ------------------------------------------------------------ | ----------------------- | | Db.InitDb(config) | 打开或创建 SQLite 数据库文件,并设置 WAL 模式 | 程序启动时先调用,用来建立后续所有 ORM 操作需要的数据库连接 | void | | Db.CreateTableByEntity(Entity) | 根据实体字段执行 CREATE TABLE;表已存在时执行 ALTER TABLE ADD COLUMN 追加新列 | 用实体类同步数据库表结构,适合应用启动时自动建表或补字段 | void | | Db.Close() | 关闭当前 SQLite 连接 | 程序退出、释放数据库文件占用时调用 | void | | Db.Backup(path) | 调用 SQLite 备份能力,把当前数据库复制到指定文件 | 用来做本地数据备份、导出数据库文件 | void | | Db.Transaction(callback) | 开启数据库事务;成功提交,异常回滚 | 把多次新增、修改、删除放到一个整体里,保证要么全部成功,要么全部撤销 | TransActionReturnType | | Db.Queryable(Entity) | 准备生成 SELECT ... FROM 表 查询语句 | 查询数据入口,后面继续接 WhereSelectToListFirst 等方法 | Queryable<T> | | Db.Insertable(Entity, data) | 准备生成 INSERT INTO 表(...) VALUES(...) 插入语句 | 往表中新增一行或多行数据 | Insertable<T> | | Db.Updateable(Entity) | 准备生成 UPDATE 表 SET ... WHERE ... 更新语句 | 修改表中已有数据,必须配合 SetColumnFiled / SetColumns 和条件使用 | Updateable<T> | | Db.Deleteable(Entity) | 准备生成 DELETE FROM 表 WHERE ... 删除语句 | 删除表中已有数据,默认必须加条件,避免误删全表 | Deleteable<T> | | Db.RunSQLQueryable(sql, args?) | 直接执行自定义 SELECT SQL | 链式查询不够用时,手写 SQL 查询数据 | any | | Db.RunSQLInsertable(sql, args?) | 直接执行自定义 INSERT SQL | 链式新增不够用时,手写 SQL 插入数据 | number | | Db.RunSQLUpdateable(sql, args?) | 直接执行自定义 UPDATE SQL | 链式更新不够用时,手写 SQL 修改数据 | number | | Db.RunSQLDeleteable(sql, args?) | 直接执行自定义 DELETE SQL | 链式删除不够用时,手写 SQL 删除数据 | number |

快速开始

安装

// 使用npm
npm i sql-petra-orm
// 或者使用pnpm
pnpm add sql-petra-orm

注意安装后如果是electron程序的话是启动不起来的,因为项目依赖better-sqlite3 并且``better-sqlite3是原生 Node.js模块。如果出现better_sqlite3.node 找不到、NODE_MODULE_VERSION 不匹配、The module was compiled against a different Node.js version等错误,通常执行pnpm rebuild better-sqlite3npm rebuild better-sqlite3` 即可。

Electron 项目需要按当前 Electron 版本重新编译原生模块:

npm i -D @electron/rebuild
npx electron-rebuild -f -w better-sqlite3

卸载

npm uninstall sql-petra-orm

更新

npm update sql-petra-orm

初始化数据库

要使用数据库前提要初始化

Db.InitDb({
  dbPath: "F:\\项目\\SQLPetra\\",
  dbName: "test.db",
  isWriteLog: true
});

参数说明:

| 参数 | 类型 | 必填 | 说明 | | ------------ | --------- | ---- | ---------------------------------------- | | dbPath | string | 是 | 数据库文件所在目录。目录不存在会自动创建 | | dbName | string | 是 | 数据库文件名。不带 .db 时会自动补上 | | isWriteLog | boolean | 是 | 是否打印 SQL、参数和事务日志 |

初始化后会打开 SQLite 数据库,并执行:

PRAGMA journal_mode = WAL;

解决报错

better-sqlite3 是原生 Node.js 模块。如果出现 better_sqlite3.node 找不到、NODE_MODULE_VERSION 不匹配、The module was compiled against a different Node.js version 等错误,通常执行 pnpm rebuild better-sqlite3npm rebuild better-sqlite3 即可。

Electron 项目需要按当前 Electron 版本重新编译原生模块:

npm i -D @electron/rebuild
npx electron-rebuild -f -w better-sqlite3

建议在 Electron 项目的 package.json 里加:

{
  "scripts": {
    "postinstall": "electron-rebuild -f -w better-sqlite3"
  }
}

如果使用 electron-builder,也可以使用:

{
  "scripts": {
    "postinstall": "electron-builder install-app-deps"
  }
}

pnpm 10 如果提示构建脚本被忽略,可以执行:

pnpm approve-builds
pnpm rebuild better-sqlite3

如果报错如下:

D:\Node\环境\npm.cmd run dev

> [email protected] dev
> electron-vite dev

vite v5.4.21 building SSR bundle for development...
✓ 5 modules transformed.
out/main/index.js  4.36 kB
✓ built in 138ms

build the electron main process successfully

-----

vite v5.4.21 building SSR bundle for development...
✓ 1 modules transformed.
out/preload/index.js  1.31 kB
✓ built in 20ms

build the electron preload files successfully

-----

dev server running for the electron renderer process at:

  ➜  Local:   http://localhost:5173/
  ➜  Network: use --host to expose

start electron app...


[鍒濆鍖栨暟鎹簱]: 鏁版嵁搴撳湴鍧€锛欶:\椤圭洰\SQLPetera-Test  鏁版嵁搴撳悕锛歵est.db  鏄惁鎵撳嵃鏃ュ織锛氭槸
(node:20460) UnhandledPromiseRejectionWarning: Error: The module '\\?\F:\茅隆鹿莽聸庐\SQLPetera-Test\node_modules\better-sqlite3\build\Release\better_sqlite3.node'
was compiled against a different Node.js version using
NODE_MODULE_VERSION 127. This version of Node.js requires
NODE_MODULE_VERSION 119. Please try re-compiling or re-installing
the module (for instance, using `npm rebuild` or `npm install`).
    at process.func [as dlopen] (node:electron/js2c/node_init:2:2214)
    at Module._extensions..node (node:internal/modules/cjs/loader:1356:18)
    at Object.func [as .node] (node:electron/js2c/node_init:2:2214)
    at Module.load (node:internal/modules/cjs/loader:1126:32)
    at Module._load (node:internal/modules/cjs/loader:967:12)
    at c._load (node:electron/js2c/node_init:2:13672)
    at Module.require (node:internal/modules/cjs/loader:1150:19)
    at require (node:internal/modules/cjs/helpers:119:18)
    at bindings (F:\椤圭洰\SQLPetera-Test\node_modules\bindings\bindings.js:112:48)
    at new Database (F:\椤圭洰\SQLPetera-Test\node_modules\better-sqlite3\lib\database.js:48:64)
(Use `electron --trace-warnings ...` to show where the warning was created)
(node:20460) UnhandledPromiseRejectionWarning: Unhandled promise rejection. This error originated either by throwing inside of an async function without a catch block, or by rejecting a promise which was not handled with .catch(). To terminate the node process on unhandled promise rejection, use the CLI flag `--unhandled-rejections=strict` (see https://nodejs.org/api/cli.html#cli_unhandled_rejections_mode). (rejection id: 1)

请执行如下命令:

npm i -D @electron/rebuild
npx electron-rebuild -f -w better-sqlite3

配置 TypeScript

tsconfig.json 需要开启装饰器:

如果是electron项目的话最好需要在tsconfig.node.json中也添加配置

{
  "compilerOptions": {
    "experimentalDecorators": true,
    "emitDecoratorMetadata": true,
    "module": "NodeNext",
    "moduleResolution": "NodeNext"
  }
}

装饰器

标记表名@table

说明:

  • @table("test_table") 表示实体对应数据库表 test_table
  • 如果没有使用 @table,默认使用实体类名作为表名。

使用

import { table } from "../Petra";

@table("test_table")
export class TestTable {

}

标记字段属性@column

参数

| 参数 | 类型 | 默认值 | 说明 | | -------------- | -------------------------------------------------- | ----------- | ----------------- | | fieldName | string | 当前属性名 | 数据库字段名 | | fieldType | "text" | "integer" | "real" | "blob" | "numeric" | 自动推断 | SQLite 字段类型 | | isPrimaryKey | boolean | false | 是否主键 | | isNull | boolean | false | 是否允许为 null | | defaultValue | string | number | null | undefined | undefined | 建表时的默认值 | | IsIgnore | boolean | false | 是否忽略该属性 |

使用示例

import { column, table } from "../Petra";

@table("test_table")
export class TestTable {
  @column({ fieldName: "table_id", isPrimaryKey: true, isNull: true })
  id?: number;

  @column({ isNull: true })
  name?: string;

  @column({ isNull: false })
  note?: string;

  @column({ IsIgnore: true })
  age?: number;
}

字段类型推断说明

| TypeScript 类型 | SQLite 类型 | | --------------- | ----------- | | number | integer | | string | text | | boolean | integer | | Date | text | | Buffer | blob | | 其它或无法识别 | text |

条件方法

该条件方法可用于查询修改删除 链式方法中

方法总览

| 方法名 | 参数 | 备注 | | ------------ | ------------------------------------ | -------------------------------------------------------- | | Where | (key, value, operator?) | where条件 | | WhereIF | (haveValue, key, value, operator?) | where条件,如果表达式为false则不触发查询条件 | | BetweenAnd | (key, begin, end) | 查询某个范围内的数据,比如 ID 范围、价格范围、时间范围 | | Or | () | 把下一条条件和上一条条件用 OR 连接 | | In | (key, values, type?) | 在给定的这个范围内查询 | | Like | (key, value, type?) | 模糊匹配 |

类型总览

// 用于Where和WhereIF方法中的operator中
type WhereOperator = "=" | ">" | "<" | ">=" | "<=";
// 用于In方法中的type
type InType = "IN" | "NOT IN"
// 一般用于Like中的type方法
type LikeType = "left"|"right"|"all"

Where条件

字段描述

  • key:该字段是给定类型中的字段
  • value:条件语句的值,需要查询的条件值
  • operator:类型条件,类型请看WhereOperator类型

数据库中做什么

生成 WHERE 字段 = @参数WHERE 字段 > @参数 等条件;valuenull 时生成 IS NULL

是什么意思 / 干嘛用

最常用的筛选条件,用来指定某个字段必须等于、大于、小于某个值

WhereIF条件

字段描述

  • haveValue:是否生效该条件

  • key:该字段是给定类型中的字段

  • value:条件语句的值,需要查询的条件值

  • operator:类型条件,类型请看WhereOperator类型

数据库中做什么

haveValuetrue 时才生成一段 WHERE 条件

是什么意思 / 干嘛用

用来写动态查询,比如搜索框有值才按关键字过滤,没有值就不过滤

BetweenAnd条件

字段描述

  • key:该字段是给定类型中的字段
  • begin:其实条件值
  • end:结束条件值

数据库中做什么

生成 字段 BETWEEN @begin AND @end

是什么意思 / 干嘛用

查询某个范围内的数据,比如 ID 范围、价格范围、时间范围

Or条件

数据库中做什么

把下一条条件和上一条条件用 OR 连接

是什么意思 / 干嘛用

表示“或者”,比如名字是张三或者李四都查出来

In条件

字段描述

  • key:该字段是给定类型中的字段
  • values:条件语句的值,需要查询的条件值,类型是数组
  • type:是否在当前范围做筛选还是在非范围内做筛选,类型请看InType

数据库中做什么

生成 字段 IN (...)字段 NOT IN (...)

是什么意思 / 干嘛用

批量匹配多个值,比如查询 ID 在 [1,2,3] 中的数据,或排除这些 ID

Like条件

字段描述

  • key:该字段是给定类型中的字段
  • value:条件语句的值,需要查询的条件值
  • type:是左匹配还是右匹配还是全匹配,类型请看LikeType

数据库中做什么

生成 字段 LIKE @参数

是什么意思 / 干嘛用

模糊搜索文本,比如按姓名、标题、备注中的关键字查数据

查询数据

查询一条数据-Where

const item = Db.Queryable<TestTable>(TestTable).Where("name", "张三").First<TestTable>();

查询所有数据-ToList

const list = Db.Queryable<TestTable>(TestTable).ToList<TestTable>();

分页查询数据-ToPageList

字段描述

  • pageIndex:当前查询第几页
  • pageSize:每一页的数据量大小

使用示例

const pageListDatas = Db.Queryable<TestTable>(TestTable).ToPageList<TestTable>(1,20);

返回类型说明

| 字段 | 类型 | 说明 | | ------------ | -------- | ------------------------- | | pageIndex | number | 当前页数 | | pageSize | number | 每页大小 | | pages | number | 总页数 | | total | number | 总条数 | | dataSource | T | 数据,T这里是给定的类型 |

查询指定字段-SelectField

const names = Db.Queryable<TestTable>(TestTable).SelectField("name").ToList<string>();

查询字段并设置别名-Select

type TestTableView = {
  tableId?: number;
  userName?: string;
};

const list = Db.Queryable<TestTable>(TestTable)
  .Select<TestTableView>(x => ({
    tableId: x.id,
    userName: x.name
  }))
  .ToList<TestTableView>();

分组-GroupBy和Having

const list = Db.Queryable<TestTable>(TestTable)
  .GroupBy(["name"])
  .Having("SUM", "id", 1, ">")
  .ToList<{ name?: string }>();

GroupBy(keys) 会生成 GROUP BY,并把查询字段重置为分组字段。Having(type, key, value, operator?) 当前支持 "SUM""AVG",会生成类似 SUM(table_id) > @having_table_id_0 的条件。

聚合方法

聚合方法说明:

| 方法 | 数据库中做什么 | 是什么意思 / 干嘛用 | 返回 | | ---------- | ------------------------------------ | ------------------------------------------------------------ | --------- | | Count() | 执行 SELECT COUNT(1) | 统计符合条件的数据有多少条 | number | | Sum(key) | 执行 SELECT SUM(字段) | 对某个数字字段求总和,比如统计金额合计、数量合计 | number | | Avg(key) | 执行 SELECT AVG(字段) | 对某个数字字段求平均值,比如平均分、平均价格 | number | | Max(key) | 执行 SELECT MAX(字段) | 查询某个字段的最大值,比如最大 ID、最高价格 | number | | Min(key) | 执行 SELECT MIN(字段) | 查询某个字段的最小值,比如最小 ID、最低价格 | number | | Any() | 执行 COUNT(1) 并判断数量是否大于 0 | 判断数据库里是否存在符合条件的数据,常用于重复校验、存在性判断 | boolean |

注意:如果聚合查询因为 GroupBy 返回多行,当前实现会返回 0true,不返回分组后的聚合列表。

const count = Db.Queryable<TestTable>(TestTable).Count();
const sum = Db.Queryable<TestTable>(TestTable).Sum("id");
const avg = Db.Queryable<TestTable>(TestTable).Avg("id");
const max = Db.Queryable<TestTable>(TestTable).Max("id");
const min = Db.Queryable<TestTable>(TestTable).Min("id");
const any = Db.Queryable<TestTable>(TestTable).Where("name", "张三").Any();

新增数据

方法说明

  • ExecuteCommand() 返回影响行数,类型为 number
  • 批量新增会复用同一个 prepared statement。
  • 批量新增如果不在外层事务中,会自动使用内部事务。
  • 批量新增如果在 Db.Transaction() 内,会复用外层事务。
  • 空数组会直接返回 0
  • 新增时会按实体字段统一生成 INSERT,未传入字段会以 undefined 作为参数值。

新增方法说明:

| 方法 | 数据库中做什么 | 是什么意思 / 干嘛用 | 返回 | | ----------------------------- | -------------------------------------- | ------------------------------------------------------------ | --------------- | | Db.Insertable(Entity, data) | 准备 INSERT INTO 表(...) VALUES(...) | 告诉 ORM 要往哪个表插入什么数据,data 可以是一条对象或对象数组 | Insertable<T> | | ExecuteCommand() | 真正执行 INSERT SQL | 前面的 Insertable 只是构造新增操作,调用它才会真正写入数据库 | number |

新增单条数据

const changes = Db.Insertable<TestTable>(TestTable, {
  id: 1,
  name: "张三",
  note: "第一条数据"
}).ExecuteCommand();

批量新增数据

const changes = Db.Insertable<TestTable>(TestTable, [
  { id: 2, name: "张三", note: "备注" },
  { id: 3, name: "李四", note: "备注" }
]).ExecuteCommand();

修改数据

方法说明

| 方法 | 数据库中做什么 | 是什么意思 / 干嘛用 | 返回 | | ----------------------------------------------- | ---------------------------- | ---------------------------------------------------------- | --------------- | | Db.Updateable(Entity) | 准备 UPDATE 表 SET ... | 告诉 ORM 接下来要修改哪个实体对应的数据库表 | Updateable<T> | | SetColumns(columns) | 生成多个 SET 字段 = @参数 | 一次设置多个要修改的列,适合表单保存、批量字段更新 | this | | SetColumnFiled(field, value) | 生成单个 SET 字段 = @参数 | 只修改某一列,field 可传实体属性名,会映射到数据库字段名 | this | | Where / WhereIF / BetweenAnd / Or / In / Like | 生成 WHERE 条件 | 限制更新哪些行,避免把整张表都改掉 | this | | AllowAll() | 允许没有 WHEREUPDATE | 明确告诉 ORM:这次就是要更新整张表 | this | | ExecuteCommand() | 真正执行 UPDATE SQL | 前面的链式方法只是构造更新语句,调用它才会提交修改 | number |

注意:

  • 不加条件执行更新会抛出 请给定条件更新
  • 没有设置任何更新字段会抛出 请给定要更新的列
  • SetColumnFiled 方法名目前保留了历史拼写。

更新多个字段

const changes = Db.Updateable<TestTable>(TestTable)
  .SetColumns({
    id: 1,
    name: "新名称",
    note: "新备注"
  })
  .Where("id", 1)
  .ExecuteCommand();

更新单个字段

const changes = Db.Updateable<TestTable>(TestTable)
  .SetColumnFiled("name", "新名称")
  .Where("id", 1)
  .ExecuteCommand();

全表更新

默认不允许无条件更新。如果确实要更新全表,必须显式调用 AllowAll()

const changes = Db.Updateable<TestTable>(TestTable)
  .SetColumnFiled("note", "全部修改")
  .AllowAll()
  .ExecuteCommand();

删除数据

方法说明

| 方法 | 数据库中做什么 | 是什么意思 / 干嘛用 | 返回 | | ----------------------------------------------- | ---------------------------- | ------------------------------------------------------ | --------------- | | Db.Deleteable(Entity) | 准备 DELETE FROM 表 | 告诉 ORM 接下来要删除哪个实体对应表里的数据 | Deleteable<T> | | Where / WhereIF / BetweenAnd / Or / In / Like | 生成 WHERE 条件 | 限制删除哪些行,常见用法是按主键、状态或 ID 列表删除 | this | | AllowAll() | 允许没有 WHEREDELETE | 明确告诉 ORM:这次就是要清空整张表数据 | this | | ExecuteCommand() | 真正执行 DELETE SQL | 前面的链式方法只是构造删除语句,调用它才会真正删除数据 | number |

注意:不加条件执行删除会抛出 请给定条件删除

按条件删除

const changes = Db.Deleteable<TestTable>(TestTable)
  .Where("id", 1)
  .ExecuteCommand();

批量删除

const changes = Db.Deleteable<TestTable>(TestTable)
  .In("id", [1, 2, 3], "IN")
  .ExecuteCommand();

全表删除

默认不允许无条件删除。如果确实要删除全表,必须显式调用 AllowAll()

const changes = Db.Deleteable<TestTable>(TestTable)
  .AllowAll()
  .ExecuteCommand();

使用事务

使用Transaction

说明

  • 回调执行成功时自动提交,并返回 { isSuccess: true, message: "事务操作成功" }
  • 回调中抛出异常时自动回滚,并返回 { isSuccess: false, message: "事务操作失败:..." }
  • 当前实现会捕获异常并返回失败结果,不会继续向外抛出原始异常。

使用示例

const result = Db.Transaction(() => {
  Db.Insertable<TestTable>(TestTable, {
    id: 10,
    name: "事务新增",
    note: "备注"
  }).ExecuteCommand();

  Db.Updateable<TestTable>(TestTable)
    .SetColumnFiled("note", "事务修改")
    .Where("id", 10)
    .ExecuteCommand();
});

console.log(result.isSuccess, result.message);

Db.Transaction(callback) 返回:

type TransActionReturnType = {
  isSuccess: boolean;
  message: string;
};

执行原生 SQL

当链式 API 不满足需求时,可以直接执行自定义 SQL。

查询

const rows = Db.RunSQLQueryable(
  "SELECT table_id, name FROM test_table WHERE table_id = @id",
  { id: 1 }
);

新增

const changes = Db.RunSQLInsertable(
  "INSERT INTO test_table(table_id, name, note) VALUES (@id, @name, @note)",
  { id: 20, name: "王五", note: "原生 SQL 新增" }
);

更新

const changes = Db.RunSQLUpdateable(
  "UPDATE test_table SET name = @name WHERE table_id = @id",
  { id: 20, name: "赵六" }
);

删除

const changes = Db.RunSQLDeleteable(
  "DELETE FROM test_table WHERE table_id = @id",
  { id: 20 }
);

建议仍然使用命名参数,不要把用户输入直接拼接进 SQL 字符串。

扩展方法

关闭数据库

Db.Close();

创建或更新表结构

行为说明:

  • 表不存在时,根据实体上的 @column 信息创建表。
  • 表已存在时,会检查实体中新增的字段,并执行 ALTER TABLE ADD COLUMN
  • 当前不会自动删除数据库中多出来的字段。
  • SQLite 不支持给已有表直接追加主键字段,遇到这种情况会抛出错误。
Db.CreateTableByEntity<TestTable>(TestTable);

Electron渲染进程使用

因为封装的当前依赖是不可以在渲染进程使用的,所以我做了一个建议版本的操作数据库链式方法,可以跟着我如下操作你的electron项目

当前演示项目是electron-vite脚手架创建的项目

首先我们需要实现一下我们的新增、修改、重新、删除方法

我们在我们的src/renderer/src/DbHelp目录中新增一个dbHelp方法(没有当前DbHelp目录的话手工新增)

import {
  getTableColumns,
  getTableName,
  QueryableBase,
  SQLType,
  PageListType,
  InsertableBase,
  TableFiledType,
  DeleteableBase,
  UpdateableBase
} from 'sql-petra'

/**
 * 查询使用的链式方法
 * @param tableEntity 表实体
 * @constructor
 */
class QueryableHelpFun<T> extends QueryableBase<T> {
  /**
   * 查询一条数据
   * @constructor
   */
  async First<FirstT>(): Promise<FirstT | T | null> {
    const sql: SQLType = this._GetFirstSQL()
    const d: FirstT[] = (await window.api.sql(sql.SQL, '查询', sql.data)) as FirstT[]
    let SQLResult: FirstT | null = null
    if (d.length > 0) SQLResult = d[0] as FirstT
    // 如果有值,并且返回值就一个的话则返回list
    if (this.data.select.length == 1 && SQLResult != null)
      return SQLResult[this.data.select[0].field] as FirstT
    return SQLResult
  }

  /**
   * 根据条件查询全部
   * @constructor
   */
  async ToList<ToListT>(): Promise<ToListT[] | T[]> {
    const sql: SQLType = this._GetToListSQL()
    const d: ToListT[] = (await window.api.sql(sql.SQL, '查询', sql.data)) as ToListT[]
    let SQLResult: ToListT[] = [] as ToListT[]
    if (d.length > 0) SQLResult = d as ToListT[]
    if (this.data.select.length == 1 && SQLResult != null) {
      return SQLResult.map((x: ToListT) => x[this.data.select[0].field]) as ToListT[]
    }
    return SQLResult
  }

  /**
   * 分页查询
   * @param pageIndex 当前页
   * @param pageSize 每页数据量大小
   * @constructor
   */
  async ToPageList<PageListT>(
    pageIndex: number,
    pageSize: number
  ): Promise<PageListType<PageListT>> {
    // 需要返回的数据
    const returnData: PageListType<PageListT> = {
      pageIndex: pageIndex,
      pageSize: pageSize,
      pages: 0,
      total: 0,
      dataSource: []
    }
    // 获取总条数的SQL语句
    const countSQL: SQLType = this._GetCountSQL()
    // 数量
    const dataCount: object[] = (await window.api.sql(
      countSQL.SQL,
      '查询',
      countSQL.data
    )) as object[]
    // 总条数
    returnData.total = dataCount[0]['data_count']
    // 计算总页数
    if (returnData.total % pageSize == 0) {
      returnData.pages = returnData.total / pageSize
    } else {
      returnData.pages = Math.floor(returnData.total / pageSize) + 1
    }
    // 查询具体的数据
    const pageSelectSQL: SQLType = this._GetToPageListSQL(pageIndex, pageSize)
    const d: PageListT[] = (await window.api.sql(
      pageSelectSQL.SQL,
      '查询',
      pageSelectSQL.data
    )) as PageListT[]
    returnData.dataSource = d as PageListT[]
    return returnData
  }

  /**
   * 获取有多少条数据
   */
  async Count(): Promise<number> {
    const sql: SQLType = this._GetCountSQL()
    const dataCount: object[] = (await window.api.sql(sql.SQL, '查询', sql.data)) as object[]
    if (dataCount.length > 1) return 0
    return dataCount[0]['data_count']
  }

  /**
   * 汇总值
   * @param key 需要汇总的字段
   * @constructor
   */
  async Sum<k extends keyof T>(key: k): Promise<number> {
    const sql: SQLType = this._GetSumSQL(key)
    const dataCount: object[] = (await window.api.sql(sql.SQL, '查询', sql.data)) as object[]
    if (dataCount.length > 1) return 0
    return dataCount[0]['data_sum']
  }

  /**
   * 平均值
   * @param key 需要平均的字段
   * @constructor
   */
  async Avg<k extends keyof T>(key: k): Promise<number> {
    const sql: SQLType = this._GetAvgSQL(key)
    const dataCount: object[] = (await window.api.sql(sql.SQL, '查询', sql.data)) as object[]
    if (dataCount.length > 1) return 0
    return dataCount[0]['data_avg']
  }

  /**
   * 最大值
   * @param key 需要求取最大值的字段
   * @constructor
   */
  async Max<k extends keyof T>(key: k): Promise<number> {
    const sql = this._GetMaxSQL(key)
    const dataCount: object[] = (await window.api.sql(sql.SQL, '查询', sql.data)) as object[]
    if (dataCount.length > 1) return 0
    return dataCount[0]['data_max']
  }

  /**
   * 最小值
   * @param key 需要求取最小值的字段
   * @constructor
   */
  async Min<k extends keyof T>(key: k): Promise<number> {
    const sql = this._GetMinSQL(key)
    const dataCount: object[] = (await window.api.sql(sql.SQL, '查询', sql.data)) as object[]
    if (dataCount.length > 1) return 0
    return dataCount[0]['data_min']
  }

  /**
   * 是否存在数据
   * @constructor
   */
  async Any(): Promise<boolean> {
    const sql = this._GetAnySQL()
    const dataCount: object[] = (await window.api.sql(sql.SQL, '查询', sql.data)) as object[]
    if (dataCount.length > 1) return true
    return dataCount[0]['data_count'] > 0
  }
}

/**
 * 新增使用的链式方法
 * @param tableEntity 表实体
 * @param insertData 需要添加的数据
 * @constructor
 */
class InsertableFun<T> extends InsertableBase<T> {
  constructor(tableName: string, fields: TableFiledType[], insertDatas: T[]) {
    super(tableName, fields, insertDatas)
  }

  /**
   * 提交更新
   * @constructor
   */
  async ExecuteCommand(): Promise<number> {
    if (this.insertDatas.length == 0) return 0
    const sql: SQLType = this._GetInsertSQL()
    const d = (await window.api.sql(sql.SQL, '新增', sql.data))
    console.log(d);
    return d;
  }
}

/**
 * 删除使用的链式方法
 * @param tableEntity 表实体
 * @constructor
 */
class DeleteableFun<T> extends DeleteableBase<T> {
  /**
   * 提交更新
   * @constructor
   */
  async ExecuteCommand(): Promise<number> {
    // SQL语句
    const sql: SQLType = this._GetDeleteSQL()
    return (await window.api.sql(sql.SQL, '删除', sql.data)) as number
  }
}

/**
 * 更新数据使用链式方法
 */
export class UpdateableFun<T> extends UpdateableBase<T> {
  /**
   * 提交更新
   * @constructor
   */
  async ExecuteCommand(): Promise<number> {
    // SQL语句
    const sql = this._GetUpdateSQL()
    return (await window.api.sql(sql.SQL, '修改', sql.data)) as number
  }
}

type Constructor<T> = new (...args: any[]) => T
export const dbHelp = {
  /**
   * 查询使用的链式方法
   * @param tableEntity 表实体
   * @constructor
   */
  Queryable<T>(tableEntity: Constructor<T>): QueryableHelpFun<T> {
    return new QueryableHelpFun<T>(getTableName(tableEntity), getTableColumns(tableEntity))
  },
  /**
   * 新增使用的链式方法
   * @param tableEntity 表实体
   * @param insertData 需要添加的数据
   * @constructor
   */
  Insertable<T>(tableEntity: Constructor<T>, insertData: T | T[]): InsertableFun<T> {
    const tableName = getTableName(tableEntity)
    const fields: TableFiledType[] = getTableColumns(tableEntity)
    const insertDatas: T[] = Array.isArray(insertData) ? insertData : [insertData]
    return new InsertableFun(tableName, fields, insertDatas)
  },
  /**
   * 删除使用的链式方法
   * @param tableEntity 表实体
   * @constructor
   */
  Deleteable<T>(tableEntity: Constructor<T>): DeleteableFun<T> {
    const tableName = getTableName(tableEntity)
    const fields: TableFiledType[] = getTableColumns(tableEntity)
    return new DeleteableFun<T>(tableName, fields)
  },
  /**
   * 修改使用的链式方法
   * @param tableEntity 表实体
   * @constructor
   */
  Updateable<T>(tableEntity: Constructor<T>): UpdateableFun<T> {
    const tableName = getTableName(tableEntity)
    const fields: TableFiledType[] = getTableColumns(tableEntity)
    return new UpdateableFun(tableName, fields)
  }
}

这里使用了渲染进程与主进程通信,所以我们还需要新增以下的方法

首先需要定义接口

需要在src/preload/index.d.ts中定义

import { ElectronAPI } from '@electron-toolkit/preload'
import { IgnoreMouseEventsOptions } from "electron";

declare global {
  interface Window {
    electron: ElectronAPI
    // 定义一下即可有提示了
    api: {
      /**
       * 执行SQL语句
       * @param sql SQL语句
       * @param type 类型
       * @param args 参数
       */
      sql: (sql: string, type: '查询' | '修改' | '删除' | '新增', args?: Record<string, any>) => Promise<any>
    }
  }
}

需要在我们的src/preload/index.ts中实现发送消息的通知

import { contextBridge, ipcRenderer } from 'electron'
import { electronAPI } from '@electron-toolkit/preload'

// Custom APIs for renderer
const api = {
  /**
   * 执行SQL语句
   * @param sql SQL语句
   * @param type 类型
   * @param args 参数
   */
  sql: (sql: string, type: '查询' | '修改' | '删除' | '新增', args?: Record<string, any>): Promise<any> => {
    return new Promise((resolve, reject) => {
      try {
        ipcRenderer
          .invoke('sql', sql, type, args)
          .then((result) => {
            resolve(result as any)
          })
          .catch((err) => {
            reject(err)
          })
      } catch (e) {
        reject(e)
      }
    })
  },
}

// Use `contextBridge` APIs to expose Electron APIs to
// renderer only if context isolation is enabled, otherwise
// just add to the DOM global.
if (process.contextIsolated) {
  try {
    contextBridge.exposeInMainWorld('electron', electronAPI)
    contextBridge.exposeInMainWorld('api', api)
  } catch (error) {
    console.error(error)
  }
} else {
  // @ts-ignore (define in dts)
  window.electron = electronAPI
  // @ts-ignore (define in dts)
  window.api = api
}

src/main/ipc.ts中定义执行逻辑的方法(这里的ipc需要在src/main/index.ts中引入)

// 主进程的main中引入
import './ipc'
import { ipcMain, IpcMainInvokeEvent } from "electron";
import { Db } from "sql-petra-orm";

/**
 * 操作SQL语句
 */
ipcMain.handle("sql",(event: IpcMainInvokeEvent, sql: string, type: '查询' | '修改' | '删除' | '新增', args?: Record<string, any>)=>{
  if (event) {
    switch (type) {
      case '查询':
        return Db.RunSQLQueryable(sql, args)
      case '修改':
        return Db.RunSQLUpdateable(sql, args)
      case '删除':
        return Db.RunSQLUpdateable(sql, args)
      case '新增':
        return Db.RunSQLInsertable(sql, args)
    }
  }
})

使用示例

<script setup lang="ts">
import { onMounted } from "vue";
import { dbHelp } from "@renderer/DbHelp/DbHelp";
import { tn_sys_user } from "../../types/tn_sys_user";

onMounted(async ()=>{
  const userCount:number = await dbHelp.Queryable<tn_sys_user>(tn_sys_user)
    .Where("cn_s_user_name","张三")
    .Count();
  const d:tn_sys_user = {
    cn_s_user_id:"2",
    cn_s_user_name:"李四",
    cn_s_user_sex:"男"
  }
  const insertCount = await dbHelp.Insertable<tn_sys_user>(tn_sys_user,d)
    .ExecuteCommand();
  console.log("张三的用户数:",userCount);
  console.log("新增李四信息是否成功:",insertCount >= 1);
})
</script>

<template>
  <div>
    测试SQLORM
  </div>
</template>

备份数据库

  • 备份底层调用 better-sqlite3backup
  • 建议在数据库初始化之后调用。
  • 如果需要严格等待备份完成,建议后续把 Db.Backup 的对外类型调整为 Promise<boolean>

使用示例

Db.Backup("F:\\项目\\SQLPetra\\backup.db");

完整示例

import { Db, table, column } from "sql-petra-orm";

@table("test_table")
class TestTable {
  @column({ fieldName: "table_id", isPrimaryKey: true })
  id?: number;

  @column({ isNull: true })
  name?: string;

  @column({ isNull: true })
  note?: string;
}

Db.InitDb({
  dbPath: "F:\\项目\\SQLPetra\\",
  dbName: "test.db",
  isWriteLog: true
});

Db.CreateTableByEntity<TestTable>(TestTable);

const tx = Db.Transaction(() => {
  Db.Insertable<TestTable>(TestTable, [
    { id: 1, name: "张三", note: "第一条数据" },
    { id: 2, name: "李四", note: "第二条数据" }
  ]).ExecuteCommand();

  Db.Updateable<TestTable>(TestTable)
    .SetColumnFiled("note", "已修改")
    .Where("id", 1)
    .ExecuteCommand();
});

if (!tx.isSuccess) {
  console.error(tx.message);
}

const list = Db.Queryable<TestTable>(TestTable)
  .Select<{ tableId?: number; userName?: string }>(x => ({
    tableId: x.id,
    userName: x.name
  }))
  .WhereIF(true, "name", "张三")
  .ToList<{ tableId?: number; userName?: string }>();

console.log(list);

Db.Close();