lines-db

中文文档
0.19.0发布于 2026/11/04MITTypeScriptES Module构建来源已验证

使用SQLite将JSONL文件视为表的数据库实现

中文使用文档
文档目录29 节
  1. 安装
  2. CLI使用
  3. 定义模式
  4. 验证json文件
  5. 迁移数据
  6. 填充缺失的值
  7. 在浏览器中浏览和编辑
  8. 使用打印稿
  9. 生成类型
  10. 加载数据
  11. 使用生成的类型
  12. 错误处理
  13. API
  14. 查询
  15. 变化
  16. 批处理操作
  17. 事务和模式
  18. 查询条件
  19. JSON列
  20. 模式转换
  21. 交易
  22. 加载后更改的文件
  23. 模式填充的值
  24. 回写选定字段
  25. 编辑没有数据库的行
  26. 配置
  27. 数据集
  28. 类型映射
  29. 许可证

lines-db

日本語

lines-db将JSONL文件加载到SQLite表中,并将更改写回它们的源文件。将它用于保存在文件中的种子数据和测试装置。

列是从行推断出来的。用标准模式库(如Valibot或Zod)定义一个模式,以验证行并生成TypeScript类型。对象和数组存储为JSON列。模式可以在加载时转换值,并在保存时转换回来。

CLI验证、迁移、填充缺失的值,并生成类型。安装 @lines-db/studio 在浏览器中编辑数据,或使用 VS Code扩展 在编辑器中进行验证。

安装

要求Node.js 22.18.0或更高版本。

npm install lines-db

对于pnpm,使用 pnpm add lines-db。

CLI使用

定义模式

在JSONL文件旁边放置一个模式文件,使用相同的表名。

data/
  ├── users.jsonl
  ├── users.schema.ts
  ├── products.jsonl
  └── products.schema.ts

以下 users.schema.ts 使用Valibot。加上 npm install valibot。

import * as v from 'valibot';
import { defineSchema } from 'lines-db';

export const schema = defineSchema(
  v.object({
    id: v.pipe(v.number(), v.integer(), v.minValue(1)),
    name: v.pipe(v.string(), v.minLength(1)),
    age: v.pipe(v.number(), v.integer(), v.minValue(0), v.maxValue(150)),
    email: v.pipe(v.string(), v.email()),
  }),
);
export default schema;

任何库实现 标准模式 可以使用。

initialize() 如果无法导入架构文件或未将标准架构导出为,则失败 schema 或 default. 错误包括文件和错误原因。

验证json文件

根据模式验证一个文件或目录中的所有json文件。

npx lines-db validate <path>
npx lines-db validate ./data

npx lines-db validate ./data/users.jsonl

npx lines-db validate ./data --verbose

该命令加载相应的模式文件并检查每一行。验证错误包括失败的文件和详细信息。

验证命名数据集

中定义共享数据和场景数据 lines-db.config.json:

{
  "schemaDir": "./data",
  "base": ["./data"],
  "datasets": {
    "cogs": ["./data/cogs"],
    "sales": ["./data/sales"]
  }
}

省略要读取的位置路径 lines-db.config.json 在当前工作目录中,并仅进行验证 base。 --dataset <name> 将命名的数据集添加到 base;重复该标志,以指定的顺序组合多个集合。 --all-datasets 结合 base 每个数据集都是一个 验证运行;不同场景共享的id会导致重复错误。

npx lines-db validate
npx lines-db validate --dataset cogs
npx lines-db validate --dataset cogs --dataset sales
npx lines-db validate --all-datasets
npx lines-db validate --config ./seed/lines-db.config.json --dataset cogs

使用 --config <path> 选择其他配置文件。 schemaDir, base 和 datasets 是必需的。 base 并且每个数据集必须包含至少一个目录;使用 datasets: {} 当没有额外的集合时。 相对路径是从配置文件的目录中解析的。目录以base-then-dataset格式加载 顺序,每个目录只加载一次。不会自动发现子目录。模式,独特的 在合并的数据中检查约束和外键,错误识别原始文件。

结合 --dataset 与 --all-datasets,或使用带有位置路径的配置选项是错误的。 未知的数据集名称和无效的配置也会失败。现有的 validate <path> 命令继续工作 不读取配置文件。只有 validate CLI命令使用该配置。

