更新记录
1.0.0(2026-07-19)
首发。嵌入式 NoSQL 文档数据库——Mongo 风格 API + 离线优先 + 整库 AES-256 加密,本地记事 / 账本 / 离线表单 / 数据缓存。
- 零 SQL、零建表:文档就是 JSON,集合首写自动创建,字段随时增减
- Mongo 风格 CRUD:insertOne/insertMany/findOne/find/updateOne/updateMany/deleteOne/deleteMany/countDocuments/distinct/bulkWrite
- 查询算子全集:$eq/$ne/$gt/$gte/$lt/$lte/$in/$nin/$exists/$and/$or/$not/$regex + 点路径
addr.city+ 数组下标 - 更新算子:$set/$unset/$inc/$mul/$rename/$push(+each/slice)/$addToSet/$pop/$pull/$currentDate 等
- 二级索引:单字段 / 复合 / 唯一 / 稀疏 / TTL 过期索引 + 执行计划(findWithPlan / findWithHint)
- 聚合管道:$match/$project/$group/$sort/$limit/$skip/$count/$unwind/$lo
平台兼容性
uni-app x(5.14)
| Chrome | Safari | Android | iOS | 鸿蒙 | 微信小程序 |
|---|---|---|---|---|---|
| × | × | 5.0 | 12 | × | × |
nex-jsondb 嵌入式 NoSQL 文档数据库(Mongo 风格 · 离线优先 · 整库加密)
把一个 Mongo 风格的文档数据库直接塞进 App——不用后端、不写一行 SQL、不建表。原生内核负责存储、索引、事务、加密,你只管用熟悉的
insertOne/find/aggregate存取 JSON。数据全程存在设备本地文件里,加密库整库 AES-256-GCM 落盘。百万级文档、二级索引、聚合管道、跨集合 ACID 事务、变更订阅——一个插件配齐本地优先 App 的数据层。
特性
- 零 SQL、零建表:文档就是 JSON,字段随时增减;
insertOne('{"name":"张三","age":30}')就存进去了 - Mongo 风格 API:
insertOne/insertMany/findOne/find/updateOne/updateMany/deleteOne/deleteMany/countDocuments/distinct/aggregate/bulkWrite——从 MongoDB / mongoose 过来零学习成本 - 查询算子全集:
$eq/$ne/$gt/$gte/$lt/$lte/$in/$nin/$exists/$and/$or/$not/$regex,支持点路径addr.city和数组下标items.0.price - 更新算子:
$set/$unset/$inc/$mul/$rename/$push(+each/slice)/$addToSet/$pop/$pull/$currentDate等,原地更新不用读改写 - 二级索引 + 执行计划:单字段 / 复合 / 唯一 / 稀疏 / TTL 过期索引;
findWithPlan看走了哪个索引、findWithHint强制指定索引 - 聚合管道:
$match/$project/$group/$sort/$limit/$skip/$count/$unwind/$lookup九阶段 + 9 种累加器,报表/分组统计端上直接算 - 跨集合 ACID 事务:
withTransaction(cb)一个回调包住多集合写入,抛错自动回滚——转账/扣库存这类操作要么全成要么全不成 - 变更订阅 ChangeStream:
watch*()订阅集合变更,subscribe(cb)增删改实时回调,做本地数据驱动的 UI 刷新 / 多页面同步 - 整库口令加密:
AES-256-GCM + PBKDF2加密到文件,口令错打不开;changePassword改口令秒级完成(不重写全库数据) - 纯本地、零权限:无网络、无账号、无隐私权限,数据只在设备本地文件——离线记事 / 账本 / 表单缓存天然合规
- 大结果集游标:
findCursor分批取 + 链式sort/limit/skip/project,百万文档不一次性撑爆内存
快速上手:存查改 + 索引 + 事务 + 订阅(完整可抄)
import { openJsonDb, JsonDb, Collection, Transaction } from '@/uni_modules/nex-jsondb';
// 1) 打开/创建数据库(路径放应用沙箱;加密库带 password,明文库省略 options)
// ⚠️ path 要绝对路径:uni.env.CACHE_PATH 在部分基座是 unifile:// 虚拟路径,
// 直传可能报「文件打不开」——见下方「路径契约」用 uni.getFileSystemManager 转真实路径。
const db: JsonDb = await openJsonDb(
uni.env.CACHE_PATH + '/app.jsondb',
{ password: 's3cret' } as OpenOptions // 不加密就整个 options 省略
);
// 2) 取集合(首写自动建,无需 CREATE TABLE)
const users: Collection = await db.collection('users');
// 3) 增:插一条 / 批量插(返回 _id;文档没写 _id 会自动生成)
const id = await users.insertOne('{"name":"张三","age":30,"city":"杭州"}');
await users.insertMany('[{"name":"李四","age":25,"city":"上海"},{"name":"王五","age":41,"city":"杭州"}]');
// 4) 查:Mongo 风格条件(返回 JSON 字符串,自己 JSON.parse)
const one = await users.findOne('{"name":"张三"}'); // 单条或 null
const adults = JSON.parse(await users.find('{"age":{"$gte":18}}')); // 数组
// 5) 改:更新算子(optionsJson 无选项传 '{}',upsert 传 '{"upsert":true}')
await users.updateOne('{"name":"张三"}', '{"$set":{"age":31},"$inc":{"visits":1}}', '{}');
// 6) 索引:给 age 建索引,city 建唯一约束
await users.createIndex('{"age":1}', '{}');
await users.createIndex('{"city":1,"age":-1}', '{}'); // 复合索引
// 7) 聚合:按城市分组算人数(端上直接出报表)
const byCity = await users.aggregate(
'[{"$group":{"_id":"$city","count":{"$sum":1}}},{"$sort":{"count":-1}}]'
); // → ['{"_id":"杭州","count":2}', '{"_id":"上海","count":1}']
// 8) 事务:跨集合原子写(转账场景——抛错整体回滚)
const accounts = await db.collection('accounts');
await db.withTransaction(async (tx: Transaction): Promise<any> => {
const acc = accounts.inTransaction(tx); // 取事务内句柄
await acc.insertOne('{"user":"张三","balance":100}');
// 任何一步抛错 / reject → 自动 rollback + rethrow,上面的插入不落库
return null;
});
// 9) 订阅变更:users 集合有增删改就回调(做实时 UI)
const stream = await db.watchCollection('users');
stream.subscribe((eventJson: string) => {
const ev = JSON.parse(eventJson); // {op, collection, id, ...}
console.log('users 变更:', ev.op, ev.id);
});
// 页面卸载时:stream.unsubscribe();
// 10) 用完关库(flush 落盘 + 释放句柄)
// await db.close();
游标取大结果集:const cur = await users.findCursor('{}'); cur原生库rt('{"age":-1}').limit(50); while (await cur.hasNext()) { const doc = await cur.next(); }。示例工程 examples/smoke-viz 里有 open→insert→find→索引→聚合→事务→watch 的完整消费链可照抄。
API 速览(53 个 · 3 顶层函数 + 6 对象共 50 方法)
| 分组 | API |
|---|---|
| 顶层(3) | openJsonDb(path, options?) → Promise<JsonDb> · platformCapabilities()(能力探测)· jsondbVersion() |
| JsonDb(8) | close · changePassword · collection · beginTransaction · withTransaction(cb)(糖衣)· watchCollection · watchCollections · watchAll |
| Collection(21) | 增 insertOne/insertMany · 查 findOne/find/findWithPlan/findWithHint/findCursor · 改 updateOne/updateMany · 删 deleteOne/deleteMany · 计数 countDocuments/estimatedDocumentCount/distinct · aggregate · bulkWrite · 索引 createIndex/dropIndex/listIndexes · reapExpired(清 TTL)· inTransaction(同步) |
| Cursor(10) | 链式配置(同步返回 this)sort/limit/skip/project/batchSize/hint/rewind · 迭代 next/hasNext/toArray |
| ChangeStream(5) | next(timeoutMs)(手动轮询)· subscribe(cb)/unsubscribe(订阅糖衣)· resumeToken(断点续读)· close |
| Transaction(3) | commit · rollback · isActive(同步) |
| TxCollection(3) | insertOne · findOne · countDocuments(事务内读写) |
- 异步 vs 同步:读写/事务/订阅启动都是 Promise(后台线程执行,不冻 JS);Cursor 的配置链、
resumeToken/isActive/platformCapabilities/jsondbVersion是同步。 - 数据全是 JSON 字符串进出:
find/findOne/aggregate返回 JSON 字符串(数组或单条),插入/更新/查询条件也传 JSON 字符串——桥接不做二次解析,调用方JSON.parse/JSON.stringify(避免大结果双拷贝)。 - 错误可
try/catch(口令错Decrypt、唯一冲突DuplicateKey、事务超时TransactionTimeout、单文档超 16MBDocumentTooLarge……message 双端一致)。
常见问题
- 打开报「文件打不开」/ os error 2? 九成是
path不是真实文件路径:uni.env.CACHE_PATH在部分自定义基座返回unifile://cache/虚拟协议,原生 文件层认不得。用uni.getFileSystemManager()的真实沙箱目录,或把file:///unifile://转成绝对路径再传(见「路径契约」)。 - 忘了口令还能打开吗? 打不开——整库 AES-256-GCM 加密,口令是解密密钥,没有后门。请自行保管口令(可用系统 Keychain/Keystore 存);换口令用
changePassword(旧, 新)(秒级,不重写全库)。 - 要不要建表 / 定义 schema? 不用。集合首次写自动创建,文档字段随意增减(同集合不同文档可以字段不一样)。要约束就建唯一索引(
createIndex('{"email":1}','{"unique":true}'))。 find为什么返回字符串不是对象? 大结果集直接吐 JSON 字符串避免跨桥双拷贝,你JSON.parse一次即可;超大结果集用findCursor分批next(),别一次toArray()全进内存。- 事务怎么用才对? 用
withTransaction(cb)糖衣:在 cb 里对每个要写的集合调collection.inTransaction(tx)拿事务句柄再写,抛错自动回滚。别在 cb 里手动commit/rollback(糖衣统一收口);事务活跃时用普通(非事务)句柄写会报MixedTransactionContext。单写者语义:同时只一个活跃事务,超时 30s 自动回滚。 - ChangeStream 会漏事件吗? 不会漏已发生的:存下
resumeToken(),重开流时传sinceToken从断点续读(token 过期报ResumeTokenExpired)。subscribe(cb)是后台轮询糖衣,一个流一个订阅者;页面卸载记得unsubscribe()。 - 能存多大 / 多少条? 单文档上限 16MB(超了报
DocumentTooLarge),文档条数受设备存储限制;百万级文档配二级索引查询依然快,大扫描走findCursor分批。 - TTL 过期数据怎么清? 建 TTL 索引(
createIndex('{"createdAt":1}','{"ttlSeconds":86400}'))后过期文档惰性清理;要立即清调reapExpired()。 - Android 打包报「找不到 jna」? 本插件含原生内核库,须制作自定义调试基座(HBuilderX → 运行 → 制作自定义调试基座),标准基座跑不了。
平台说明
| 端 | 说明 |
|---|---|
| Android | 原生内核库(arm64-v8a / armeabi-v7a / x86_64 全覆盖);需自定义基座运行 |
| iOS | 原生内核库(真机 + 模拟器);HBuilderX 打包勾选「支持 Swift」 |
| H5 / 小程序 | 不支持(数据库 = App 端原生文件存储 + 加密能力,浏览器/小程序无对应底座);platformCapabilities().supported 在这些端返回 false,所有 API 诚实抛错——请在业务层按 platformCapabilities() 判端降级 |
路径契约(务必读)
openJsonDb(path, ...) 的 path 必须是能被原生文件系统打开的绝对路径:
- ✅ 应用沙箱绝对路径,如
/data/.../files/app.jsondb(Android)、.../Documents/app.jsondb(iOS) - ✅
file://URI(内部会转绝对路径) - ⚠️
uni.env.CACHE_PATH/_doc在部分自定义基座是unifile://虚拟协议——直传会失败。用uni.getFileSystemManager()或平台 API 拿到真实沙箱目录后拼接文件名再传。 - 目录须已存在(插件只建数据库文件,不递归建父目录)。
上限保护:单文档 16MB、事务超时 30s、聚合/排序内存各 10MB——超限抛可捕获错误,绝不静默截断或闪退。完整 API 文档与验证状态见仓库 docs/jsondb-plugin-contract.md。
技术支持 / 问题反馈:插件评论区或联系作者。

收藏人数:
购买普通授权版(
试用
赞赏(0)
下载 1178
赞赏 0
下载 12439287
赞赏 1934
赞赏
京公网安备:11010802035340号