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

lite-oss-db

v1.1.1

Published

Lightweight SQLite database with real-time OSS sync

Readme

lite-oss-db

轻量级 SQLite 数据库,实时同步到阿里云 OSS(或自定义对象存储)。

License: ISC TypeScript

特性

  • 🔄 双模式架构 — 读写模式(单写者 + 自动同步)与只读模式(轮询 + 自动刷新)
  • ☁️ 实时备份 — 本地 SQLite 文件变化后自动同步快照到 OSS(防抖机制)
  • 🔒 分布式锁 — 单写多读并发模型,基于 OSS 文件锁实现
  • 🛡️ 自我修复 — 运行时本地文件意外删除时,自动从内存恢复并重建文件
  • 🏷️ ETag 变化检测 — 精确识别远程变化,避免不必要的下载
  • 🔐 可选加密 — AES-256-CTR + HMAC-SHA256,流式加解密,低内存占用
  • 📦 定时备份 — 可配置的定时备份到独立前缀,支持自动清理旧备份
  • 🚀 零核心依赖 — 仅一个运行时依赖 (chokidar),核心逻辑全部基于 Node.js 原生 API
  • 🔌 运行时无关 — 通过适配器模式支持 better-sqlite3(Node.js)和 bun:sqlite(Bun)

目录


安装

lite-oss-db 的核心不直接依赖任何 SQLite 驱动或 OSS 客户端。你需要根据使用环境安装对应的 peer dependencies。

Node.js 环境

pnpm add lite-oss-db ali-oss better-sqlite3
# 或
npm install lite-oss-db ali-oss better-sqlite3

Bun 环境

bun add lite-oss-db ali-oss
# bun:sqlite 是 Bun 内置模块,无需额外安装

快速开始

Node.js 环境(better-sqlite3)

import { OssDb, AliyunOssAdapter, BetterSqlite3Adapter } from 'lite-oss-db';
import OSS from 'ali-oss';
import Database from 'better-sqlite3';

// 1. 创建 OSS 客户端
const client = new OSS({
  region: 'oss-cn-hangzhou',
  accessKeyId: process.env.ALIYUN_ACCESS_KEY_ID!,
  accessKeySecret: process.env.ALIYUN_ACCESS_KEY_SECRET!,
  bucket: 'my-app-db-bucket',
});

// 2. 创建 OssDb 实例
const db = new OssDb({
  dbPath: './local.db',
  adapter: new AliyunOssAdapter(client),
  remotePath: 'prod/app.sqlite',
  dbFactory: BetterSqlite3Adapter.createFactory(Database),
  syncDebounceMs: 2000,
  lockTtlMs: 10000,
});

// 3. 初始化(自动从 OSS 恢复 + 获取锁)
await db.init();

// 4. 像普通 SQLite 一样使用
db.db.exec('CREATE TABLE IF NOT EXISTS users (id INTEGER PRIMARY KEY, name TEXT)');
db.db.prepare('INSERT INTO users (name) VALUES (?)').run('Alice');

// 查询
const users = db.db.prepare('SELECT * FROM users').all();
console.log(users);

// 5. 关闭(自动最后一次同步 + 释放锁)
await db.close();

Bun 环境(bun:sqlite)

BunSqliteAdapter 是内置的 preset,与 BetterSqlite3Adapter 使用方式一致。

import { OssDb, AliyunOssAdapter, BunSqliteAdapter } from 'lite-oss-db';
import { Database } from 'bun:sqlite';
import OSS from 'ali-oss';

// 1. 创建 OSS 客户端
const client = new OSS({
  region: 'oss-cn-hangzhou',
  accessKeyId: process.env.ALIYUN_ACCESS_KEY_ID!,
  accessKeySecret: process.env.ALIYUN_ACCESS_KEY_SECRET!,
  bucket: 'my-app-db-bucket',
});

// 2. 创建 OssDb 实例(注意 dbFactory 使用 BunSqliteAdapter)
const db = new OssDb({
  dbPath: './local.db',
  adapter: new AliyunOssAdapter(client),
  remotePath: 'prod/app.sqlite',
  dbFactory: BunSqliteAdapter.createFactory(Database),
  syncDebounceMs: 2000,
  lockTtlMs: 10000,
});