程序可以加载相同的配置 loadDatasetConfig():

import { LinesDB, loadDatasetConfig } from 'lines-db';

const config = await loadDatasetConfig('./lines-db.config.json', {
  datasets: ['cogs', 'sales'],
});
const db = LinesDB.create(config);

省略 datasets 或通过 [] 只针对碱。数组按数组顺序将基本集合和选定集合组合在一起; 'all' 组合基础和每组在配置顺序。每个目录加载一次,包括符号链接别名。 调用 loadDatasetConfig() 不带参数读取 lines-db.config.json 在当前工作目录中。 它返回 Promise<DatabaseConfig> 拒绝不可读或无效的配置、未知的集合名称和缺失 选定的数据目录。一个失踪的 schemaDir 或者指向文件的也会被拒绝。呼叫 db.initialize() 加载并验证数据。

迁移数据

对每一行应用一个函数,验证结果,并将其保存到JSONL文件中。

npx lines-db migrate <file> <transform> [options]
npx lines-db migrate ./data/users.jsonl "(row) => ({ ...row, age: row.age + 1 })"

npx lines-db migrate ./data/users.jsonl "(row) => ({ ...row, active: true })" --filter "{ age: (age) => age > 18 }"

npx lines-db migrate ./data/users.jsonl "(row) => ({ ...row, age: row.age + 1 })" --errorOutput ./migrated.jsonl

npx lines-db migrate ./data "(row) => row" --fields id

可用的选项:

  • --filter, -f <expr>:选择带有过滤表达式的行。
  • --fields <list>:只回写以逗号分隔的字段。其他值仍然存储在JSONL文件中。如果省略,则写入每个字段。
  • --errorOutput, -e <path>:如果迁移失败,将转换后的数据保存到此路径。
  • --verbose, -v:显示详细的错误信息。

迁移在事务中运行,并在提交之前验证所有转换后的行。

回写选定字段

正常的迁移将整行写回文件,包括模式默认值和省略的字段。使用 --fields 保持其他价值不变。

npx lines-db migrate ./data/users.jsonl "(row) => row" --fields id

行按主键匹配,或者在文件还没有主键时按位置匹配,比如在填充主键时。保留现有的行顺序,并追加新行。如果文件没有主键,并且行数已经更改,则迁移失败,因为无法匹配行。

--fields 适用于所有目标表。没有命名字段的表不会被回写,因此一个列表可以涵盖具有不同列的表。如果每个表中都没有字段,则会导致错误。

填充缺失的值

npx lines-db fill <path...> [--schema-dir ./schemas] [--fields id,createdAt]

传递一个或多个数据目录或json文件。使用 --schema-dir 对于共享模式;否则,模式将加载到每个JSONL文件旁边。该命令用验证模式提供的值填充缺失的字段。没有 --fields,它填充每个表的主键。

现有的值永远不会被替换。没有获得值的行保持不变,包括行尾。更新后的行使用模式的键顺序。不写入验证失败的行。在写入任何文件之前计算所有值。如果另一个工具在读取文件后更改了文件,则该命令不写入任何内容,并以一个 JsonlConflictError 命名该文件。

使用存储在文件中的字段名。当一个 backward 转换重命名主键,默认目标是数据库的主键名;传递文件的字段名 --fields。

fillFields() 接受一个函数来计算值。本例使用 hook 由模式文件导出。如果函数抛出,则不写入任何内容。

import { fillFields, unwrap } from 'lines-db';

const { filled } = unwrap(
  await fillFields({
    path: './data',
    fields: ['id'],
    loadFiller: (schemaModule, { schemaPath }) => {
      if (typeof schemaModule.hook !== 'function') throw new Error(`${schemaPath} does not export \`hook\``);
      return schemaModule.hook;
    },
  }),
);

结果的 tablesWithoutSchema 列出没有模式的表。 unreadableLines 列出非JSON对象的行,以及 unproducedFields 列出没有表生成值的字段。

fillFields() 的字符串或非空的只读字符串数组 path. 在所有路径上,计算每个值,并在写入之前检查每个挂起的写入是否有外部更改。如果写入失败,则恢复已写入的文件。重叠路径只处理每个文件一次。不可读的行仍然被报告并且保持不变。

