更新记录

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 风格 APIinsertOne / 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) 一个回调包住多集合写入,抛错自动回滚——转账/扣库存这类操作要么全成要么全不成
  • 变更订阅 ChangeStreamwatch*() 订阅集合变更,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、单文档超 16MB DocumentTooLarge……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

技术支持 / 问题反馈:插件评论区或联系作者。

隐私、权限声明

1. 本插件需要申请的系统权限列表:

2. 本插件采集的数据、发送的服务器地址、以及数据用途说明:

插件不采集任何数据。所有计算/处理均在本地完成,无任何网络请求、不发送数据到任何服务器。

3. 本插件是否包含广告,如包含需详细说明广告表达方式、展示频率:

暂无用户评论。