// 3. 初始化
await db.init();

// 4. 使用方式与 Node.js 完全相同
db.db.exec('CREATE TABLE IF NOT EXISTS users (id INTEGER PRIMARY KEY, name TEXT)');
db.db.prepare('INSERT INTO users (name) VALUES (?)').run('Alice');
const users = db.db.prepare('SELECT * FROM users').all();
console.log(users);

// 5. 关闭
await db.close();

Node.js vs Bun 区别总结: 唯一的区别是 dbFactory 参数:

  • Node.js: BetterSqlite3Adapter.createFactory(Database)Database 来自 better-sqlite3
  • Bun: BunSqliteAdapter.createFactory(Database)Database 来自 bun:sqlite

其余所有 API(initclosedb.execdb.prepare、事件等)完全一致。

只读模式(适用于两种环境)

只读模式适用于需要跟踪远程数据库变化的读副本场景(如 API 服务器、Dashboard、报表系统等)。

const replica = new OssDb({
  mode: 'readonly',
  dbPath: './local-replica.db',
  adapter: new AliyunOssAdapter(client),
  dbFactory: BetterSqlite3Adapter.createFactory(Database), // 或 BunSqliteAdapter.createFactory(Database)
  poll: {
    intervalMs: 10000,   // 每 10 秒检查远程更新
    autoReload: true,    // 检测到变化后自动刷新数据库
  },
});

await replica.init();

// 方式一:监听事件,在数据更新时执行操作
replica.on('data:updated', () => {
  console.log('数据已刷新!');
  const users = replica.db.prepare('SELECT * FROM users').all();
  console.log('最新数据:', users);
});

// 方式二:手动检查更新
const changed = await replica.checkForUpdates();
if (changed) {
  console.log('有新数据');
}

// 方式三:检查数据库是否就绪(rehydration 期间返回 false)
if (replica.isReady) {
  const count = replica.db.prepare('SELECT COUNT(*) as c FROM users').get();
  console.log(count);
}

await replica.close();

配置参考

OssDbConfig — 主配置

创建 OssDb 实例时传入的配置对象。

必填参数

| 参数 | 类型 | 说明 | |:---|:---|:---| | dbPath | string | 本地 SQLite 数据库文件路径。如 './data/app.db' | | adapter | StorageAdapter | 存储适配器实例。内置 AliyunOssAdapterMockAdapter,也可自定义 | | dbFactory | DatabaseFactory | 数据库工厂函数。传入路径和选项返回 IDatabase 实例 |

DatabaseFactory 签名: (path: string, options?: { readonly?: boolean }) => IDatabase

模式选项

| 参数 | 类型 | 默认值 | 说明 | |:---|:---|:---|:---| | mode | 'readwrite' \| 'readonly' | 'readwrite' | 工作模式。readwrite 获取锁并同步写入;readonly 以只读方式打开,可配合轮询 | | remotePath | string | 'db.sqlite' | 远程存储中的对象路径(key)。锁文件自动派生为 {remotePath}.lock |