unwrap(
  await fillFields({
    path: ['./data/dataset-a', './data/dataset-b'],
    schemaDir: './data',
  }),
);

在浏览器中浏览和编辑

在项目中安装Studio并使用CLI启动它。

npm install -D @lines-db/studio
npx lines-db studio ./data --open

默认URL为 http://127.0.0.1:4848. 保存时将验证更改并将其写入json文件。看到 工作室的文档 用于编辑、共享模式和多个数据目录。

使用打印稿

生成类型

从模式生成数据库配置和类型。默认输出为 <dataDir>/db.ts。

npx lines-db generate <dataDir>
npx lines-db generate ./data

来运行这些命令 package.json:

{
  "scripts": {
    "db:validate": "lines-db validate ./data",
    "db:generate": "lines-db generate ./data"
  }
}

加载数据

创建 data/users.jsonl。

{"id":1,"name":"Alice","age":30,"email":"[email protected]"}
{"id":2,"name":"Bob","age":25,"email":"[email protected]"}
{"id":3,"name":"Charlie","age":35,"email":"[email protected]"}

从TypeScript中加载目录。

import { LinesDB, unwrap } from 'lines-db';

const db = LinesDB.create({ dataDir: './data' });
unwrap(await db.initialize());

const users = unwrap(db.find('users'));
console.log(users); // [{ id: 1, name: "Alice", ... }, ...]

const user = unwrap(db.findOne('users', { id: 1 }));
console.log(user); // { id: 1, name: "Alice", age: 30, ... }

const adults = unwrap(db.find('users', { age: (age) => age >= 30 }));

unwrap(await db.close());

unwrap() 返回成功对象的值 Result 或者抛出错误。看到 错误处理 用于显式处理故障。

使用生成的类型

生成的配置从表名推断行类型。

import { LinesDB, unwrap } from 'lines-db';
import { config } from './data/db.js';

const db = LinesDB.create(config);
unwrap(await db.initialize());

const users = unwrap(db.find('users'));

unwrap(
  db.insert('users', {
    id: 10,
    name: 'Alice',
    age: 30,
    email: '[email protected]',
  }),
);

unwrap(await db.close());

错误处理

方法在 LinesDB 和 JsonlReader 可以失败返回 Result<T, Error>. 一个成功的结果是 { ok: true, value };失败的结果是 { ok: false, error }。 getSchema, getTableNames, getDb 直接返回它们的值。

const result = db.insert('users', { id: 2, name: '' });

if (result.ok) {
  console.log(result.value.changes);
} else {
  console.log(result.error.message);
}

unwrap(result) 成功时返回值,失败时抛出错误。当故障应该停止当前操作时使用它。

import { unwrap } from 'lines-db';

const users = unwrap(db.find('users'));

在一个事务中, tx 也返回Results。一个失败的 insert() 或 update() 不会自行回滚事务。要在失败时中止,请使用 unwrap() 或者检查结果并抛出 throw result.error。

通过写 tx 在事务内部。写在 db 来自其他代码的请求将成为同一事务的一部分,因此它们在事务打开时被拒绝。SQL通过 db.getDb(), db.execute(), db.query() 在事务期间是只读的。 SELECT, VALUES, WITH, EXPLAIN,以及只读的pragma,例如 PRAGMA table_info(...) 被接受。运行设置值的事务控制或程序 tx。

API

下面的操作返回 Result<T, Error> 除非另有说明。 getSchema, getTableNames, getDb 直接返回它们的值。

查询

  • find(table, where?):查找所有匹配的记录。
  • findOne(table, where?)找一条记录。
  • query(sql, params?):运行原始SQL查询。

变化

  • insert(table, data):插入一条记录。
  • update(table, data, where):更新匹配记录。
  • delete(table, where):删除匹配的记录。

批处理操作

  • batchInsert(table, data[]):插入几条记录。
  • batchUpdate(table, updates[]):更新几条记录。
  • batchDelete(table, where):删除多条记录。

