{"_id":"@chaeco/indexed-db-storage","_rev":"3-58c8ee6e78a09debfadfa7f75582154a","name":"@chaeco/indexed-db-storage","dist-tags":{"latest":"0.3.0"},"versions":{"0.1.0":{"name":"@chaeco/indexed-db-storage","version":"0.1.0","keywords":["indexeddb","storage","persistent","browser","database","typescript","generic"],"author":{"name":"chaeco"},"license":"MIT","_id":"@chaeco/indexed-db-storage@0.1.0","maintainers":[{"name":"iptodays","email":"kingiswinter@outlook.com"}],"homepage":"https://github.com/chaeco/indexed-db-storage#readme","bugs":{"url":"https://github.com/chaeco/indexed-db-storage/issues"},"dist":{"shasum":"f9b093a65094e7cb520b7842f167cc4b9d6b9893","tarball":"https://registry.npmjs.org/@chaeco/indexed-db-storage/-/indexed-db-storage-0.1.0.tgz","fileCount":7,"integrity":"sha512-doZ9tnDpX4FH8aQYTrRKjQQyYlcQgOPSb16f6zsmWtoXAKm5LZQ/+FXYTFM1OVusRfS4lA0RoA9NgM+axpc5xA==","signatures":[{"sig":"MEYCIQDuL4eBcEjHm8rXownpgGU6KxlymJAPxCboQiji0QAHrQIhAPozNIYSvqWFHS8UMNb/Gd+KRlIKw0X5D2fcvuaV31U/","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"attestations":{"url":"https://registry.npmjs.org/-/npm/v1/attestations/@chaeco%2findexed-db-storage@0.1.0","provenance":{"predicateType":"https://slsa.dev/provenance/v1"}},"unpackedSize":196088},"main":"./dist/index.js","type":"module","types":"./dist/index.d.ts","engines":{"node":">=14.0.0"},"exports":{".":{"types":"./dist/index.d.ts","import":"./dist/index.js","require":"./dist/index.js"}},"gitHead":"b3734aa1df069c01f76de775b7ba8c1fcee8c961","scripts":{"lint":"eslint src test --ext .ts","test":"vitest","build":"npm run clean && rollup -c rollup.config.mjs","clean":"rm -rf dist","format":"prettier --write \"src/**/*.ts\" \"test/**/*.ts\" \"*.md\" \"examples/*.md\"","test:ui":"vitest --ui","lint:fix":"eslint src test --ext .ts --fix","test:once":"vitest run","type-check":"tsc --noEmit","format:check":"prettier --check \"src/**/*.ts\" \"test/**/*.ts\" \"*.md\" \"examples/*.md\"","test:coverage":"vitest --coverage"},"_npmUser":{"name":"iptodays","email":"kingiswinter@outlook.com"},"repository":{"url":"git+ssh://git@github.com/chaeco/indexed-db-storage.git","type":"git"},"_npmVersion":"10.8.2","description":"A generic browser-based persistent storage using IndexedDB","directories":{},"_nodeVersion":"20.20.2","publishConfig":{"access":"public","registry":"https://registry.npmjs.org/"},"_hasShrinkwrap":false,"devDependencies":{"jsdom":"^27.3.0","tslib":"^2.8.1","eslint":"^9.39.2","rollup":"^4.62.4","vitest":"^4.0.16","prettier":"^3.7.4","@eslint/js":"^9.39.2","@vitest/ui":"^4.0.16","typescript":"^5.9.3","@types/node":"^25.0.3","fake-indexeddb":"^6.2.5","rollup-plugin-dts":"^6.5.1","@vitest/coverage-v8":"^4.0.16","eslint-config-prettier":"^10.1.8","@rollup/plugin-commonjs":"^29.0.3","@rollup/plugin-typescript":"^12.3.0","@typescript-eslint/parser":"^8.50.0","@rollup/plugin-node-resolve":"^16.0.3","@typescript-eslint/eslint-plugin":"^8.50.0","markdownlint-cli2-formatter-sarif":"^0.0.4"},"_npmOperationalInternal":{"tmp":"tmp/indexed-db-storage_0.1.0_1788430786394_0.9018219472893321","host":"s3://npm-registry-packages-npm-production"}},"0.2.0":{"name":"@chaeco/indexed-db-storage","version":"0.2.0","keywords":["indexeddb","storage","persistent","browser","database","typescript","generic"],"author":{"name":"chaeco"},"license":"MIT","_id":"@chaeco/indexed-db-storage@0.2.0","maintainers":[{"name":"iptodays","email":"kingiswinter@outlook.com"}],"homepage":"https://github.com/chaeco/indexed-db-storage#readme","bugs":{"url":"https://github.com/chaeco/indexed-db-storage/issues"},"dist":{"shasum":"c1a0366a88413d190d84f12de6e16ba57d801bf2","tarball":"https://registry.npmjs.org/@chaeco/indexed-db-storage/-/indexed-db-storage-0.2.0.tgz","fileCount":7,"integrity":"sha512-nb73UB9NwBxNBsA6EycDnSYxXWltQ62qRv7QVwBLhhQGdVUGSZU/VVDARIgN39RBFCblO0oy3q+N6Xptzlx8Ow==","signatures":[{"sig":"MEUCIGjpBmGjmQ5MuABPIyGNREE1bEMR/1xVaomfOjZ8GYj8AiEAtoFe1zx+ZlvZB3YbHe+b+mNksHImWxsGbAhbI88C41c=","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"attestations":{"url":"https://registry.npmjs.org/-/npm/v1/attestations/@chaeco%2findexed-db-storage@0.2.0","provenance":{"predicateType":"https://slsa.dev/provenance/v1"}},"unpackedSize":218499},"main":"./dist/index.js","type":"module","types":"./dist/index.d.ts","engines":{"node":">=14.0.0"},"exports":{".":{"types":"./dist/index.d.ts","import":"./dist/index.js","require":"./dist/index.js"}},"gitHead":"4eb117225135198e07f7e24cc03b329dd08e49f8","scripts":{"lint":"eslint src test --ext .ts","test":"vitest","build":"npm run clean && rollup -c rollup.config.mjs","clean":"rm -rf dist","format":"prettier --write \"src/**/*.ts\" \"test/**/*.ts\" \"*.md\" \"examples/*.md\"","test:ui":"vitest --ui","lint:fix":"eslint src test --ext .ts --fix","test:once":"vitest run","type-check":"tsc --noEmit","format:check":"prettier --check \"src/**/*.ts\" \"test/**/*.ts\" \"*.md\" \"examples/*.md\"","test:coverage":"vitest --coverage"},"_npmUser":{"name":"iptodays","email":"kingiswinter@outlook.com"},"repository":{"url":"git+ssh://git@github.com/chaeco/indexed-db-storage.git","type":"git"},"_npmVersion":"10.8.2","description":"A generic browser-based persistent storage using IndexedDB","directories":{},"_nodeVersion":"20.20.2","publishConfig":{"access":"public","registry":"https://registry.npmjs.org/"},"_hasShrinkwrap":false,"devDependencies":{"jsdom":"^27.3.0","tslib":"^2.8.1","eslint":"^9.39.2","rollup":"^4.62.4","vitest":"^4.0.16","prettier":"^3.7.4","@eslint/js":"^9.39.2","@vitest/ui":"^4.0.16","typescript":"^5.9.3","@types/node":"^25.0.3","fake-indexeddb":"^6.2.5","rollup-plugin-dts":"^6.5.1","@vitest/coverage-v8":"^4.0.16","eslint-config-prettier":"^10.1.8","@rollup/plugin-commonjs":"^29.0.3","@rollup/plugin-typescript":"^12.3.0","@typescript-eslint/parser":"^8.50.0","@rollup/plugin-node-resolve":"^16.0.3","@typescript-eslint/eslint-plugin":"^8.50.0","markdownlint-cli2-formatter-sarif":"^0.0.4"},"_npmOperationalInternal":{"tmp":"tmp/indexed-db-storage_0.2.0_1788487216484_0.7940115184686936","host":"s3://npm-registry-packages-npm-production"}},"0.3.0":{"name":"@chaeco/indexed-db-storage","version":"0.3.0","description":"A generic browser-based persistent storage using IndexedDB","type":"module","sideEffects":false,"main":"./dist/index.cjs","types":"./dist/index.d.ts","exports":{".":{"types":"./dist/index.d.ts","import":"./dist/index.js","require":"./dist/index.cjs"}},"scripts":{"build":"npm run clean && rollup -c rollup.config.mjs","clean":"rm -rf dist","type-check":"tsc --noEmit","test":"vitest","test:ui":"vitest --ui","test:coverage":"vitest --coverage","test:once":"vitest run","lint":"eslint src test --ext .ts","lint:fix":"eslint src test --ext .ts --fix","format":"prettier --write \"src/**/*.ts\" \"test/**/*.ts\" \"*.md\" \"examples/*.md\"","format:check":"prettier --check \"src/**/*.ts\" \"test/**/*.ts\" \"*.md\" \"examples/*.md\""},"keywords":["indexeddb","storage","persistent","browser","database","typescript","generic"],"author":{"name":"chaeco"},"license":"MIT","devDependencies":{"@eslint/js":"^9.39.2","@rollup/plugin-commonjs":"^29.0.3","@rollup/plugin-node-resolve":"^16.0.3","@rollup/plugin-typescript":"^12.3.0","@types/node":"^25.0.3","@typescript-eslint/eslint-plugin":"^8.50.0","@typescript-eslint/parser":"^8.50.0","@vitest/coverage-v8":"^4.0.16","@vitest/ui":"^4.0.16","eslint":"^9.39.2","eslint-config-prettier":"^10.1.8","fake-indexeddb":"^6.2.5","jsdom":"^27.3.0","markdownlint-cli2-formatter-sarif":"^0.0.4","prettier":"^3.7.4","rollup":"^4.62.4","rollup-plugin-dts":"^6.5.1","tslib":"^2.8.1","typescript":"^5.9.3","vitest":"^4.0.16"},"engines":{"node":">=14.0.0"},"publishConfig":{"access":"public","registry":"https://registry.npmjs.org/"},"repository":{"type":"git","url":"git+ssh://git@github.com/chaeco/indexed-db-storage.git"},"bugs":{"url":"https://github.com/chaeco/indexed-db-storage/issues"},"homepage":"https://chaeco.github.io/indexed-db-storage/","_id":"@chaeco/indexed-db-storage@0.3.0","gitHead":"e52c4f14555eb30d105f7abba7b9708fdd6d639a","_nodeVersion":"20.20.2","_npmVersion":"10.8.2","dist":{"integrity":"sha512-RS5CyJeRF3LzrLtbdxwy0WE+f4fgOIRBOwda4Gg9/RO1OyzbtEYlqBwzePZKPsiDFxb4Sby3TkwL3T+7X9zUhg==","shasum":"0f33aca4dbe371bb4ca59ec3319fa4ec8a6b8649","tarball":"https://registry.npmjs.org/@chaeco/indexed-db-storage/-/indexed-db-storage-0.3.0.tgz","fileCount":9,"unpackedSize":440134,"attestations":{"url":"https://registry.npmjs.org/-/npm/v1/attestations/@chaeco%2findexed-db-storage@0.3.0","provenance":{"predicateType":"https://slsa.dev/provenance/v1"}},"signatures":[{"keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U","sig":"MEUCIEX6AzrALVMpJEFE8EPHOqGuODY+f2RlgiaE6YmzKkR+AiEA7NEZyY/KgeIp4SDL+aJIEWBRM1hFTcv0F7etptO4unU="}]},"_npmUser":{"name":"iptodays","email":"kingiswinter@outlook.com"},"directories":{},"maintainers":[{"name":"iptodays","email":"kingiswinter@outlook.com"}],"_npmOperationalInternal":{"host":"s3://npm-registry-packages-npm-production","tmp":"tmp/indexed-db-storage_0.3.0_1788574636391_0.6270294480924468"},"_hasShrinkwrap":false}},"time":{"created":"2026-09-03T10:19:46.218Z","modified":"2026-09-05T02:17:16.910Z","0.1.0":"2026-09-03T10:19:46.556Z","0.2.0":"2026-09-04T02:00:16.650Z","0.3.0":"2026-09-05T02:17:16.533Z"},"bugs":{"url":"https://github.com/chaeco/indexed-db-storage/issues"},"author":{"name":"chaeco"},"license":"MIT","homepage":"https://chaeco.github.io/indexed-db-storage/","keywords":["indexeddb","storage","persistent","browser","database","typescript","generic"],"repository":{"type":"git","url":"git+ssh://git@github.com/chaeco/indexed-db-storage.git"},"description":"A generic browser-based persistent storage using IndexedDB","maintainers":[{"name":"iptodays","email":"kingiswinter@outlook.com"}],"readme":"# @chaeco/indexed-db-storage\n\n[![npm version](https://img.shields.io/npm/v/@chaeco/indexed-db-storage.svg)](https://www.npmjs.com/package/@chaeco/indexed-db-storage)\n[![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)\n\n通用 IndexedDB 存储解决方案，为浏览器端提供强大的持久化存储能力。\n\n## ✨ 特性\n\n- 🎯 **通用存储** - 支持任意数据类型，不限于特定场景\n\n- 🔍 **强大查询** - 支持 where 条件、多字段排序、自定义过滤等高级查询；范围条件自动编译为 `IDBKeyRange` 下推到索引（Dexie 式优化）\n\n- ⚡ **批量与原子操作** - `bulkAdd`/`bulkPut`/`bulkDelete` 单事务批量读写，`runInTransaction` 原子事务\n\n- 📄 **高效分页** - keyset 分页（`after`/`before`），无限滚动不受 offset 性能惩罚\n\n- 🔔 **跨标签页事件** - `onWrite` 订阅写入事件，基于 BroadcastChannel 跨标签页同步\n\n- 🔒 **类型安全** - 完整的 TypeScript 泛型支持\n\n- 🔄 **单例模式** - 基于 `dbName` + `storeName` 组合自动管理实例，相同配置复用同一连接\n\n- 🧹 **自动清理** - 可配置的数据清理机制（按时间/数量）\n\n- ⚙️ **灵活配置** - 自定义 keyPath、索引等数据库配置；已有 store 的新增/变更索引会在下次 `init()` 时自动升级\n\n- 📦 **零依赖** - 无外部依赖，轻量级设计\n\n- 🚀 **现代化** - 基于 Promise 的异步 API\n\n- ✅ **测试完善** - 172 个测试用例，全量覆盖核心逻辑和边界情况\n\n## 安装\n\n```bash\nnpm install github:chaeco/indexed-db-storage\n```\n\n## 模块格式\n\n同时提供 **ESM**（`import`）与 **CommonJS**（`require`）两种构建产物，适用于浏览器、打包器、服务端渲染（SSR）及 Node.js 工具链：\n\n```typescript\nimport { IndexedDBStorage } from '@chaeco/indexed-db-storage' // ESM\nconst { IndexedDBStorage } = require('@chaeco/indexed-db-storage') // CommonJS\n```\n\n## 快速开始\n\n```typescript\nimport { IndexedDBStorage } from '@chaeco/indexed-db-storage'\n\n// 定义数据类型\ninterface User {\n  id?: number\n  name: string\n  email: string\n  createdAt: number\n}\n\n// 创建存储实例\nconst storage = new IndexedDBStorage<User>(\n  {\n    dbName: 'my-app',\n    storeName: 'users',\n  },\n  {\n    storeName: 'users',\n    keyPath: 'id',\n    autoIncrement: true,\n  }\n)\n\n// 初始化\nawait storage.init()\n\n// 保存数据\nawait storage.save({\n  name: 'John Doe',\n  email: 'john@example.com',\n  createdAt: Date.now()\n})\n\n// 查询数据\nconst users = await storage.query({ limit: 10 })\n\n// 获取单条数据\nconst user = await storage.get(1)\n\n// 更新数据\nawait storage.update({\n  id: 1,\n  name: 'Jane Doe',\n  email: 'jane@example.com',\n  createdAt: Date.now()\n})\n\n// 删除数据\nawait storage.delete(1)\n\n// 清空所有数据\nawait storage.clear()\n\n```\n\n## 高级用法\n\n### 自动清理配置\n\n```typescript\n// 带自动清理的存储\nconst storage = new IndexedDBStorage<Log>(\n  {\n    dbName: 'app-logs',\n    storeName: 'logs',\n    maxRecords: 1000,                       // 可选：最多保留 1000 条\n    retentionTime: 7 * 24 * 60 * 60 * 1000, // 可选：保留 7 天\n    cleanupInterval: 60 * 60 * 1000,        // 可选：每小时清理一次\n  },\n  {\n    storeName: 'logs',\n    keyPath: 'id',\n    autoIncrement: true,\n    indexes: [\n      { name: 'timestamp', keyPath: 'timestamp' }\n    ]\n  }\n)\n\nawait storage.init()\n\n// 注意：\n// - 如果不配置 maxRecords/retentionTime/cleanupInterval，则不会启用自动清理\n// - cleanupInterval 必须配置才会启动定时清理\n// - maxRecords 或 retentionTime 至少配置一个才会触发清理逻辑\n\n```\n\n### 自定义数据库配置\n\n```typescript\nimport { IndexedDBStorage } from '@chaeco/indexed-db-storage'\nimport type { StoreConfig } from '@chaeco/indexed-db-storage'\n\nconst storeConfig: StoreConfig = {\n  storeName: 'products',\n  keyPath: 'id',\n  autoIncrement: false,\n  indexes: [\n    { name: 'category', keyPath: 'category' },\n    { name: 'price', keyPath: 'price' },\n  ]\n}\n\nconst storage = new IndexedDBStorage(\n  {\n    dbName: 'shop',\n    storeName: 'products',\n  },\n  storeConfig\n)\n\nawait storage.init()\n\n```\n\n### 使用索引查询\n\n```typescript\n// 按索引查询\nconst products = await storage.query({\n  indexName: 'category',\n  range: IDBKeyRange.only('electronics'),\n  limit: 20\n})\n\n// 范围查询\nconst expensiveProducts = await storage.query({\n  indexName: 'price',\n  range: IDBKeyRange.lowerBound(1000),\n  limit: 10\n})\n\n```\n\n### 高级查询（where 条件）\n\n```typescript\n// 1. 等值查询\nconst results = await storage.query({\n  where: { field: 'age', operator: 'eq', value: 25 }\n})\n\n// 2. 范围查询\nconst results = await storage.query({\n  where: { field: 'age', operator: 'gt', value: 30 } // > 30\n})\n\nconst results = await storage.query({\n  where: { field: 'age', operator: 'between', value: [25, 35] } // 25-35之间\n})\n\n// 3. 字符串查询\nconst results = await storage.query({\n  where: { field: 'name', operator: 'contains', value: '李' } // 包含\"李\"\n})\n\nconst results = await storage.query({\n  where: { field: 'name', operator: 'startsWith', value: '张' } // 以\"张\"开头\n})\n\n// 4. 数组查询\nconst results = await storage.query({\n  where: { field: 'department', operator: 'in', value: ['工程', '产品'] }\n})\n\n// 5. 多条件查询（AND）\nconst results = await storage.query({\n  where: [\n    { field: 'age', operator: 'gt', value: 25 },\n    { field: 'department', operator: 'eq', value: '工程' }\n  ]\n})\n\n// 6. 排序\nconst results = await storage.query({\n  sort: { field: 'age', order: 'asc' } // 按年龄升序\n})\n\n// 多字段排序\nconst results = await storage.query({\n  sort: [\n    { field: 'department', order: 'asc' },\n    { field: 'age', order: 'desc' }\n  ]\n})\n\n// 7. 自定义过滤函数\nconst results = await storage.query({\n  filter: (item) => item.age % 2 === 0 && item.salary > 8000\n})\n\n// 8. 组合查询\nconst results = await storage.query({\n  where: { field: 'age', operator: 'gte', value: 25 },\n  sort: { field: 'salary', order: 'desc' },\n  filter: (item) => item.isActive,\n  limit: 10,\n  offset: 0\n})\n\n```\n\n**支持的查询操作符：**\n\n- `eq` - 等于\n- `ne` - 不等于\n- `gt` - 大于\n- `gte` - 大于等于\n- `lt` - 小于\n- `lte` - 小于等于\n- `between` - 在范围内（需要提供 [min, max] 数组）\n- `in` - 在数组中\n- `contains` - 包含（字符串）\n- `startsWith` - 开头匹配（字符串）\n- `endsWith` - 结尾匹配（字符串）\n\n```\n\n### 实例复用\n\n```typescript\n// 相同配置会返回同一实例\nconst storage1 = new IndexedDBStorage({\n  dbName: 'my-app',\n  storeName: 'users'\n})\n\nconst storage2 = new IndexedDBStorage({\n  dbName: 'my-app',\n  storeName: 'users'\n})\n\nconsole.log(storage1 === storage2) // true\n\n// 不同 storeName 会创建独立实例\nconst storage3 = new IndexedDBStorage({\n  dbName: 'my-app',\n  storeName: 'posts'\n})\n\nconsole.log(storage1 === storage3) // false\n\n```\n\n### 手动清理\n\n```typescript\n// 手动触发清理（不需要配置自动清理参数）\nawait storage.cleanup()\n\n// 获取记录数\nconst count = await storage.count()\nconsole.log(`当前有 ${count} 条记录`)\n\n```\n\n## API 文档\n\n### 构造函数\n\n```typescript\nnew IndexedDBStorage<T>(options: StorageOptions, storeConfig?: StoreConfig)\n\n```\n\n**参数：**\n\n`options` (StorageOptions):\n\n- `dbName` (string, 必填) - 数据库名称\n\n- `storeName` (string, 必填) - 对象存储名称\n\n- `maxRecords` (number, 可选) - 最大记录数，超出后触发清理。**不配置则不限制数量**\n\n- `retentionTime` (number, 可选) - 数据保留时间（毫秒）。**不配置则不限制时间**\n\n- `cleanupInterval` (number, 可选) - 自动清理间隔（毫秒）。**必须配置才会启动定时清理**\n\n- `timestampIndexName` (string, 可选) - 时间戳索引名称（用于 `retentionTime` 过期清理，默认为 `'timestamp'`）\n\n- `version` (number, 可选) - 目标 schema 版本（正整数）。配置后 `init()` 以 `max(当前版本, 该版本)` 打开：即使没有 schema 变更也会触发升级事件，供 `onUpgrade` 做纯数据迁移\n\n- `onUpgrade` ((ctx: UpgradeContext) => void | Promise<void>, 可选) - 版本升级迁移钩子：在升级事务内、索引 schema 变更应用之后执行，用于旧数据结构迁移；全新数据库（oldVersion === 0）时可用于种子数据。⚠️ ctx 内只允许 await IndexedDB 请求；同步抛错或迁移请求失败会中止升级并使 `init()` 拒绝\n\n`storeConfig` (StoreConfig, 可选):\n\n- `storeName` (string, 必填) - 对象存储名称\n\n- `keyPath` (string, 可选) - 主键字段名。**不配置则使用 out-of-line keys**\n\n- `autoIncrement` (boolean, 可选) - 是否自动递增。**默认为 true**\n\n- `indexes` (IndexConfig[], 可选) - 索引配置数组\n\n### 实例方法\n\n#### `async init(): Promise<void>`\n\n初始化数据库。支持重复调用，只会初始化一次。\n\n#### `async save(data: T): Promise<IDBValidKey>`\n\n保存数据。返回生成的主键。\n\n#### `async update(data: T): Promise<IDBValidKey>`\n\n更新数据。返回主键。\n\n#### `async query(options?: QueryOptions): Promise<T[]>`\n\n查询数据。\n\n**QueryOptions:**\n\n- `limit` (number) - 返回数量限制\n\n- `offset` (number) - 偏移量\n\n- `indexName` (string) - 使用的索引名称\n\n- `range` (IDBKeyRange) - 查询范围\n\n- `after` (IDBValidKey) - keyset 分页：从该键之后开始遍历（不含该键）。作用于主键或 `indexName` 索引键。**与 `range` 互斥**\n\n- `before` (IDBValidKey) - keyset 分页：遍历到该键之前结束（不含该键）。配合 `direction: 'prev'` 可实现降序翻页\n\n- `direction` (IDBCursorDirection) - 游标遍历方向（**仅在同时提供 `where` 或 `filter`、或使用 `after`/`before` 时生效**，否则走 getAll 路径，该选项被忽略并输出警告）\n\n- `where` (WhereCondition | WhereCondition[]) - 查询条件（支持多条件，AND 语义）\n\n- `sort` (SortOption | SortOption[]) - 排序选项（支持多字段排序，在 `finishQuery` 阶段应用）\n\n- `filter` ((item: T) => boolean) - 自定义过滤函数\n\n**WhereCondition:**\n\n- `field` (string) - 字段名（支持嵌套字段如 `user.address.city`）\n\n- `operator` (QueryOperator) - 操作符（eq、ne、gt、gte、lt、lte、between、in、contains、startsWith、endsWith）\n\n- `value` (unknown) - 比较值\n\n**SortOption:**\n\n- `field` (string) - 排序字段名\n\n- `order` ('asc' | 'desc') - 排序方向\n\n#### `async get(key: IDBValidKey): Promise<T | undefined>`\n\n根据主键获取单条数据。\n\n#### `async delete(key: IDBValidKey): Promise<void>`\n\n根据主键删除数据。\n\n#### `async clear(): Promise<void>`\n\n清空所有数据。\n\n#### `async count(): Promise<number>`\n\n获取记录总数。\n\n#### `async bulkAdd(items: T[]): Promise<IDBValidKey[]>`\n\n批量插入（单事务，全有或全无）。任一记录写入失败时整个批次回滚并以首个错误 reject。返回与输入顺序一致的主键数组。\n\n#### `async bulkPut(items: T[]): Promise<IDBValidKey[]>`\n\n批量 upsert（单事务，全有或全无）。返回主键数组。\n\n#### `async bulkDelete(keys: IDBValidKey[]): Promise<number>`\n\n批量删除（单事务）。返回实际删除的记录数（删除不存在的 key 不算错误）。\n\n#### `async getMany(keys: IDBValidKey[]): Promise<(T | undefined)[]>`\n\n批量获取（单事务）。结果与输入顺序一致，不存在的 key 对应 `undefined`。\n\n#### `async iterate(onItem, options?): Promise<number>`\n\n流式遍历：游标逐条回调，不在内存中累积全量结果，适合大数据量导出/批处理。`onItem(item, key)` 的 `key` 为记录主键，返回 `false` 可提前终止。不支持 `sort`。\n\n#### `async deleteMany(options?): Promise<number>`\n\n按查询条件批量删除（单事务），支持全部 QueryOptions（`sort`+`limit` 可实现\"删除最旧的 N 条\"）。不带条件时等价于 `clear()`。返回实际删除数。\n\n#### `async queryKeys(options?): Promise<IDBValidKey[]>`\n\n只查询键、不反序列化记录值，适合存在性检查/批量取 ID。始终返回记录主键。不支持 `sort`。\n\n#### `async exportData(): Promise<T[]>`\n\n导出全部记录（备份 / 跨存储迁移）。内联 keyPath 存储可经 `importData` 无损恢复；out-of-line keys 存储导出的是值本身，键无法恢复。\n\n#### `async importData(items, options?): Promise<number>`\n\n导入记录（单事务 bulkPut 覆盖写，内联 keyPath 主键保留）。`options.clearBefore: true` 时先清空再导入（全量恢复）。返回写入条数。\n\n#### `onWrite(listener): () => void`\n\n订阅写入事件（本地写入 + 其他标签页经 BroadcastChannel 同步的写入）。返回取消订阅函数。\n\n事件结构：`{ storeName, type: 'add' | 'put' | 'delete' | 'bulkAdd' | 'bulkPut' | 'bulkDelete' | 'clear' | 'cleanup', keys?, source: 'local' | 'remote' }`。自动清理删除的数据会以 `cleanup` 类型发出。\n\n#### `async runInTransaction<R>(mode, scope, options?): Promise<R>`\n\n在单个事务中原子执行一组操作。scope 接收共享同一事务的操作集（`get`/`getMany`/`save`/`update`/`bulkAdd`/`bulkPut`/`delete`/`bulkDelete`/`count`/`query`/`forStore`），任何失败都会回滚全部写入。\n\n⚠️ scope 内只允许 await IndexedDB 请求；await 非 IDB 异步操作（fetch/setTimeout 等）会导致事务自动提交（IndexedDB 规范行为），后续请求将抛出 InvalidStateError。\n\n`options.stores` 可声明同库内其他 store，配合 `tx.forStore(name)` 实现跨 store 原子写入：\n\n```typescript\nawait orders.runInTransaction('readwrite', async tx => {\n  await tx.save(order)\n  await tx.forStore('stocks').put({ item: order.item, qty: 3 })\n}, { stores: ['stocks'] })\n```\n\n#### `async cleanup(): Promise<void>`\n\n手动触发清理操作。\n\n#### `stopCleanupTimer(): void`\n\n停止定期清理定时器。停止后仍可通过 `cleanup()` 手动触发清理。通常无需直接调用，`close()` / `destroy()` 内部会自动停止。\n\n#### `close(): void`\n\n关闭数据库连接。若在 `init()` 进行中调用，会使正在进行的连接失效，避免竞态泄漏。\n\n#### `destroy(): void`\n\n销毁实例（关闭连接并从单例缓存中移除）。\n\n### 静态方法\n\n#### `static clearInstance(options?: StorageOptions): void`\n\n清除指定的实例缓存。不传参数则清除所有实例。\n\n#### `static async requestPersistence(): Promise<boolean | null>`\n\n请求将当前源（origin）标记为持久化存储，降低浏览器在存储压力下驱逐数据的概率。对 Safari ITP 的\"7 天不活跃清除\"无效。环境不支持时返回 `null`。\n\n#### `static async isPersistent(): Promise<boolean | null>`\n\n查询当前源是否已被标记为持久化存储。环境不支持时返回 `null`。\n\n#### `static async estimate(): Promise<StorageEstimate | null>`\n\n查询当前源的存储配额与用量（origin 级别，非单库）。环境不支持时返回 `null`。\n\n## 📁 项目结构\n\n```text\nsrc/\n├── core/\n│   ├── config-manager.ts    # 配置管理\n│   └── data-operations.ts   # CRUD 操作\n├── managers/\n│   ├── instance.ts          # 实例管理\n│   ├── database.ts          # 数据库初始化\n│   └── cleanup.ts           # 清理管理\n├── types/\n│   ├── config.ts            # 配置类型\n│   ├── operations.ts        # 操作类型\n│   └── storage.ts           # 存储类型\n├── storage.ts               # 主存储类\n└── index.ts                 # 入口导出\n\n```\n\n## 💡 使用场景\n\n- **用户数据缓存** - 离线优先的应用\n\n- **表单草稿** - 防止数据丢失\n\n- **聊天记录** - 本地消息存储\n\n- **购物车** - 跨会话持久化\n\n- **日志收集** - 客户端日志\n\n- **文件管理** - 上传文件元数据\n\n- **游戏存档** - 本地进度保存\n\n## 🌐 浏览器兼容性\n\n| 浏览器  | 最低版本 |\n| ------- | ------- |\n| Chrome  | 11+ ✅  |\n| Firefox | 10+ ✅  |\n| Safari  | 10+ ✅  |\n| Edge    | 15+ ✅  |\n| Opera   | 15+ ✅  |\n| IE      | ❌ 不支持 |\n\n## 📚 示例\n\n查看 [examples](./examples) 目录获取更多示例：\n\n- [basic.html](./examples/basic.html) - 基础 CRUD 操作\n\n- [logger.html](./examples/logger.html) - 日志系统示例\n\n- [bulk-transaction.html](./examples/bulk-transaction.html) - 批量操作、事务、keyset 分页、跨标签页事件\n\n## 🔧 开发\n\n```bash\n\n# 安装依赖\n\nnpm install\n\n# 运行测试\n\nnpm test\n\n# 构建\n\nnpm run build\n\n# 代码检查\n\nnpm run lint\n\n# 格式化\n\nnpm run format\n\n```\n\nMIT © [chaeco](https://github.com/chaeco)\n\n","readmeFilename":"README.zh-CN.md"}