UNPKG

@isdk/kvsqlite

Version:

[![npm version](https://img.shields.io/npm/v/@isdk/kvsqlite.svg)](https://www.npmjs.com/package/@isdk/kvsqlite) [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)

219 lines (160 loc) 14.3 kB
# ai-tool-sqlite SQlite(>=3.45.0) key/value 文档型NoSQL数据库 * 📐 **灵活数据建模**: 固定字段 + JSON 字段自动融合 * ⏱️ **自动时间戳**: 触发器自动维护 `createdAt` / `updatedAt` * 🔍 **全文搜索(FTS5)**: 支持中英文分词、布尔查询、排序 * 🧮 **高效索引**: 支持唯一索引、部分索引、JSON 索引 * 📥 **事务与批量操作**: 支持事务、批量插入 * 📄 **文档式 API**: 类似 MongoDB,易于上手 * 🧩 **插件系统**: 可加载 SQLite 插件 ## Getting started ### 灵活的数据建模能力 在传统的 SQLite 表结构中,字段是固定的,新增字段需要执行 `ALTER TABLE` 操作,维护成本较高。而使用 JSON 类型字段(如 JSON 或 JSONB)则可以将复杂结构以文档形式存储,但会牺牲部分查询性能和结构校验能力。本库通过 **混合使用固定字段和 JSON 字段**,实现两者的平衡。 * 使用 `val` 字段作为主 JSON/JSONB 存储字段。 * 固定字段(如 `_id`, `createdAt` 等)用于关键索引或常用查询字段。 * 插入或更新数据时,非固定字段的数据会自动合并到 `val` 字段中。 * 查询时,系统会自动将 `val` 字段中的属性映射为虚拟字段供查询使用。 ```ts import { KVSqlite } from '@isdk/kvsqlite' // this will create a new in-memory SQLite database and a default collection named 'kv'. const db = new KVSqlite(':memory:db_in_memory') // create the users collection in db const users = db.create('users', { fields: { email: { type: 'TEXT', unique: true }, createdAt: {type: 'DATETIME'}, updatedAt: {type: 'DATETIME'}, }, usingJsonb: true, // 是否启用 jsonb 存储 val 字段,默认为启用. }) // 插入数据 users.set('user1', { _id: 'user1', email: 'user1@example.com', name: 'Alice', age: 30, address: { city: 'Beijing', zip: '100000' } }); ``` 此时,`name`, `age`, `address` 等字段会被自动写入 `val` 字段(通常是 `JSONB`),而 `_id`, `email`, `createdAt`, `updatedAt` 是固定字段。 查询时自动映射: ```ts const user = users.get('user1'); console.log(user); // 输出: // { // _id: 'user1', // email: 'user1@example.com', // name: 'Alice', // age: 30, // address: { city: 'Beijing', zip: '100000' }, // createdAt: '2025-04-05T12:00:00Z' // updatedAt: '2025-04-05T12:00:00Z' // } const items = users.search({name: {$like: 'A%'}}) ``` 即使这些字段没有显式定义在表结构中,也能像普通字段一样被访问和操作。 #### 🎯 核心优势 | 优势 | 说明 | | :---- | :---- | | ✅ 结构灵活性 | 可动态添加任意字段,无需修改表结构 | | ✅ 查询兼容性 | 支持对 JSON 内部字段进行 SQL 查询 | | ✅ 性能优化空间大 | 常用字段仍可作为固定列索引,提升查询效率 | | ✅ 兼容 ORM 风格 | 开发者体验类似 MongoDB 文档模型,又保留了 SQL 的强类型特性 | ## KVSqlite Class KVSqlite类是对`better-sqlite3`库中Database类的扩展,专为在SQLite数据库中管理键值对而设计。它引入了一种结构化的方式来与这些键值对交互并进行操作,提供了创建、检索、更新和删除操作的方法。该类集成了多种功能和设计选择,以增强其功能性和灵活性: ### Interface and Type Definitions * `IKVObjItem`: 定义单个键值对对象的结构,必须包含一个类型为string的`_id`属性,并可包含任意类型的其他属性。 * `IKVSetOptions`: 扩展了better-sqlite3库中Database.Options接口,加入了针对KVSqlite类特有的选项,如id、location、collection、collections和overwrite。这些选项允许对数据库连接细节、集合管理及数据操作行为进行更细致的控制。 * `IKVCollections`: 表示类似字典的映射,其中键为集合名称(字符串),值为KVSqliteCollection类的实例。 ### Constructor * 接收两个参数:filename(可选,为字符串或Buffer类型)代表SQLite数据库文件路径,options(同样可选)需符合IKVSetOptions接口。 * 使用给定的filename和options调用Database构造函数初始化基础super实例。 * 如果提供store `id`选项,则基于此设置`id`属性。 * 若数据库未以只读模式打开(!this.readonly),则根据collections和collection选项创建一个或多个集合,确保始终存在名为'kv'的默认集合。 ### Collections Management * 维护一个内部`collections`属性,类型为`IKVCollections`,其中保存着对应于不同命名集合的已实例化的`KVSqliteCollection`对象引用。 * 提供一个create方法,对于给定的name创建一个新的KVSqliteCollection实例并将其存储在collections映射中,若该集合尚不存在。 ### Key-Value Pair Operations * 提供一系列CRUD(增删改查)方法,对指定集合内的键值对执行操作: * `set(obj: IKVObjItem, options?: IKVSetOptions)`:在由`options.collection`指定或默认的'kv'集合中插入或更新键值对(obj)。将操作委托给对应的`KVSqliteCollection.set()`方法。 * `get(_id: string, options?: IKVSetOptions)`: 从指定集合或默认集合中检索由_id标识的键值对。返回封装在`IKVObjItem`接口中的检索到的项。 * `del(_id?: string, options?: IKVSetOptions)`: 根据提供的_id从指定集合中删除键值对,若未提供_id则删除默认集合中的所有项。将操作委托给对应的`KVSqliteCollection.del()`方法。 * `isExists(_id: string, options?: IKVSetOptions)`: 检查指定集合或默认集合中是否存在由_id标识的键值对。返回一个布尔值表示存在与否。 * `count(query?: string, options?: IKVSetOptions)`: 统计指定集合或默认集合中键值对的数量,可选地根据LIKE查询应用过滤条件。将操作委托给对应的`KVSqliteCollection.count()`方法。 总之,KVSqlite类提供了一个全面且可定制的接口,用于管理存储在SQLite数据库中的键值对。它利用底层的better-sqlite3库,并通过引入自己的面向集合的API引入专门功能,使其适用于需要轻量级、持久化键值存储解决方案且具备高级查询能力的应用场景。 ## KVSqliteCollection Class `KVSqliteCollection` 类代表一个容器,用于在 KVSqlite 数据库系统中管理特定的键值对集合。此类封装了对集合执行各种操作所需的逻辑,包括添加、更新、检索、删除、检查存在性以及计数项目。以下是 KVSqliteCollection 类的关键方面: ### 内部状态与初始化 KVSqliteCollection 类主要维护以下内部状态: 1. Name: 通过构造函数接收并存储的字符串变量,代表该集合的唯一名称。这是与其他 KVSqliteCollection 实例区分的重要标识。 2. Database Reference: 作为构造函数的参数传递并存储的一个 KVSqlite 类实例引用,表示当前 KVSqliteCollection 所关联的数据库上下文。该引用使得 KVSqliteCollection 可以直接与底层数据库交互。 3. Prepared Statements: 初始化了一系列预编译的 SQL 语句,这些语句封装了针对键值对集合的基本操作,如添加、更新、检查存在性、获取、删除单个项、删除所有项以及计数项。每个预编译语句都作为一个类属性存储,便于后续高效执行。具体包括: * `preAdd`: 插入新的键值对。 * `preUpdate`: 更新已有键值对。 * `preExists`: 检查指定 `_id` 的键值对是否存在。 * `preGet`: 根据 `_id` 获取键值对的值。 * `preDel`: 删除指定 `_id` 的键值对。 * `preDelAll`: 删除集合内的所有键值对。 * `preCount`: 计算集合内键值对总数。 * `preCountW`: 使用 LIKE 查询条件计算符合条件的键值对总数。 在 KVSqliteCollection 类的构造函数中进行了如下初始化操作: 1. **Table Creation**: 如果关联的 `KVSqlite` 实例不是只读模式,构造函数会调用 `createTableSql` 函数生成创建表的 SQL 语句,并使用 `db.prepare().run()` 执行该语句,确保与当前集合同名的表已在数据库中创建且符合预期结构。这个表用于存储实际的键值对数据。 2. **Prepared Statements Initialization**: 构造函数接下来初始化所有的预编译 SQL 语句。每条语句都是根据集合名称动态构建的,确保其作用于正确的表。这些语句使用 db.prepare() 方法创建,并赋值给对应的类属性。这样,当需要执行诸如插入、更新、查询等操作时,可以直接调用预编译好的语句,提高了执行效率并减少了潜在的 SQL 注入风险。 `KVSqliteCollection` 类在其内部状态中持有一个唯一的集合名称、对关联数据库的引用,以及一组预编译的 SQL 语句。在构造函数中,它首先确保与集合同名的数据库表已创建,随后初始化所有预编译语句以便高效地执行后续的键值对操作。这样的设计使得 `KVSqliteCollection` 能够作为一个独立且高效的单元,管理特定键值对集合在 `KVSqlite` 数据库中的存储与操作。 ### 预编译语句 为了优化性能并减少重复解析开销, `KVSqliteCollection` 类在构造时声明并初始化一系列 SQLite 预编译语句。这些语句对应于管理键值对相关的常见数据库操作,如插入、更新、检查存在性、获取、删除单个项、删除所有项以及计数项。每个语句绑定到一个特定属性(如 `preAdd``preUpdate``preExists` 等)并使用提供的 `name` 和适当的 SQL 语法构建。预编译语句使在整个 `KVSqliteCollection` 实例生命周期内高效执行这些操作成为可能。 ### Key-Value Pair Operations On a Collection * **Setting Items**: `_set` 方法负责处理键值对的插入或更新。它接受一个 `IKVObjItem``obj`)和可选的 `IKVSetOptions``options`)。在内部,它从输入对象中提取 `_id`,临时移除它,然后检查集合中是否已存在具有相同 `_id` 的记录。如果存在,方法确定是否完全覆盖现有记录(`options.overwrite === true`)还是将新属性合并到现有记录中(`options.overwrite !== false`)。在这两种情况下,更新后的记录都被序列化为 JSON 并传递给相应的预编译语句(`preUpdate``preAdd`)以对数据库执行。 * **Getting Items**: `get` 方法通过 `_id` 获取单个键值对。它使用 `preGet` 预编译语句执行 SELECT 查询,反序列化返回的 JSON 值,将 `_id` 添加回结果,并返回重构的 `IKVObjItem`* **Deleting Items**: del 方法支持通过其 `_id` 删除特定键值对或在不提供 `_id` 时从集合中移除所有项。它相应地调用相应的预编译语句(`preDel``preDelAll`)。 * **检查存在性**: `isExists` 方法使用 `preExists` 预编译语句查询数据库,确定集合中是否存在具有给定 `_id` 的键值对。它返回布尔结果。 * **Counting Items**: `count` 方法允许计数集合中的项数量,可选地在提供时按 `LIKE` 查询进行过滤。它根据是否存在查询参数使用 `preCount``preCountW` 预编译语句。 ### 事务支持 虽然并未直接提及,但 set 方法将对 _set 的调用包装在父 KVSqlite 实例提供的事务中。这确保了插入/更新操作的原子性,防止在事务失败期间出现部分更新或不一致状态。 总之,KVSqliteCollection 类充当 KVSqlite 数据库系统中特定键值对集合的专用管理器。它通过预编译语句处理低级别的数据库交互,为键值对上的 CRUD 操作提供干净的 API,并通过事务支持确保数据一致性。通过在 KVSqlite 类中维护 KVSqliteCollection 实例的映射,可以在同一 SQLite 数据库中高效管理和访问多个集合。 ### 索引支持 支持创建普通索引(CREATE INDEX)和 JSON 索引(适用于 JSON 字段内的属性)。 * **createIndex(indexName: string, fields: string|string[])**: `createIndex` 方法允许为val对象创建索引。它使用 SQLite 的 `CREATE INDEX` 语句创建索引,并记录索引名称以备后续查询使用。 * 例如: `collection.createIndex('name', 'name')` * 使用 `EXPLAIN QUERY PLAN SELECT * FROM ...`检查是否使用索引。 * **search(query: string|JSONQuery, options?: {size?: number, page?:number = 0})**: `search` 方法允许搜索val对象中的内容. * `query`: 待搜索的内容, eg: `val->>'$.name' = 'something'`, 同时支持 JSONQuery 语法: `{name: {$like : 'something'}}` * Added `createdAt`/`updatedAt` fields to the `val` object(with indexed). ### FTS(全文检索)支持 在创建表的时候,可以指定 `fts` 参数,表示该表支持全文检索功能。 ```ts const table = db.create('testCollection', { fields: {content: {type: 'TEXT',}, type: {}}, fts: { unIndexed: ['type'], // no indexed type field as fts }, }) as KVSqliteCollection ``` 或者在创建表之后,也可以使用 `enableFts` 方法添加全文检索功能。 ```ts table.enableFts({ unIndexed: ['type'], }); ``` 使用 `searchFts` 方法进行全文检索。 ```ts // 简单FTS搜索: MATCH 'hello' let res = table.searchFts('hello'); // 带查询语法的FTS搜索: MATCH 'apple OR banana' res = table.searchFts({ $or: ["apple", "banana"] }); res = table.searchFts("'apple OR banana'", { ftsQueryStyle: true }); // 联结普通查询和FTS查询: MATCH 'hello' AND _id like 'fts2_%' res = table.searchFts("hello", {query: {_id: {$like: 'fts2_%'}}}) // mixture query res = table.searchFts({$match: 'hello', _id: {$like: 'fts2_%'}}) ``` ### AutoTimestamp 自动时间戳 创建表时,会自动设置 `createdAt` 和 `updatedAt` 字段为自动时间戳。 并自动为这两个字段设置索引。 当插入数据时,如果没有手工设置 `createdAt` 以及 `updatedAt` 字段,那么这两个会自动填充当前时间。 如果手工设置这两个字段,需要确保他们是ISO 8601格式, 如果格式错误也会自动填充当前时间。 当更新数据时,如果没有手工设置 `updatedAt` 字段或内容不是ISO 8601格式,那么该字段会自动填充当前时间。