事务和模式

  • transaction(fn):在事务中运行操作。
  • sync(table?, options?):将更改写回json文件。
  • getSchema(table):获取模式。返回 TableSchema | undefined 直接。
  • validateRow(table, row):验证一行,包括由于验证错误而在加载时被排除的表中的一行。返回 Result<JsonObject, ValidationError>。
  • getTableNames():获取所有表名。返回 string[] 直接。

查询条件

unwrap(db.find('users', { age: 30 }));

unwrap(db.find('users', { age: 30, name: 'Alice' }));

unwrap(
  db.find('users', {
    age: (age) => age > 25,
    name: (name) => name.startsWith('A'),
  }),
);

JSON列

对象和数组作为JSON列存储,并作为原始值返回。

unwrap(
  db.insert('orders', {
    id: 1,
    items: [{ name: 'Laptop', quantity: 1 }],
    metadata: { source: 'web' },
  }),
);

const order = unwrap(db.findOne('orders', { id: 1 }));
console.log(order.items[0].name); // "Laptop"

JSON列保留插入时的任何值。每个值都存储为JSON文本,字符串加引号,所以字符串 "123" 返回为 "123",和数字 123 作为 123. 因此,在SQL中,字符串以其引号形式进行比较: WHERE data = '"abc"'。

模式转换

如果模式将日期字符串转换为 Date 对象加载时,定义 backward 转换,以便在保存时将它们转换为字符串。JSONL存储字符串;应用程序使用 Date 对象。

import * as v from 'valibot';
import { defineSchema } from 'lines-db';

const eventSchema = v.pipe(
  v.object({
    id: v.number(),
    date: v.pipe(
      v.string(),
      v.isoDate(),
      v.transform((str) => new Date(str)),
    ),
  }),
);

export const schema = defineSchema(eventSchema, {
  backward: (output) => ({
    ...output,
    date: output.date.toISOString().slice(0, 10),
  }),
});

将日期存储为字符串 events.jsonl。

{"id":1,"date":"2024-01-01"}

加载的行将日期公开为 Date。

const event = unwrap(db.findOne('events', { id: 1 }));
console.log(event.date instanceof Date); // true
console.log(event.date.getFullYear()); // 2024

交易

事务外部的更改会自动同步到JSONL文件。

unwrap(db.insert('users', { id: 10, name: 'Alice', age: 30 }));

Sync保留原始的行顺序并追加新行。

使用事务将多个更改保存在一起。

unwrap(
  await db.transaction(async (tx) => {
    unwrap(tx.insert('users', { id: 10, name: 'Alice', age: 30 }));
    unwrap(tx.update('users', { age: 31 }, { id: 1 }));
  }),
);

使用 unwrap() 在回调中抛出失败的操作并回滚事务。

回调完成后,在提交之前将更改的表写回。在第一次写入之前检查所有目标文件。如果回写失败,则回滚事务并恢复已写入的文件。如果恢复也失败,则该文件保留其更改。

未修改的表不回写。但是,通过更改行的SQL tx.execute() 或 tx.query() 回写每个表,因为无法识别已更改的表。调用 sync() 在回调内部返回一个错误;事务在最后处理回写。

加载后更改的文件

数据库在读取或写入每个JSONL文件时记录其内容。如果编辑器或其他工具在此之后更改或删除了该文件,则sync将使其保持不变,并返回 JsonlConflictError 命名文件。

const result = await db.transaction((tx) => unwrap(tx.update('users', { age: 31 }, { id: 1 })));

if (!result.ok && result.error.name === 'JsonlConflictError') {
  console.log(`${(result.error as JsonlConflictError).file} has changed on disk`);
}

要加载外部更改,请重新创建数据库,调用 initialize(),重试写操作。事务只回写已更改的表,并在发生冲突时回滚数据库更改。

与 sync() 或者在事务外自动同步,数据库更改在回写失败后仍然保留。一个 sync() 没有表名的目标是每个表,因此对任何文件的更改都会导致失败。自动同步通过报告错误 console.error。

文件在检查其内容和写入之间不会被锁定。在此期间由另一个进程保存的更改将被覆盖。这适用于一个人编辑本地文件;它不协调并发写器。

hasExternalChanges() 检测自上次读写以来JSONL和模式文件的更改、添加和删除。 findExternalChanges() 列出受影响的文件。使用它们检测其他工具所做的编辑,并在长时间运行的流程中重新加载数据。

