Evolu SQLite 瓦斯
这个软件包是SQLite 编译到WebAssembly的 Evolu 自己最小的 TypeScript 层,用于加载和使用. Evolu's SQLite Core, in C并链接到WebAssembly,加密数据库.
为什么我们自己的类型脚本
SQLite的WebAssembly建造了通用JavaScript层. 它大约有一万五千行源,覆盖了整个CPI,多个存储VFS,以及一个工人API,外加Emscripten所生成的运行时间.
我们用我们自己写的JavaScript 来代替它 尽可能正确 它全由TypeScript所出,有品牌指针类型和打出错误,而不是用手写声明来松散地打出JavaScript. 我们移植了SQLite团队的测试 瓦克丽特 为了这个包裹船。 我们还为本地第一数据重要的故障路径撰写了自己的测试,例如中断交易后恢复,存储耗尽,连接之间锁定冲突等. 他们在Chromium,Firefox和WebKit的CI中运行.
我们重新执行 SQLite 的 JavaScript 严格测试先:红色,绿色,重构. 每一个行为都是作为失败测试开始的,从SQLite团队的测试或wa-sqlite的测试中移植,或者写给我们的失败路径. 只有这样,我们才会添加代码, 至少可以使测试通过, 我们清理它的同时测试会保持绿色.
这些失败路径是真实的。 在2026年9月准备这个软件包时,我们发现opfs-sahpool,VFS Evolu所依赖的,从未卷起由于关闭分页而中断的交易. 下一个开口时看到它半应用. 纠正这种情况发现了第二个问题:一个工人的重叠关系可能默默地失去承诺的数据。 Evolu只打开了一个连接,但是打开了几个的应用程序和库被曝光了. 现在他们得到 SQLITE_BUSY 换句话说 我们报告了这两起事件,SQLite小组在一天之内在SQLite 3.54.0. 这个 论坛线索 有细节和复制.
我们还要求 SQLITE_FULL 当浏览器表示存储已满时,所以一个应用程序可以告诉它的用户而不是显示通用磁盘 I/O 错误. SQLite团队 拒绝。他们的 JavaScript 避免了浏览器特定的行为,所以每个失败的写入都会保留 SQLITE_IOERR。但主要信号不是针对浏览器的。 这个 文件系统标准 需要同步访问控件 write() 财务报告和审定财务报表 truncate() 要掷出 QuotaExceededError 什么时候会超过限额。 我们的分层报告 SQLITE_FULL 为它和Firefox的短文。 我们在Chromium和Firefox测试这个.
WebAssembly出口的每个公共 SQLite C 函数都可以作为独立函数导入 @evolu/sqlite-wasm/c-api,所以应用程序只支付其使用的功能. 我们从SQLite自己的签名表里生成它们,并对照二进制来检查它们. Evolu的SQLite核心的功能,层被称作"内". 流浪汉 sqlite3_config, (中文(简体) ), sqlite3_db_config 财务报告和审定财务报表 sqlite3_vtab_config 如SQLite的"JavaScript"中,通过建筑的固定参数shims. 一个小装填器取代了 Emscripten 生成的运行时间. 一起使JavaScript小得多. 地层是同步的,高级API返回打出的结果.
SQLite的存储 VFS, 我们只保留 opfs - sahpool。 Evolu已经使用它,它最适合Evolu。 它是同步的,通过OPFS同步访问手柄读取并写入的,直接在工人体内,没有辅助工人. 不需要跨源地隔离. SQLite的"opfs" VFS 需要 SharedArrayBuffer,它要求 Cross-Origin-Opener-Policy 财务报告和审定财务报表 Cross-Origin-Embedder-Policy 头和他们带来的限制。 萨赫普尔也不需要 这是最快的。 SQLite的文档称它是其OPFS VFS中表现最出色的,对于分批操作来说显然是如此.
捕捉到的是,每次只有一分标签或工人可以打开一个数据库. 作为 Offs- sahpool 的 SQLite 文件, 一个池可能一次使用一个目录, 跨越工人和用地实例。 所以一个目录的打开必须被序列化,Evolu用它的领袖Web Lock来做. 否则,两个新目录或空目录的打开器可以各自创建自己的槽. 在下一次打开时,只有一个版本的文件显示,另一个则被隐藏,永远不再被再利用. 我们保留 Opfs -sahpool 的字节字节用于字节,所以现有的数据库继续工作. 如果Evolu曾经需要另一个VFS,比如每个数据库都有一个OPFS文件,就可以在它上创建出新的数据库,而现有的数据库则在池中继续工作,没有迁移.
Evolu的 SQLite 核心,它自己的 C 在 @evolu/sqlite-core,加密网络大会内部的数据库。 池以内部名称注册其VFS,而核心的编码器则以池名注册其上,再将VFS解除注册,因此每个文件SQL通过池所到达的都通过代码. 核心加密为 熊猫'被咬断的常时AES, aes_ct64,及其SHA-512,并拥有自有的HMAC-SHA512和PBKDF2. 与基于表格的AES,其内存访问依赖于密钥不同,它不会在CPU的缓存中留下任何密钥的痕迹,用于在旁边运行代码,例如另一个分页或app. scripts/constant-time.mts 检查编译的函数分支是否没有秘密,并逐一索引任何内存 scripts/constant-time.md 继续检讨 WebAssembly及其JIT编译器承诺不会持续时间,因此只要一个引擎不添加自己依赖秘密的分支或内存访问,这就可以维持.
格式为 SQLCipher 4's 作为 sqlcipher sQLite3的图案是多密码的。 所以数据库 @evolu/web 3 个加密 @evolu/sqlite-wasm 2.2.4 开放不变,2.2.4 打开我们写的东西。 每页,同样在回滚日记中,都用AES-256-CBC和随机IV加密,并用HMAC-SHA512认证. SQLite从不看到密钥:在开口的原声内存中进行分层分层,紧接后再进行零分;而核心则将其密钥保存在原声内存中,直到数据库被处理,或者在一个陷阱后直到实例结束. SQLite的繁多分享了这个内存,所以在SQLite或 sqlite3-wasm.c 可以用3.54.0-building2, 其池在 TypeScript 中加密, 它可以到达纯文本, 但不能到达密钥 . src/evolu_page.h 联 合 国 @evolu/sqlite-core 文档格式. @evolu/web 1.0.1-审查6至2.4.0加密数据库 PRAGMA legacy = 4,SQLCipher 4的自有格式,从字节16加密出第1页. 这些数据库自2006年12月31日以来尚未开放 @evolu/web 3: 00,他们也不在这里打开.
@evolu/web 作为 SQL 文本通过 2.2.4 键 x'<hex>'SQLCipher对生钥匙的注解 SQLite3 多发密码 2.2.4 把它当作密码句,因为 错误,固定在2.2.5中。 所以数据库 @evolu/web 3 加密,2.2.4 打开 键从该文本,像以前。 它们永远不会自动重接。 我们创建的一个数据库 用钥匙本身加密了 它的2.2.4 只有在它原始钥匙标记中的钥匙才打开, raw:<hex>。当一个崩溃撕毁了一个遗留数据库的盐, 它的前16个字节, 它的热门期刊的一页记录商店打开它.
只有数据库文件及其日记被加密. 2.2.4 加密文件 VACUUM INTO 财务报告和审定财务报表 ATTACH 使用数据库的密钥写入 。 这里, VACUUM INTO 写入一个未加密的文件,以及 a File 或者说 EncryptedFile 数据库中的 ATTACH 附加只读文件, 忽略它 KEY 条款,并创建任何文件. PRAGMA key, (中文(简体) ), rekey, (中文(简体) ), cipher 及其变体在文件数据库中失败,是否加密,而不是被忽略,所以没有数据库通过SQL键入,而一个内存数据库仍然忽略它们. 保存加密数据库 secure_delete 打开了2.2.4 与 2.2.4 不同,它拒绝关闭它,因为自由的一页 SQLite 不写会无法认证. 我们的测试将编码器与2.2.4本身和3.54.0-build2进行比较,后者的池在TypeScript、Node.js和Chromium、Firefox和WebKit加密,并与该格式的安装进行比较 node:crypto。 。 。 .
更早的Evolu版本的Tabs可以与更新型的共享池子,轮流使用Evolu的领跑锁. 在被Evolu所保持的SQLite默认的DELETE日记模式中,这个建筑和3.53.4-build1到3.54.0-build2,其池被加密为TypeScript,读取并扩展了对方的数据库并翻回了对方的热日记. 一个日记 SQLite 以 PERSIST 模式或 EXCLUSIVE 锁定方式保存,并在路径上重建一个数据库,可以使这些建筑保持半应用交易,因此共享池只支持 DELETE 模式. 2.2.4从不退缩热门期刊,因为其opfs-sahpool报告每个文件都是被锁定的. 在此坠机后,一 2.2.4 分页读取了半写行,其下个写出交易使得它们永久.
常时AES比基于表格的AES慢 @awasm/noble,它加密了3.53.4-build1到3.54.0-build2. 在一款Apple M5上,破解一页8 KiB需要60到70μs,并用它的日记记录写出一页,约300μs,对应40到60μs和约100μs并有3.54.0-building2,在Chromium,Firefox和WebKit都一样. 单位 bench:sqlite-wasm与3.54.0-building2相比,在加密数据库中,Wa-sqlite的工作量要多50%到80%。 少花钱 在存储被闲置后,就像在应用程序中一样,加密在所有三个引擎的小型承诺上增加了2.5至2.7 ms,而用3.54.0-building2的存储量为1.4至1.9 ms. 在Chromium的macOS上,背对背承负每架多花费2至4 ms,而Firefox和WebKit的背负负负负负负负负负负负负负负负负负负负负负负负负负负负负负负负负负负负负负负负负负负负负负负负负负负负负负负负负负负负负负. 铬的 OPFS 冲出 F_BARRIERFSYNC,在 macOS 上,在存储活动暂停约100到200 μs后会放慢速度,而加密则使这种暂停.
无论如何,我们保持常时的AES, 因为保护关键事项比原始速度更重要。 一个小的承诺需要更长的毫秒,而成本只随着交易所写的页面而增加,这与加密所保护的内容几乎没有关系. 价为临时. aes_ct64 每通口口口口口口口口口口口口口口口口口口口口口口口口口口口口口口口口口口口口口口口口口口口口口口口口口口口口口口口口口口口口口口口口口口口口口口口口口口口口口口口口口口口口口口口口口口口口口口口口口口口口口口口口口口口口口口口口口口口口口口口口口口口口口口口口口口口口口口口口口口口口口口口口口口口口口口口口口口口口口口口口口口口口口口口口口口口口口口口口口口口口口口口口口口口口口口口口口口口口口口口口口口口口口口口口口口口口口口口口口口口口口口口口口口口口口口口口口口口口口口口口口口口口口口口口口口口口口口 一旦SQLCipher 5.0被放出,Evolu的核心切换到它的页面格式AES-256-GCM(757号),其计数器模式不连锁块,所以加密一页可以填满所有四个道,并且应该花费现在解密一个道成本的成本.
二进制不包含 SQLite3 多个密码,其中2.2.4加密为. 是SQLite自取自sqlite.org的C,由SQLite自取自出自出自出自出自出自出自出自出自出自出自出自出自出自出自出自出自出自出自出自出自出自出自出自出自出自出自出自出自出自出自出自出自出自出自出自出自出自出自出自出自出自出自出自出自出自出自出自出自出自出自出自出自出自出自出自出自出自出自出自出自出自出自出自出自出自出自出自出自出自出自出自出自出自出自出自出自出自出自出自出自出自出自出自出自出自出自出自出自出自出自出自出自出自出自出自出自出自出自出自出自出自出自出自出自出自出自出自出自出自出自出自出自出自出自出自出 核心的解码器,页面格式和密码学, evolu_codec.c, (中文(简体) ), evolu_page.c 财务报告和审定财务报表 evolu_crypto.c 没有标题,是2 066行的C线,其中只有本土建筑编译,超过1 296行的BearSSL aes_ct64 和 SHA-512 的字节帮助符,其中3.53.4-build1至3.54.0-build2加密后有975行 TypeScript 和 @awasm/noble在SQLite的3.53.4全版建筑中,在核心之前,这里是省去SQLite3多发密码2.5.1给出的:
- C源编译的建筑缩短了100 241行. 这是SQLite的269 649行
sqlite3.c而不是369,890行的 SQLite3 复式 Ciphers 组合. 额外线路中约72,500条是第三方代码:它的密码计划,它们的密码,以及被捆绑的库如AEGIS,mitz和Argon2等. 其余是SQLite自己的代码:sqlite3.h, (中文(简体) ),sqlite3ext.h, (中文(简体) ),tclsqlite.c和扩展名ext/misc。 。 。 . - 电磁波从1,068,015到868,886字节,从473,354到401,985字节
gzip -9。 。 。 . - 原出口216个公共C功能,而不是232个。 11号
sqlite3mc_*函数已删除。 SQLite加密扩展的API也是如此,SQLite3多位加密器已经实现:sqlite3_key, (中文(简体) ),sqlite3_key_v2, (中文(简体) ),sqlite3_rekey, (中文(简体) ),sqlite3_rekey_v2财务报告和审定财务报表sqlite3_activate_see。 。 。 .
亦有少相相相信相. 2.2.4 重新包装了一个WebAssembly二进制的又一个项目,其内部有该项目的密码和捆绑的库. 我们从SQLite的源头在CI中构建我们的,由hash sqlite.org的出版所支撑,有Evolu的SQLite核心和BearSSL的文档,由承诺所支撑,并由他们的文摘检查,而建筑复制了同一种字节. 软件包没有运行时间依赖关系,只有对等依赖关系, @evolu/common埃沃鲁自己的,谁的 Result 财务报告和审定财务报表 Task 它的API返回。 2.2.4没有。 这样就少了审计代码 少了供应链攻击的藏身地.
不过我们不会载运那座建筑 我们把SQLite的裸骨配置 和makefile自己的变量一起选择 它伴随着由Evolu查询的JSON,以及由应用通过Evolu的查询构建器调取的数学函数. 它还保存临时文件,如种类,临时表格和声明日记等,作为记忆. 它省略了WAL,授权者,进度回调,增量blob I/O和内检平分. Evolu自己的SQL测试在Evolu的本土建筑上通过. 消费物价指数只涵盖我们建造的东西。 FTS5, R* Tree, 会话扩展名, 更新前的钩子, 列元数据函数 sqlite3_column_database_name, (中文(简体) ), sqlite3_column_table_name 财务报告和审定财务报表 sqlite3_column_origin_name,而虚拟表格和窗口功能的注册则不存在. 如果你需要其中之一,启动 讨论情况。 与 SQLite 的全版建筑3.53.4 相比,没有 SQLite3 多个 Ciphers :
- 减C被编译. 13个档案
sqlite3.c269 649行中的49 818行无贡献,其中fts5.c, (中文(简体) ),sqlite3session.c, (中文(简体) ),wal.c财务报告和审定财务报表rtree.c. 预处理器后,在我们3.53.4所建的SQLite的C有87,381条非横线,而不是119,158条,在Evolu所建的SQLite核心被链接到之前. - 从868,886到649,755字节,从401,985到298,537
gzip -9,从348,808到259,521与布罗特利的质量11. 使用 SQLite3 多重 Cephers ,全版建筑为 1 068 015字节,473 354 有gzip -9我们添加的堆栈溢出检查如下: 3.53.4 二进制 669 403 字节, 302 491 以gzip -9和262,026 与布罗特利。 出口备用API的3.54.0 二进制是677 479字节,305 733同gzip -9和265,111 与布罗特利。 我们现在的3.54.1 二进制,它也包含 Evolu 的 SQLite 核心,是 720,542 字节, 323,059 与gzip -9和279,123 与布罗特利。 JavaScript 的收缩超过被子的收缩: 什么@evolu/web从包中的进口量为39,956字节,由esbuilding精炼而成,13,493个字节gzip -9,其中194 844和67 501与@awasm/noble。加密的工人也不再分配@awasm/noble是两个WebAssembly的记忆,30个MiB. - 原站出口156个公共C功能,而不是216个,进口32个功能,而不是35个,除了它的内存和堆放检查的处理器之外。 自3.54.0起,赤骨也制造出备份API的五个功能,因此我们的出口161. 自3.54.1-build1起,它还出口了Evolu的SQLite核心的5个功能 层调和进口WASI的
random_get,核心从中提取出盐类和IV.
Evolu 打开每个数据库一个连接,一次运行一个操作. 没有 SQLITE_BUSY {\fn黑体\fs22\bord1\shad0\3aHBE\4aH00\fscx67\fscy66\2cHFFFFFF\3cH808080}处理和不与理性分离 一个工人更多的联系 也不会增加平行性。 它们都运行在相同的JavaScript线上,有了回滚的日记,一个读者仍然会阻碍另一个连接的承诺. 因为每一次通话都是同步的, 这种连接得到 SQLITE_BUSY 连另一个都等不及了 它只能屈服和重试,这正在用额外的步骤来序列化. 我们更喜欢这种简单的心理模型,但如果出现真正的使用案例,我们愿意重新审视. 开始吧 讨论情况。 。 。 .
我们观察着SQLite的JavaScript的上游变化,并在我们自己审计后将其纳入其中. 我们的加载器还依赖于WebAssembly进出口的功能,这些功能来自于Emscripten版本,SQLite的构建设置和Evolu的SQLite核心的功能,这些功能是层所呼唤的. 发电机将进出口品同其相干类型,以及SQLite的常数和结构布局相配合。 检查失败时, 当其中任何一个新建不同时, 所以每个工具链的升级都得到相同的审查 。 装载器拒绝一个二进制,其常数或导出名称与被绑定名称不同,或者需要导入它不执行的二进制. 检查或加载器都无法比较代码或构建不改变这些设置的设置,例如 SQLITE_TEMP_STORE测试针指向那些 PRAGMA compile_options。 。 。 .
网络大会
我们建造 .wasm 我们自己 scripts/build-wasm.mts这是SQLite 3.54.1 从sqlite.org的源zip,未标注,发布 @evolu/sqlite-core pins,由 Emscripten 6.0.3 与 SQLite 自己的 makefile 相编译,在其有以上选项的赤骨配置中,加上 Emscripten 的堆叠溢出检查,并与 Evolu 的 SQLite 核心和 它的供应商的 BearSSL 代码相接, Emcc 与 emcc 编译 -O2也写了一张符号地图, wasm/sqlite3.symbols,带有被剥去的被剥去的函数名称。 来源由SHA3-256 sqlite.org出版. 缩写由它的版本所标注,在CI中也由图像摘要所标注. 输出的sha256覆盖了其余部分:建筑失败,除非它等于被钉住的一并和发电机的针相匹配. 在新工作目录中有两个建筑给出了相同的字节. 这个 SQLite Wasm checks工作流程的工作在CI中构建,在文摘所标注的官方标记图像中构建。 当输入没有改变时, 任务会从缓存中恢复原站 。 不管怎么样,它都会把它交给测试它的工作。 已建文件从未执行 .
Evolu 仓库装入的测试和游乐场 wasm/sqlite3.wasm 当它不见了或者不是被钉住的时候就失败了 要得到它而不建造它,运行 pnpm sqlite-wasm:download 在寄存器根中。 它下载了这个软件包中的版本 package.json 并写下这个包的原型,前提是它的sha256 符合针入 scripts/check-wasm.mts。在该版本发布前,下载失败与 HTTP 404. 如果在软件包版本之前的针头发生变化,下载的被子有不同的校验和. 在这两种情况下,均按下文所述在当地建造。 例子也装入了原作,但并不检查,因为它们是复制的模板.
要建立或验证它,使用 图像 已安装并激活了 。 如果建筑成功,它复制了被钉住的字节:
EMSDK=/path/to/emsdk node scripts/build-wasm.mts
没有emsdk,在Emscripten图像 CI的用法中用多克28来建造,由同一份文摘来支撑. 运行 pnpm install 首先,这个在寄存器根中。 图像的节点(Node.js)比寄存器的要长,所以命令从官上挂载寄存器的版本 node 图像,Docker不会自己拉出一个挂载。 在Linux上这个图像的构建给出的字节与macOS上的emsdk相同:
docker pull node:24.22.0-slim
docker run --rm -u "$(id -u):$(id -g)" -e HOME=/tmp -v evolu-sqlite-wasm:/tmp \
-v "$PWD":/src -w /src \
--mount type=image,source=node:24.22.0-slim,target=/node \
emscripten/emsdk:6.0.3@sha256:bb0910e6a18bb9bd7cb31ae4ed40f9073148b78cb2cdb8ea8676454e0d85425c \
/node/usr/local/bin/node packages/sqlite-wasm/scripts/build-wasm.mts
建筑在 evolu-sqlite-wasm 音量而不是在寄存器中,因为基于macOS主机的文件共享可以失去SQLite配置集的权限. 一个失败的建筑将它的工作目录保存在那里, 所以要将它的名称标出, 运行 packages/sqlite-wasm/scripts/generate.mts --pin-build 用同一命令中的路径代替 build-wasm.mts。 。 。 . docker volume rm evolu-sqlite-wasm 删除工作目录.
SQLite的制作文件将512 KiB C堆放在了SQLite的静态数据上方. 在SQLite的自建中,SQL是合法而深为巢穴的,例如SQLite允许1000的600个触发器的链子,将堆栈运行入到该数据并覆盖它而不出错. 2.2.4,这种插入从未返回。 没有支票,我们的二进制失败了 SQLITE_NOMEM,甚至打开另一个数据库。 所以,我们与 -sSTACK_OVERFLOW_CHECK=2。用它,每个函数将它设置的堆栈指针与堆栈的极限进行比较,我们的装载器将溢出物变成一个 WebAssembly.RuntimeError,它打破了实例 像任何陷阱做。 它在我们的3.53.4 二进制中增加了19,648字节,3,954个字节 gzip -9在Apple M5上的第24个节点中 run 财务报告和审定财务报表 exec 在一个内存数据库上 需要1%到5%的时间.
炸弹
sqliteWasmUrl 实值 new URL("../wasm/sqlite3.wasm", import.meta.url). Vite, webpack, Next.js和其他捆绑工都认得它. 他们用应用程序发出二进制,并给出其副本的URL. 过 fetch(sqliteWasmUrl) 改为 createSqliteWasm。它编辑二进制,同时下载,如果服务器发送的准确性 application/wasm,没有参数,这就是类型 WebAssembly.compileStreaming 要求。 否则,一旦下载,它就会从它的字节中编译二进制.
Vite 不按它预捆绑的依赖处理 URL, 所以添加 @evolu/sqlite-wasm 改为 optimizeDeps.exclude。当应用程序导入时, Vite 也会发出二进制 @evolu/sqlite-wasm,即使应用程序装入自己的二进制, 所以PWA 预留 *.wasm 下载它。 网页包只在应用程序使用时才发布 sqliteWasmUrl。 。 。 .
版本数
和SQLite自己的npm软件包一样,该版本是SQLite软件包所构建的版本,之后是 -build<n>。 。 。 . 3.53.4-build1 是我们第一个建造的SQLite 3.53.4, 3.53.4-build2 下一张,新的 SQLite 发布开始于 -build1。构建号数计算我们的发布,发布时也可能改变TypeScript API。 将精确的版本打入并读取升级前的发行注释. npm 治疗 -build<n> 作为预放,所以范围如: ^3.53.4-build1 从来没有达到另一个 SQLite 发行的构建。 Semver 比较 build10 与 build9 作为文本,所以第10个建筑将排序到第9个下面。 仓库的 scripts/version-sqlite-wasm.mts 拒绝,所以SQLite的发布最多得到9个建筑,而第10个需要一个新的SQLite接针.
贷项
- SQL 点,公有领域.
- 熊猫 由Thomas Pornin,麻省理工学院许可, 其常时AES和SHA-512加密数据库, 以及其SHA-256的跳过列表函数使用.
- 标记麻省理工学院授权书 将Web Assembly和Musl的C图书馆连入其中.
THIRD_PARTY_NOTICES.md 持有WebAssembly许可要求的通知.
文档
详情和使用实例请访问 (原始内容存档于2017-10-21). evolu.dev。 。 。 .
来源、实例和测试
Npm 包包括源和测试, 匹配安装的版本在
node_modules/@evolu/sqlite-wasm/。以下链接打开 GitHub main
分支; 检查特定版本的行为时使用已安装的文件.
src/载有执行情况和JSDoc API文件, 在Memory.ts。 。 。 .*.test.ts源文件同时提供使用示例并显示 测试行为 边缘案例 和类型合同 见Memory.test.ts。 。 。 .
对于应用程序的进口,使用 exports 在安装的软件包中
package.json测试的相对进口可以进入内部帮助者.
社区
埃沃卢社区开始 GitHub 讨论在那里,你可以问 问题和声音的想法.
与其他社群成员聊天时,您可以加入 Evolu 迪斯科尔。 。 。 .