@1-/scan 0.1.3 → 0.1.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -5,16 +5,16 @@
5
5
  <a id="en"></a>
6
6
  # @1-/scan : Incrementally scan directory files and track metadata in SQLite
7
7
 
8
- Incrementally scans directory files, compares file sizes and modification times to detect changes, synchronizes metadata to an SQLite database, and returns updated relative paths.
8
+ Incrementally scans directory files, compares file sizes and modification times to detect changes, synchronizes metadata to SQLite database, and returns updated relative paths.
9
9
 
10
10
  ## Features
11
11
 
12
- - **Incremental Scanning**: Detects and processes only new, modified, or deleted files, avoiding redundant file system operations.
12
+ - **Incremental Scanning**: Processes only new, modified, or deleted files, avoiding redundant file system operations.
13
13
  - **Key Optimization**: Stores relative paths within 16 bytes directly as raw bytes; hashes longer paths to 16-byte MD5 digests to optimize database index space and query performance.
14
14
  - **Metadata Compression**: Compresses file sizes and modification times using Varint (variable-length byte) encoding.
15
- - **Transactional Integrity**: Packages updates and deletions in a single database transaction to guarantee consistency.
16
- - **Flexible Filtering**: Supports custom ignore callback functions to filter specific files and directories.
17
- - **Native Database**: Integrates Bun's native `bun:sqlite` module, eliminating external database driver dependencies.
15
+ - **Transactional Integrity**: Packages updates and deletions in database transactions to guarantee consistency.
16
+ - **File Filtering**: Supports custom ignore callback functions to filter files and directories.
17
+ - **Native Database**: Integrates Bun native `bun:sqlite` module, eliminating external database driver dependencies.
18
18
 
19
19
  ## Usage
20
20
 
@@ -24,29 +24,65 @@ Incrementally scans directory files, compares file sizes and modification times
24
24
  import scan from "@1-/scan";
25
25
 
26
26
  const dir = "./data";
27
- const dbPath = "./scan_record.db";
27
+ const db_path = "./scan_record.db";
28
28
 
29
- // Scan directory and sync metadata to SQLite, returning modified relative paths
30
- const updatedPaths = await scan(dir, dbPath);
31
- console.log("Updated files:", updatedPaths);
29
+ // Scan directory and sync metadata to SQLite, returning modified relative paths and upsert function
30
+ const [updated_paths, upsert] = await scan(dir, db_path);
31
+
32
+ // Auto-close database when exiting scope
33
+ using _upsert = upsert;
34
+
35
+ console.log("Updated files:", updated_paths);
36
+
37
+ // Update scanned file metadata in database
38
+ for (const rel_path of updated_paths) {
39
+ await upsert(rel_path);
40
+ }
32
41
  ```
33
42
 
34
43
  ### Scan with Ignore Filter
35
44
 
36
45
  ```javascript
37
- import { FILE } from "@1-/walk";
38
46
  import scan from "@1-/scan";
39
47
 
40
48
  const dir = "./data";
41
- const dbPath = "./scan_record.db";
49
+ const db_path = "./scan_record.db";
42
50
 
43
51
  // Ignore temporary files and specific configurations