if (unwrap(await db.hasExternalChanges())) {
  await db.close();
  db = LinesDB.create(config);
  unwrap(await db.initialize());
}

模式填充的值

普通同步写入整行,包括模式默认值和计算值。与 writeFilledValues: 'primaryKey',它写入文件中存在的字段,传递给 insert(),或被 update(). 其他默认值保持省略。生成的主键将被保存,以便该行在下一次加载时保持其标识。

const db = LinesDB.create({ dataDir: './data', writeFilledValues: 'primaryKey' });
// people.jsonl: {"id":1,"name":"Alice"}
unwrap(await db.transaction((tx) => unwrap(tx.update('people', { name: 'Alicia' }, { id: 1 }))));
// people.jsonl: {"id":1,"name":"Alicia"}

使用 sync(tableName, { writeFilledValues }) 覆盖一个同步的设置。将字段设置为其现有值不是更改,因此将默认字段设置为相同的值不会将其写入文件。中显式命名的字段 writeBackFields 或 sync(..., { fields }) 即使在模式提供它们时也会编写。

resetToDefault 恢复字段的模式默认值。与 writeFilledValues: 'primaryKey',该字段将从文件中删除。

unwrap(db.update('people', {}, { id: 1 }, { resetToDefault: ['age'] }));

findWithDefaults() 返回模式提供的每一行和字段的名称。使用它来区分默认值和存储在文件中的值。

unwrap(await db.findWithDefaults('people'));
// [{ row: { id: 1, name: 'Alicia', age: 20 }, defaulted: ['age'] }]

行,其 backward 转换重命名字段或SQL添加的没有原始行或写记录的行,因为不能标识显式提供的字段,所以会完整地写入。通过SQL修改的表也会回写所有行。迁移设置的字段 transform 明确提供的负载计数。

原始行的未知字段在回写时保留其位置。去掉未知键的模式,比如Valibot的模式 v.object(),允许这些行通过验证。使用 v.strictObject() 或者另一个拒绝未知键并将其报告为错误的模式。

回写选定字段

当指定字段时,即使模式提供了它们,也会写入它们的数据库值。其他值仍然存储在文件中。

// users.jsonl: {"name":"Alice"}
unwrap(await db.sync('users', { fields: ['id'] }));
// users.jsonl: {"id":"...","name":"Alice"}

集 writeBackFields 将规则应用于每个同步,包括插入、更新和事务之后的自动同步。

const db = LinesDB.create({ dataDir: './data', writeBackFields: ['id'] });

行按主键匹配,如果文件还没有主键,则按位置匹配。保留行顺序,并追加新行。所选字段使用数据库值,包括null。如果行不能匹配, sync() 返回一个失败的结果。

新字段按模式顺序插入。例如, id 首先声明的在稍后填充时插入到行开头。现有的键不会移动,而未知的键会留在声明的键之后。

键顺序是从模式生成的行中读取的。首先生成的字段被优先排序;在后面的行中首先遇到的字段紧随其后。集 mergeFields的 keyOrder 显式指定顺序。

一个 sync() 对于特定表,拒绝表中没有的字段。跨所有表和配置同步 writeBackFields 跳过没有这些字段的表,因此相同的列表适用于不同的列集。

编辑没有数据库的行

mergeFields 获取原始行和计算行,并返回仅更新了所选字段的行。使用它来编辑文件而不将它们加载到SQLite中,例如当钩子提供id时。

import { JsonlReader, JsonlWriter, mergeFields, unwrap } from 'lines-db';

const rows = unwrap(await JsonlReader.read('./data/users.jsonl'));
const filled = rows.map((row) => mergeFields(row, fillIds(row), { fields: ['id'] }));
await JsonlWriter.write('./data/users.jsonl', filled);

// {"name":"Alice"}  ->  {"id":"018f...","name":"Alice"}

选择之外的字段不被验证。即使它们的值在模式中失败,所选字段仍然可以更新。计算行中缺少的字段保留其原始值。如果计算的键顺序与模式顺序不同,则使用 keyOrder 设置新键的位置。

配置