读写模式选项(mode: 'readwrite'

| 参数 | 类型 | 默认值 | 说明 | |:---|:---|:---|:---| | workerId | string | randomUUID() | 当前实例的唯一标识符,用于分布式锁。多实例部署时建议显式设置 | | lockTtlMs | number | 10000 | 分布式锁的 TTL(毫秒)。锁每 lockTtlMs / 3 自动续期一次 | | syncDebounceMs | number | 2000 | 文件变化后的同步防抖时间(毫秒)。在此时间内的连续写入会合并为一次同步 | | maxWaitMs | number | 10000 | 最大等待时间(毫秒)。即使文件持续变化,到达此时间也会强制同步,防止无限延迟 | | scheduledBackup | ScheduledBackupConfig | undefined | 定时备份配置。如果设置,将按间隔自动创建带时间戳的备份 |

只读模式选项(mode: 'readonly'

| 参数 | 类型 | 默认值 | 说明 | |:---|:---|:---|:---| | poll | PollConfig | undefined | 轮询配置。如果设置(即使为空对象 {}),启用远程变化检测 |

通用选项

| 参数 | 类型 | 默认值 | 说明 | |:---|:---|:---|:---| | logger | Logger \| false | 内置 console logger | 自定义日志实例,或传 false 完全禁用日志输出 | | encryption | EncryptionConfig | undefined | 加密配置。如果设置,上传到 OSS 的数据会被 AES-256-CTR 加密 |


PollConfig — 轮询配置

配置只读模式下的远程变化轮询行为。

| 参数 | 类型 | 默认值 | 说明 | |:---|:---|:---|:---| | intervalMs | number | 30000 | 检查远程更新的间隔(毫秒) | | autoReload | boolean | true | 检测到远程变化时是否自动重新加载数据库。设为 false 时仅发出 poll:check 事件 |

// 示例:每 5 秒检查一次,自动刷新
poll: {
  intervalMs: 5000,
  autoReload: true,
}

// 示例:每 60 秒检查一次,手动处理更新
poll: {
  intervalMs: 60000,
  autoReload: false,
}

ScheduledBackupConfig — 定时备份配置

配置读写模式下的自动定时备份。备份使用 ISO 时间戳命名,存储在指定前缀下。

| 参数 | 类型 | 默认值 | 说明 | |:---|:---|:---|:---| | intervalMs | number | 必填 | 备份间隔(毫秒)。如 3600000 表示每小时备份一次 | | prefix | string | 必填 | 远程存储的路径前缀。备份文件存储为 {prefix}/{timestamp}.db | | maxBackups | number | undefined(不限制) | 保留的最大备份数。超出后自动删除最旧的备份 |

// 示例:每小时备份一次,最多保留 24 个
scheduledBackup: {
  intervalMs: 3600000,         // 1 小时
  prefix: 'backups/prod',     // 存储路径: backups/prod/2026-02-17T06-00-00-000Z.db
  maxBackups: 24,              // 保留最近 24 个
}

EncryptionConfig — 加密配置

配置上传到 OSS 前的数据库文件加密。使用 AES-256-CTR + HMAC-SHA256。

| 参数 | 类型 | 默认值 | 说明 | |:---|:---|:---|:---| | key | string \| Buffer | 必填 | 加密密码(字符串)或 32 字节原始密钥(Buffer) | | isRawKey | boolean | false | 是否直接使用 key 作为加密密钥而不进行 PBKDF2 派生。如果 key 是 32 字节 Buffer 且已满足安全要求,设为 true 可跳过派生步骤提升性能 |

// 方式一:使用密码(自动 PBKDF2 派生,100,000 次迭代)
encryption: {
  key: 'my-secret-password-here',
}

// 方式二:使用原始 32 字节密钥(跳过派生)
encryption: {
  key: crypto.randomBytes(32),
  isRawKey: true,
}

注意事项:

  • 本地 .db 文件始终是未加密的,方便直接使用标准 SQLite 工具访问
  • 仅上传到 OSS 的数据是加密的
  • 传输安全依赖 HTTPS(阿里云 OSS 默认 HTTPS)
  • 加密和解密都使用流式处理(EncryptTransform/DecryptTransform),大文件不会撑爆内存

Logger — 日志接口

你可以传入自定义日志器,或使用内置的 console 日志器。

interface Logger {
  debug(...args: any[]): void;
  info(...args: any[]): void;
  warn(...args: any[]): void;
  error(...args: any[]): void;
}
// 使用内置日志(默认)
const db = new OssDb({ ... });

// 禁用日志
const db = new OssDb({ ..., logger: false });

// 自定义日志(如 pino, winston)
import pino from 'pino';
const db = new OssDb({
  ...,
  logger: pino({ level: 'info' }),
});

API 参考

属性

| 属性 | 类型 | 说明 | |:---|:---|:---| | db | IDatabase | 底层数据库实例。在读写模式下可读写,只读模式下仅可读 | | mode | 'readwrite' \| 'readonly' | 当前工作模式 | | isWriteMode | boolean | 是否处于写入模式(仅读写模式下获取到锁时为 true) | | isReady | boolean | 数据库是否就绪(只读模式 rehydration 期间返回 false) |

方法

init(): Promise<void>

初始化数据库。执行以下步骤:

  • 两种模式共有:从远程存储恢复数据库(hydration),使用 ETag 检测是否需要下载
  • 读写模式:尝试获取分布式锁 → 成功则以读写模式打开,失败则降级为只读 → 启动文件变化监听和自动同步
  • 只读模式:以只读模式打开 → 如果配置了 poll,启动轮询
await db.init();

close(): Promise<void>

关闭数据库。执行以下步骤:

  • 读写模式:执行最后一次同步 → 释放锁 → 停止文件监听 → 关闭数据库
  • 只读模式:停止轮询 → 关闭数据库
await db.close();

createBackup(name?, prefix?): Promise<string>

手动创建备份并上传到远程存储。使用 SQLite 的 Online Backup API 保证数据一致性。

| 参数 | 类型 | 默认值 | 说明 | |:---|:---|:---|:---| | name | string | ISO 时间戳 | 备份名称(不含 .db 后缀) | | prefix | string | scheduledBackup.prefix || 'backups' | 远程路径前缀 |

const path = await db.createBackup('before-migration', 'backups/v2');
console.log(`备份已创建: ${path}`); // 'backups/v2/before-migration.db'

listBackups(prefix?): Promise<Array<{ name, path, createdAt }>>

列出远程存储中的备份文件,按时间倒序排列。

| 参数 | 类型 | 默认值 | 说明 | |:---|:---|:---|:---| | prefix | string | scheduledBackup.prefix || 'backups' | 远程路径前缀 |

const backups = await db.listBackups();
// [{ name: '2026-02-17T06-00-00', path: 'backups/...', createdAt: Date }]

checkForUpdates(): Promise<boolean>

仅只读模式。手动检查远程是否有更新,如果有则自动 rehydrate。

const changed = await db.checkForUpdates();
if (changed) {
  console.log('数据已更新');
}

on(event, listener): this

注册事件监听器。类型安全。

db.on('sync:complete', () => console.log('同步完成'));
db.on('sync:error', (err) => console.error('同步失败:', err));
db.on('data:updated', () => console.log('只读副本已刷新'));

事件

同步事件(仅读写模式)

| 事件 | 回调签名 | 触发时机 | |:---|:---|:---| | sync:start | () => void | 开始将本地快照同步到远程 | | sync:complete | () => void | 同步成功完成 | | sync:error | (error: Error) => void | 同步失败 |

锁事件(仅读写模式)

| 事件 | 回调签名 | 触发时机 | |:---|:---|:---| | lock:acquired | () => void | 成功获取分布式锁 | | lock:lost | () => void | 锁续期失败,丧失写入权限 | | lock:renewed | () => void | 锁续期成功 |

恢复事件(两种模式)

| 事件 | 回调签名 | 触发时机 | |:---|:---|:---| | hydration:start | () => void | 开始从远程下载数据库 | | hydration:complete | () => void | 恢复完成 | | hydration:skipped | () => void | 本地已是最新(ETag 匹配),跳过下载 | | hydration:error | (error: Error) => void | 恢复失败 |

轮询事件(仅只读模式)

| 事件 | 回调签名 | 触发时机 | |:---|:---|:---| | poll:check | () => void | 开始一次远程检查 | | poll:no-change | () => void | 检查结果:远程无变化 | | poll:error | (error: Error) => void | 检查失败 |

数据事件(仅只读模式)

| 事件 | 回调签名 | 触发时机 | |:---|:---|:---| | data:updated | () => void | 数据库已从远程刷新完成 |


架构说明

lite-oss-db 将对象存储(OSS)视为 "单一数据源"(Source of Truth)

读写模式流程

init()
  ├─ hydrate: 检查 OSS → ETag 对比 → 下载/跳过
  ├─ acquireLock: 原子创建 {remotePath}.lock
  │   ├─ 成功 → Writer 模式
  │   │   ├─ 打开 DB (readwrite)
  │   │   ├─ 启动文件监听 (chokidar)
  │   │   ├─ 启动锁续期 (每 lockTtlMs/3)
  │   │   └─ [可选] 启动定时备份
  │   └─ 失败 → 降级为 Reader(只读打开)
  └─ ready

文件变化 → debounce(syncDebounceMs) → maxWait(maxWaitMs) → snapshot()
  ├─ SQLite backup API → .backup 文件
  ├─ [可选] 加密
  ├─ 上传到 OSS
  └─ 清理 .backup 文件

close()
  ├─ 最后一次 snapshot()
  ├─ 释放锁 (delete .lock)
  ├─ 停止文件监听
  └─ 关闭 DB

只读模式流程

init()
  ├─ hydrate: 检查 OSS → ETag 对比 → 下载/跳过
  ├─ 打开 DB (readonly)
  └─ [可选] 启动 PollManager

PollManager (每 intervalMs):
  ├─ head(remotePath) → 获取 ETag
  ├─ ETag 相同 → emit('poll:no-change')
  └─ ETag 不同 → rehydrate()
      ├─ 关闭 DB
      ├─ 重新下载
      ├─ 重新打开 DB (readonly)
      └─ emit('data:updated')

close()
  ├─ 停止 PollManager
  └─ 关闭 DB

并发与分布式锁

为防止多实例同时写入导致数据冲突,lite-oss-db 使用基于 OSS 文件的 单写者租约(Single Writer Lease)

  1. 锁文件{remotePath}.lock — JSON 文件,包含 workerIdexpiresAt
  2. 获取锁init() 时原子创建锁文件(forbidOverwrite: true
    • 成功 → Writer 模式,以读写方式打开数据库
    • 失败 → Reader 模式,以只读方式打开数据库
  3. 锁续期:Writer 每 lockTtlMs / 3 毫秒更新 expiresAt,包含重试逻辑
  4. 过期锁窃取:当检测到锁已过期时,使用 delete + 原子 create 模式安全窃取,避免竞态条件
  5. 锁释放close() 时删除锁文件

⚠️ 注意: 此模型不支持多主写入。同一时刻只能有一个写入者。


自我修复机制

lite-oss-db 使用 chokidar 监控本地数据库文件(包括 -wal 文件)。

当本地数据库文件被意外删除时:

  1. 检测到 unlink 事件
  2. 立即从内存中的数据库句柄创建快照(句柄仍然有效)
  3. 上传快照到 OSS 确保数据安全
  4. 从快照恢复本地文件,应用继续运行

💡 虽然自我修复可以恢复数据,但仍建议在修复后重启应用以确保文件描述符干净。


加密方案

| 层面 | 状态 | |:---|:---| | OSS 存储(静态) | ✅ AES-256-CTR + HMAC-SHA256 加密 | | 传输中 | ✅ HTTPS(阿里云 OSS 默认) | | 本地文件 | ❌ 未加密(便于使用标准 SQLite 工具) |

加密格式(二进制布局):

[version: 1B] [salt: 16B] [iv: 16B] [ciphertext: *B] [hmac: 32B]
  • 密钥派生:PBKDF2(SHA-256, 100,000 次迭代)— 从密码字符串派生 32 字节密钥
  • 流式处理:加密和解密均使用 Node.js Transform Stream,内存占用恒定,不受文件大小影响
  • 完整性验证:HMAC-SHA256 覆盖 salt + iv + ciphertext,防篡改

自定义适配器

StorageAdapter 接口

要支持其他对象存储(如 AWS S3、MinIO、腾讯 COS 等),实现此接口即可。

interface StorageAdapter {
  /** 上传数据 */
  put(path: string, buffer: Buffer, options?: PutOptions): Promise<void>;

  /** 上传流 */
  putStream(path: string, stream: any, options?: PutOptions): Promise<void>;

  /** 下载数据,不存在返回 null */
  get(path: string): Promise<Buffer | null>;

  /** 获取元数据(ETag, size, updatedAt),不存在返回 null */
  head(path: string): Promise<ObjectMetadata | null>;

  /** 删除对象 */
  delete(path: string): Promise<void>;

  /** 按前缀列出对象 */
  list(prefix: string): Promise<ObjectInfo[]>;
}

interface PutOptions {
  /** 如果为 true,对象已存在时操作失败(原子创建) */
  forbidOverwrite?: boolean;
}

interface ObjectMetadata {
  size: number;
  etag: string;
  updatedAt: Date;
}

interface ObjectInfo {
  name: string;
  updatedAt: Date;
}

内置的 AliyunOssAdapter 使用了一个最小化的 AliOssClient 接口,避免对 ali-oss 包的硬依赖:

import { AliyunOssAdapter } from 'lite-oss-db';
import type { AliOssClient } from 'lite-oss-db';

// 任何满足 AliOssClient 接口的对象都可以传入
const adapter = new AliyunOssAdapter(ossClient);

IDatabase 接口

要支持其他 SQLite 驱动,实现此接口即可。

interface IDatabase {
  exec(sql: string): unknown;
  prepare(sql: string): {
    run(...args: any[]): unknown;
    get(...args: any[]): unknown;
    all(...args: any[]): unknown[];
  };
  /** @deprecated 推荐使用 backup(),serialize() 会将整个数据库加载到内存 */
  serialize(): Buffer;
  backup(destination: string): Promise<void>;
  pragma(pragma: string, options?: { simple?: boolean }): unknown;
  close(): void;
  readonly: boolean;
}

type DatabaseFactory = (path: string, options?: { readonly?: boolean }) => IDatabase;

内置了两个数据库适配器,分别对应 Node.js 和 Bun:

// Node.js — better-sqlite3
import { BetterSqlite3Adapter } from 'lite-oss-db';
import Database from 'better-sqlite3';
const factory = BetterSqlite3Adapter.createFactory(Database);

// Bun — bun:sqlite
import { BunSqliteAdapter } from 'lite-oss-db';
import { Database } from 'bun:sqlite';
const factory = BunSqliteAdapter.createFactory(Database);

完整配置示例

import { OssDb, AliyunOssAdapter, BetterSqlite3Adapter } from 'lite-oss-db';
import OSS from 'ali-oss';
import Database from 'better-sqlite3';

// 读写模式 + 加密 + 定时备份(全量配置)
const db = new OssDb({
  // === 必填 ===
  dbPath: './data/app.db',
  adapter: new AliyunOssAdapter(new OSS({
    region: 'oss-cn-hangzhou',
    accessKeyId: process.env.AK!,
    accessKeySecret: process.env.SK!,
    bucket: 'my-bucket',
  })),
  dbFactory: BetterSqlite3Adapter.createFactory(Database),

  // === 模式 ===
  mode: 'readwrite',          // 'readwrite' | 'readonly'
  remotePath: 'prod/app.db',  // OSS 中的对象路径

  // === 读写模式选项 ===
  workerId: 'server-1',       // 分布式锁标识
  lockTtlMs: 15000,           // 锁 TTL 15 秒
  syncDebounceMs: 3000,       // 防抖 3 秒
  maxWaitMs: 15000,           // 最大等待 15 秒

  // === 定时备份 ===
  scheduledBackup: {
    intervalMs: 3600000,       // 每小时
    prefix: 'backups/prod',
    maxBackups: 48,            // 保留 48 个
  },

  // === 加密 ===
  encryption: {
    key: process.env.DB_SECRET!,
  },

  // === 日志 ===
  logger: false,               // 禁用日志
});

开发

# 克隆仓库
git clone <repo-url> && cd lite-oss-db

# 安装依赖
pnpm install

# 运行测试(使用内存 MockAdapter,无需云凭据)
pnpm test

# 类型检查
npx tsc --noEmit

# 运行示例
npx tsx examples/demo.ts

# 构建
pnpm build

License

ISC