44
- const ignore = (kind, relPath) => {
45
- return relPath.startsWith("temp/") || relPath === "config.json";
52
+ const ignore = (kind, rel_path) => {
53
+ return rel_path.startsWith("temp/") || rel_path === "config.json";
46
54
  };
47
55
 
48
- const updatedPaths = await scan(dir, dbPath, ignore);
49
- console.log("Synced. Updated files:", updatedPaths);
56
+ const [updated_paths, upsert] = await scan(dir, db_path, ignore);
57
+ using _upsert = upsert;
58
+
59
+ console.log("Synced. Updated files:", updated_paths);
60
+
61
+ for (const rel_path of updated_paths) {
62
+ await upsert(rel_path);
63
+ }
64
+ ```
65
+
66
+ ### Bulk Storage Module Usage
67
+
68
+ ```javascript
69
+ import save from "@1-/scan/save.js";
70
+ import sqlite from "@1-/scan/sqlite.js";
71
+
72
+ const db = sqlite("./scan_record.db");
73
+
74
+ // Bulk update and delete metadata
75
+ save(
76
+ db,
77
+ [
78
+ ["file.txt", new Uint8Array([1, 2, 3]), 123, 1620000000]
79
+ ],
80
+ [
81
+ new Uint8Array([4, 5, 6])
82
+ ]
83
+ );
84
+
85
+ db.close();
50
86
  ```
51
87
 
52
88
  ## Design Ideas
@@ -55,24 +91,25 @@ The main entry orchestrates independent modules to execute the incremental scann
55
91
 
56
92
  ```mermaid
57
93
  graph TD
58
- Entry["_.js (Entry Point)"] -->|1. Initialize Connection| Sqlite[sqlite.js]
59
- Entry -->|2. Load Existing Records| Load[load.js]
60
- Entry -->|3. Walk & Compare Files| DirWalk[dirWalk.js]
94
+ Entry["_.js (Entry Point)"] -->|1. Initialize Connection| Sqlite["sqlite.js"]
95
+ Entry -->|2. Load Existing Records| Load["load.js"]
96
+ Entry -->|3. Walk & Compare Files| DirWalk["dirWalk.js"]
61
97
  DirWalk -->|Invoke| Walk["@1-/walk/walkRelIgnore"]
62
- DirWalk -->|Process Path Keys| Hash[hash.js]
63
- Entry -->|4. Persist Changes| Save[save.js]
64
- Save -->|Transaction Wrapper| Trans[trans.js]
98
+ DirWalk -->|Process Path Keys| Hash["hash.js"]
99
+ Entry -->|4. Delete Absent & Return Upsert| Trans["trans.js"]
100
+ Save["save.js (Independent Sync Helper)"] -->|Transaction Wrapper| Trans
65
101
  ```
66
102
 
67
- 1. **Initialize Connection (`sqlite.js`)**: Opens the SQLite database connection and configures automatic connection disposal.
68
- 2. **Load Records (`load.js`)**: Automatically creates the schema if missing, retrieves existing file hashes, sizes, and modification times, and reconstructs the reference set in memory.
69
- 3. **Walk & Compare (`dirWalk.js`)**: Traverses the directory structure recursively. Paths are transformed into 16-byte keys via `hash.js`. File attributes are encoded using `@3-/vb` and compared against database records to identify additions and modifications.
70
- 4. **Persist Changes (`save.js`)**: Executes bulk inserts and deletions in a single transaction via `trans.js` to update database state.
103
+ 1. **Initialize Connection (`sqlite.js`)**: Opens SQLite database connection and configures automatic connection disposal.
104
+ 2. **Load Records (`load.js`)**: Automatically creates `scanMtimeLen` table if missing, retrieves existing file hashes, sizes, and modification times, and reconstructs reference set in memory.
105
+ 3. **Walk & Compare (`dirWalk.js`)**: Traverses directory structure recursively. Paths are transformed into 16-byte keys via `hash.js`. File attributes are encoded using `@3-/vb` and compared against database records to identify additions and modifications.
106
+ 4. **Delete & Return Upsert**: Uses `trans.js` to execute transaction-safe deletions for deleted files, and returns modified relative paths and an `upsert` function so that caller can update database records.
107
+ 5. **Independent Sync Helper (`save.js`)**: Exported independent module to execute bulk updates and deletions in transactions.
71
108
 
72
109
  ## Tech Stack
73
110
 
74
111
  - **Bun**: Runtime environment and test framework.
75
- - **Bun SQLite**: Native high-performance SQLite engine built into Bun.
112
+ - **Bun SQLite**: Native SQLite engine built into Bun.
76
113
  - **@1-/walk**: Directory walker with ignore support.
77
114
  - **@3-/vb**: Variable-length byte (Varint) encoder and decoder.
78
115
  - **@3-/binmap / @3-/binset**: Memory-efficient collections designed for binary keys.
@@ -82,14 +119,14 @@ graph TD
82
119
  ```
83
120
  .
84
121
  ├── src
85
- │ ├── _.js # Entry point coordinating scanning and synchronization
122
+ │ ├── _.js # Entry point coordinating scanning and returning upsert helper
86
123
  │ ├── dirWalk.js # Directory traverser comparing file metadata
87
124
  │ ├── hash.js # Hashing helper mapping paths to 16-byte keys
88
125
  │ ├── load.js # Database loader initializing schema and loading records
89
- │ ├── save.js # Writer executing bulk updates and deletions
126
+ │ ├── save.js # Independent helper executing bulk updates and deletions
90
127
  │ ├── sqlite.js # Connection manager instantiating SQLite database
91
128
  │ └── trans.js # Transaction wrapper providing rollback mechanism
92
- └── tests # Test suites
129
+ └── tests # Test directory
93
130
  ```
94
131
 
95
132
  ## History
@@ -104,16 +141,16 @@ To conserve disk space and reduce I/O overhead, SQLite utilizes Varint (variable
104
141
  <a id="zh"></a>
105
142
  # @1-/scan : 增量扫描目录文件并使用 SQLite 记录元数据
106
143
 
107
- 增量扫描目录文件,通过比对文件大小和修改时间检测变更,并同步至 SQLite 数据库中,最终返回有更新的相对路径列表。
144
+ 增量扫描目录文件,比对大小与修改时间以检测变更,同步元数据至 SQLite 数据库,返回发生变更之相对路径列表。
108
145
 
109
146
  ## 功能介绍
110
147
 
111
- - **增量扫描**:仅处理新增、修改或删除的文件,避免冗余的文件系统读写,提升同步速度。
112
- - **路径压缩**:当相对路径长度小于等于 16 字节时保留原始字节;超出 16 字节则转换为 16 字节 MD5 值作为数据库主键,优化索引空间与查询性能。
113
- - **元数据压缩**:使用 Varint(可变字节整型)编码方式压缩存储文件大小和修改时间。
114
- - **事务安全**:将更新与删除操作合并在单个数据库事务中执行,确保数据一致性。
115
- - **灵活过滤**:支持通过自定义回调函数过滤指定类型的文件与目录。
116
- - **原生依赖**:基于 Bun 内置 `bun:sqlite` 模块,无需额外安装或编译数据库驱动。
148
+ - **增量扫描**:处理新增、修改或删除之文件,避免冗余文件系统读写,提升同步效率。
149
+ - **路径压缩**:相对路径长度不大于 16 字节时保留原始字节;超出 16 字节则转换为 16 字节 MD5 值作为主键,优化索引空间与查询性能。
150
+ - **元数据压缩**:使用 Varint(可变字节整型)编码方式压缩存储文件大小与修改时间。
151
+ - **事务安全**:将更新与删除操作合并在数据库事务中执行,确保数据一致性。
152
+ - **文件过滤**:支持自定义过滤函数以排除特定文件与目录。
153
+ - **原生依赖**:基于 Bun 内置 `bun:sqlite` 模块,免去安装与编译数据库驱动步骤。
117
154
 
118
155
  ## 使用演示
119
156
 
@@ -123,55 +160,92 @@ To conserve disk space and reduce I/O overhead, SQLite utilizes Varint (variable
123
160
  import scan from "@1-/scan";
124
161
 
125
162
  const dir = "./data";
126
- const dbPath = "./scan_record.db";
163
+ const db_path = "./scan_record.db";
127
164
 
128
- // 扫描目录并同步至 SQLite,返回发生变更的相对路径列表
129
- const updatedPaths = await scan(dir, dbPath);
130
- console.log("更新文件列表:", updatedPaths);
165
+ // 扫描目录并同步至 SQLite,返回发生变更之相对路径列表与更新函数
166
+ const [updated_paths, upsert] = await scan(dir, db_path);
167
+
168
+ // 退出作用域时自动关闭数据库
169
+ using _upsert = upsert;
170
+
171
+ console.log("更新文件列表:", updated_paths);
172
+
173
+ // 更新已处理文件元数据至数据库
174
+ for (const rel_path of updated_paths) {
175
+ await upsert(rel_path);
176
+ }
131
177
  ```
132
178
 
133
- ### 带有忽略规则的扫描
179
+ ### 过滤规则扫描
134
180
 
135
181
  ```javascript
136
- import { FILE } from "@1-/walk";
137
182
  import scan from "@1-/scan";
138
183
 
139
184
  const dir = "./data";
140
- const dbPath = "./scan_record.db";
185
+ const db_path = "./scan_record.db";
141
186
 
142
- // 忽略特定文件或目录
143
- const ignore = (kind, relPath) => {
144
- return relPath.startsWith("temp/") || relPath === "config.json";
187
+ // 过滤临时文件与特定配置
188
+ const ignore = (kind, rel_path) => {
189
+ return rel_path.startsWith("temp/") || rel_path === "config.json";
145
190
  };
146
191
 
147
- const updatedPaths = await scan(dir, dbPath, ignore);
148
- console.log("已同步,更新列表:", updatedPaths);
192
+ const [updated_paths, upsert] = await scan(dir, db_path, ignore);
193
+ using _upsert = upsert;
194
+
195
+ console.log("已同步,更新列表:", updated_paths);
196
+
197
+ for (const rel_path of updated_paths) {
198
+ await upsert(rel_path);
199
+ }
200
+ ```
201
+
202
+ ### 批量存储模块使用
203
+
204
+ ```javascript
205
+ import save from "@1-/scan/save.js";
206
+ import sqlite from "@1-/scan/sqlite.js";
207
+
208
+ const db = sqlite("./scan_record.db");
209
+
210
+ // 批量更新与删除元数据
211
+ save(
212
+ db,
213
+ [
214
+ ["file.txt", new Uint8Array([1, 2, 3]), 123, 1620000000]
215
+ ],
216
+ [
217
+ new Uint8Array([4, 5, 6])
218
+ ]
219
+ );
220
+
221
+ db.close();
149
222
  ```
150
223
 
151
224
  ## 设计思路
152
225
 
153
- 系统主入口调用各个独立模块完成增量扫描与数据同步流程。
226
+ 系统主入口调度各独立模块完成增量扫描与数据同步。
154
227
 
155
228
  ```mermaid
156
229
  graph TD
157
- Entry["_.js (主入口)"] -->|1. 初始化连接| Sqlite[sqlite.js]
158
- Entry -->|2. 加载已有记录| Load[load.js]
159
- Entry -->|3. 扫描文件系统并对比| DirWalk[dirWalk.js]
230
+ Entry["_.js (主入口)"] -->|1. 初始化连接| Sqlite["sqlite.js"]
231
+ Entry -->|2. 加载已有记录| Load["load.js"]
232
+ Entry -->|3. 扫描文件系统并对比| DirWalk["dirWalk.js"]
160
233
  DirWalk -->|调用| Walk["@1-/walk/walkRelIgnore"]
161
- DirWalk -->|处理路径键| Hash[hash.js]
162
- Entry -->|4. 持久化数据变更| Save[save.js]
163
- Save -->|事务保障| Trans[trans.js]
234
+ DirWalk -->|处理路径键| Hash["hash.js"]
235
+ Entry -->|4. 删除失效记录并返回更新函数| Trans["trans.js"]
236
+ Save["save.js (独立批量存储模块)"] -->|事务保障| Trans
164
237
  ```
165
238
 
166
239
  1. **初始化连接 (`sqlite.js`)**:打开 SQLite 数据库,并配置自动释放连接机制。
167
- 2. **加载记录 (`load.js`)**:若表不存在则自动创建,读取已记录的文件哈希、大小及修改时间,在内存中还原比对集合。
240
+ 2. **加载记录 (`load.js`)**:若数据表 `scanMtimeLen` 不存在则自动创建,读取已记录的文件哈希、大小及修改时间,在内存中还原比对集合。
168
241
  3. **文件系统扫描 (`dirWalk.js`)**:递归遍历目录,利用 `hash.js` 将路径映射为 16 字节键。对比当前文件与数据库元数据(利用 `@3-/vb` 进行压缩状态对比),筛选出新增和修改的文件。
169
- 4. **数据存储 (`save.js`)**:使用 `trans.js` 开启事务,将需要删除的无效记录及需要更新的元数据批量写入 SQLite 数据库。
242
+ 4. **删除与返回更新函数**:使用 `trans.js` 开启事务,批量删除已被移除的记录,并返回变更的相对路径列表与 `upsert` 函数,供调用者持久化数据。
243
+ 5. **独立批量存储模块 (`save.js`)**:供外部调用的独立工具模块,用于在事务中批量写入与删除。
170
244
 
171
245
  ## 技术栈
172
246
 
173
- - **Bun**:JavaScript 运行时及测试框架。
174
- - **Bun SQLite**:内置的轻量级、高性能 SQLite 实现。
247
+ - **Bun**:JavaScript 运行时与测试框架。
248
+ - **Bun SQLite**:内置 SQLite 实现。
175
249
  - **@1-/walk**:支持过滤规则的目录递归遍历工具。
176
250
  - **@3-/vb**:Varint(可变字节)编码与解码器。
177
251
  - **@3-/binmap / @3-/binset**:针对二进制键优化的 Map 和 Set 容器。
@@ -181,19 +255,19 @@ graph TD
181
255
  ```
182
256
  .
183
257
  ├── src
184
- │ ├── _.js # 核心流程控制器,调度各模块完成增量同步
258
+ │ ├── _.js # 核心流程控制器,调度各模块并返回变更及更新函数
185
259
  │ ├── dirWalk.js # 遍历目录并比对元数据,输出变更队列
186
260
  │ ├── hash.js # 将文件相对路径编码或计算为固定 16 字节键
187
261
  │ ├── load.js # 查询数据库现有记录,若数据表缺失则执行初始化
188
- │ ├── save.js # 执行批量写入与删除操作
262
+ │ ├── save.js # 独立导出的批量持久化与删除辅助函数
189
263
  │ ├── sqlite.js # 创建并配置 SQLite 数据库实例
190
264
  │ └── trans.js # 封装 SQLite 事务,提供异常回滚机制
191
- └── tests # 单元测试模块
265
+ └── tests # 单元测试目录
192
266
  ```
193
267
 
194
268
  ## 历史故事
195
269
 
196
- SQLite 的诞生与军事应用密切相关。2000 年,D. Richard Hipp 在为美国海军陆战队设计导弹驱逐舰板载损害控制系统软件时,遇到商业数据库由于配置复杂、日常需要专业维护且一旦连接丢失便会导致整个软件瘫痪的问题。Hipp 随即着手设计了一套无需任何独立服务器、零配置且直接对本地文件进行读写的嵌入式数据库,这便是 SQLite。
270
+ SQLite 的诞生源自海军军工项目。2000 年,D. Richard Hipp 为美国海军陆战队设计导弹驱逐舰板载损害控制软件时,遭遇商业数据库因配置复杂、日常维护繁琐且连接丢失即导致系统瘫痪之痛点。Hipp 随后设计出免服务器配置、直接读写本地文件之嵌入式数据库,即 SQLite。
197
271
 
198
- 为极限节约磁盘空间和降低读写延迟,SQLite 广泛应用了 Varint(可变字节整型)编码。在这种编码下,数值较小的整数(如常见的文件大小、序列号)仅占用 1 个字节,只有大数值才会占用更多字节。本项目中对文件大小和修改时间采用同样的压缩设计,从而秉承了 SQLite 极致节约空间与高效率的系统设计哲学。
272
+ 为了节省磁盘空间与降低读写延迟,SQLite 广泛应用了 Varint(可变字节整型)编码。在这种编码下,数值较小的整数仅占用 1 字节,只有大数值才会占用更多字节。本项目中对文件大小和修改时间采用同样的压缩设计,秉承了 SQLite 节省空间与高效之设计哲学。
199
273
  ../doc/zh/about.md
package/_.js CHANGED
@@ -3,25 +3,39 @@ import vbE from "@3-/vb/vbE.js";
3
3
  import sqlite from "./sqlite.js";
4
4
  import load from "./load.js";
5
5
  import dirWalk from "./dirWalk.js";
6
- import save from "./save.js";
6
+ import { stat } from "node:fs/promises";
7
+ import { join } from "node:path";
8
+ import int from "@3-/int";
9
+ import hash from "./hash.js";
10
+ import trans from "./trans.js";
7
11
 
8
12
  export default async (dir, db_path, ignore) => {
9
- using db = sqlite(db_path);
10
- const existing = new BinMap(),
13
+ const db = sqlite(db_path),
14
+ existing = new BinMap(),
11
15
  db_rows = load(db);
12
16
 
13
- for (const row of db_rows) {
14
- existing.set(row.hash, vbE([row.size, row.mtime]));
15
- }
17
+ db_rows.forEach(({ hash, size, mtime }) => existing.set(hash, vbE([size, mtime])));
16
18
 
17
19
  const [scanned, to_update] = await dirWalk(dir, existing, ignore),
18
- to_delete = [];
19
- for (const row of db_rows) {
20
- if (!scanned.has(row.hash)) {
21
- to_delete.push(row.hash);
22
- }
20
+ to_delete = db_rows.filter(({ hash }) => !scanned.has(hash)).map(({ hash }) => hash);
21
+
22
+ if (to_delete.length > 0) {
23
+ trans(db, () => {
24
+ const del = db.prepare("DELETE FROM scanMtimeLen WHERE hash=?");
25
+ to_delete.forEach((h) => del.run(h));
26
+ });
23
27
  }
24
28
 
25
- save(db, to_update, to_delete);
26
- return to_update.map(([rel_path]) => rel_path);
29
+ const insert = db.prepare("INSERT OR REPLACE INTO scanMtimeLen(hash,size,mtime)VALUES(?,?,?)"),
30
+ upsert = async (rel_path) => {
31
+ const fp = join(dir, rel_path),
32
+ { size, mtimeMs } = await stat(fp),
33
+ mtime = int(mtimeMs),
34
+ h = hash(rel_path);
35
+ insert.run(h, size, mtime);
36
+ };
37
+
38
+ upsert[Symbol.dispose] = () => db.close();
39
+
40
+ return [to_update.map(([rel_path]) => rel_path), upsert];
27
41
  };
package/dirWalk.js CHANGED
@@ -13,7 +13,7 @@ export default async (dir, existing, ignore) => {
13
13
  to_update = [];
14
14
 
15
15
  await walkRelIgnore(dir, async (kind, rel_path) => {
16
- if (ignore && ignore(kind, rel_path)) {
16
+ if (ignore && ignore(kind, rel_path) === false) {
17
17
  return false;
18
18
  }
19
19
  if (kind === FILE) {
package/hash.js CHANGED
@@ -3,5 +3,5 @@ import utf8e from "@3-/utf8/utf8e.js";
3
3
 
4
4
  export default (str) => {
5
5
  const buf = utf8e(str);
6
- return buf.length <= 16 ? buf : createHash("md5").update(buf).digest();
6
+ return buf.length <= 16 ? buf : new Uint8Array(createHash("md5").update(buf).digest());
7
7
  };
package/load.js CHANGED
@@ -2,10 +2,10 @@ const SQLITE_ERROR = 1;
2
2
 
3
3
  export default (db) => {
4
4
  try {
5
- return db.prepare("SELECT hash,size,mtime FROM file").all();
5
+ return db.prepare("SELECT hash,size,mtime FROM scanMtimeLen").all();
6
6
  } catch (err) {
7
7
  if (err.errno === SQLITE_ERROR) {
8
- db.exec("CREATE TABLE file(hash PRIMARY KEY,size INT UNSIGNED,mtime INT UNSIGNED)");
8
+ db.exec("CREATE TABLE scanMtimeLen(hash PRIMARY KEY,size INT UNSIGNED,mtime INT UNSIGNED)");
9
9
  return [];
10
10
  }
11
11
  throw err;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@1-/scan",
3
- "version": "0.1.3",
3
+ "version": "0.1.5",
4
4
  "description": "Incrementally scan directory files and track metadata in SQLite / 增量扫描目录文件并使用 SQLite 记录元数据",
5
5
  "keywords": [
6
6
  "scan",
package/save.js CHANGED
@@ -4,16 +4,14 @@ export default (db, to_update, to_delete) => {
4
4
  if (to_update.length > 0 || to_delete.length > 0) {
5
5
  trans(db, () => {
6
6
  if (to_update.length > 0) {
7
- const insert = db.prepare("INSERT OR REPLACE INTO file(hash,size,mtime)VALUES(?,?,?)");
8
- for (const [_, h, size, mtime] of to_update) {
9
- insert.run(h, size, mtime);
10
- }
7
+ const insert = db.prepare(
8
+ "INSERT OR REPLACE INTO scanMtimeLen(hash,size,mtime)VALUES(?,?,?)",
9
+ );
10
+ to_update.forEach(([_, h, size, mtime]) => insert.run(h, size, mtime));
11
11
  }
12
12
  if (to_delete.length > 0) {
13
- const del = db.prepare("DELETE FROM file WHERE hash=?");
14
- for (const h of to_delete) {
15
- del.run(h);
16
- }
13
+ const del = db.prepare("DELETE FROM scanMtimeLen WHERE hash=?");
14
+ to_delete.forEach((h) => del.run(h));
17
15
  }
18
16
  });
19
17
  }