interface DatabaseConfig {
  dataDir: string | readonly string[];
  schemaDir?: string;
  writeDataSets?: boolean;
  writeBackFields?: readonly string[];
}

const db = LinesDB.create({ dataDir: './data' });

数据集

从特定于场景的数据中拆分共享的种子数据,并一起加载所需的目录。

data/
  Item.schema.ts   Item.jsonl              # schemas and base data
  ItemValuation.schema.ts
  cogs/
    Item.jsonl                             # additional Item rows
    ItemValuation.jsonl                    # references Item rows from both sets
const db = LinesDB.create({ dataDir: ['./data', './data/cogs'], schemaDir: './data' });
const result = unwrap(await db.initialize({ detailedValidate: true }));

将同一表的行连接在 dataDir 秩序。在所有行中检查验证、唯一约束和外键。 cogs/ItemValuation.jsonl 可以从 data/Item.jsonl. 跨集合的重复id会导致约束冲突。错误标识源文件及其中的行。

多个目录需要 schemaDir 查找共享模式。它也可以与一个目录一起使用。如果省略单个目录,模式将在JSONL文件旁边查找。

默认情况下,具有多个目录的数据库是只读的。集 writeDataSets: true 将更新和删除写回其源文件。插入需要显式配置的目录。

const db = LinesDB.create({ dataDir: ['./data', './data/cogs'], schemaDir: './data', writeDataSets: true });
unwrap(await db.initialize());
unwrap(
  await db.transaction((tx) => {
    const inserted = tx.insert('Item', { id: 'new-item', name: 'New item' }, { dataDir: './data/cogs' });
    if (!inserted.ok) throw inserted.error;
  }),
);

使用 insert() 具有跨多个目录添加行的目标。 batchInsert() 没有目的地参数。SQL通过 execute(), query(), queryOne(), getDb() 保持只读以保留每行的源位置。

事务在写入前检查目标文件,如果稍后的写入失败,则恢复已写入的文件。工作室使 writeDataSets 自动。

类型映射

JSON类型列类型SQLite存储
数字(整数)整数整数
号(浮动)真正的真正的
字符串文本文本
布尔整数整数
对象JSON文本
数组JSON文本

许可证

麻省理工学院

版本与发布记录

lines-db 共发布 5 个版本,首次发布于 2026/10/13;过去 12 个月发布了 5 个版本。最新版本附带 npm provenance 构建来源证明,可追溯到公开的 CI 构建记录。

版本发布日期距今
0.19.0latest2026/11/041 天前
0.18.12026/10/269 天前
0.18.02026/10/1520 天前
0.15.02026/10/1323 天前
0.0.0-stage2026/10/1323 天前

关于 lines-db 的常见问题

lines-db 是什么?

使用SQLite将JSONL文件视为表的数据库实现

如何安装 lines-db?

在项目目录运行 npm install lines-db,或使用 pnpm add / yarn add / bun add。它还提供命令行工具 lines-db,可以用 npx lines-db 直接运行。

lines-db 支持 TypeScript 吗?

支持。包内自带 TypeScript 类型声明(package.json 声明了 types 入口),安装后即可获得类型提示,无需再安装 @types 包。

lines-db 支持 ESM 还是 CommonJS?

以 ES Module 形式发布,推荐使用 import 引入。在 CommonJS 项目中,Node.js 20.19+ / 22.12+ 可以直接 require() 不含顶层 await 的 ES Module,更早的版本需要改用动态 import()。

lines-db 需要什么版本的 Node.js?

package.json 的 engines 字段要求 Node.js >=22.18.0。版本不符时 npm 默认只给出警告,开启 engine-strict 后会拒绝安装。

lines-db 可以商用吗?

lines-db 使用 MIT 许可。宽松许可:允许商业使用、修改和再分发,通常只需在分发时保留原版权与许可声明。以上为概括,不构成法律意见,请以许可原文为准。

lines-db 还在维护吗?

最新版本 0.19.0 发布于 2026/11/04(1 天前);至今共发布 5 个版本,其中过去 12 个月发布了 5 个;首次发布于 2026/10/13。发布频率只是参考信号:功能稳定的工具可能很少需要更新,建议结合源码仓库的 issue 和提交记录判断。