pi-hashline-edit-pro 0.17.13 → 0.18.0

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pi-hashline-edit-pro",
3
- "version": "0.17.13",
3
+ "version": "0.18.0",
4
4
  "type": "module",
5
5
  "description": "Strict hashline read/replace tool for pi-coding-agent with hash-anchored edits (3-char, 18-bit, perfect hashing)",
6
6
  "main": "index.ts",
@@ -24,8 +24,7 @@
24
24
  "src",
25
25
  "prompts",
26
26
  "README.md",
27
- "LICENSE",
28
- "scripts"
27
+ "LICENSE"
29
28
  ],
30
29
  "pi": {
31
30
  "extensions": [
@@ -33,10 +32,8 @@
33
32
  ]
34
33
  },
35
34
  "dependencies": {
36
- "better-sqlite3": "^13.0.1",
37
35
  "diff": "^8.0.2",
38
36
  "file-type": "^21.3.0",
39
- "sql.js": "1.11",
40
37
  "xxhash-wasm": "^1.1.0"
41
38
  },
42
39
  "peerDependencies": {
@@ -47,23 +44,18 @@
47
44
  "test": "vitest run",
48
45
  "test:watch": "vitest",
49
46
  "lint": "eslint 'src/**/*.ts' 'index.ts'",
50
- "typecheck": "tsc --noEmit",
51
- "postinstall": "node scripts/ensure-better-sqlite3.cjs",
52
- "preuninstall": "node scripts/cleanup-better-sqlite3.cjs"
47
+ "typecheck": "tsc --noEmit"
53
48
  },
54
49
  "devDependencies": {
55
50
  "@earendil-works/pi-coding-agent": "^0.74.0",
56
51
  "@eslint/js": "^10.0.1",
57
- "@types/better-sqlite3": "^7.6.13",
58
- "@types/node": "^22.0.0",
59
- "@types/sql.js": "^1.4.11",
52
+ "@types/node": "^24.0.0",
60
53
  "eslint": "^10.7.0",
61
54
  "typescript": "^5.8.0",
62
55
  "typescript-eslint": "^8.65.0",
63
56
  "vitest": "^4.1.8"
64
57
  },
65
58
  "allowScripts": {
66
- "better-sqlite3@13.0.1": true,
67
59
  "@google/genai@1.52.0": true,
68
60
  "koffi@2.16.2": true,
69
61
  "protobufjs@7.6.4": true
package/src/hash-store.ts CHANGED
@@ -1,10 +1,10 @@
1
- import { readFileSync, writeFileSync, existsSync } from "fs";
1
+ import { existsSync } from "fs";
2
2
  import { readFile, rename, mkdir, stat } from "fs/promises";
3
+ import { DatabaseSync } from "node:sqlite";
3
4
  import { hashStorePath, hashStoreDir, legacyHashStorePath } from "./paths";
4
5
  import { errCode } from "./utils";
5
6
  import { initHasher, contentChecksum } from "./hashline/hasher";
6
7
  import { HASH_STORE_VERSION, HASH_STORE_BUSY_TIMEOUT } from "./constants";
7
- import initSqlJs from "sql.js";
8
8
 
9
9
  type SqlParams = (string | number)[];
10
10
 
@@ -17,7 +17,7 @@ interface Prepared {
17
17
 
18
18
  export interface HashStore {
19
19
  readonly stmts: Prepared;
20
- readonly engine: "better-sqlite3" | "sql.js";
20
+ readonly engine: "node:sqlite";
21
21
  }
22
22
 
23
23
  interface LegacySnapshot {
@@ -36,38 +36,15 @@ function isValidSnapshot(value: unknown): value is LegacySnapshot {
36
36
  return true;
37
37
  }
38
38
 
39
+ let cachedDb: { path: string; db: DatabaseSync; stmts: Prepared } | null = null;
39
40
 
40
-
41
- interface BackendHandle {
42
- store: HashStore;
43
- close: () => void;
44
- transact: (fn: () => void) => void;
45
- prepare: (sql: string) => { run: (...params: unknown[]) => void; free: () => void };
46
- }
47
-
48
- let cachedHandle: { path: string; handle: BackendHandle } | null = null;
49
-
50
-
51
-
52
- let BetterDatabase: any = undefined;
53
-
54
- async function tryLoadBetter(): Promise<boolean> {
55
- if (BetterDatabase !== undefined) return BetterDatabase !== null;
56
- try {
57
- const mod = await import("better-sqlite3");
58
- BetterDatabase = mod.default || mod;
59
- return true;
60
- } catch {
61
- BetterDatabase = null;
62
- return false;
63
- }
64
- }
65
-
66
- function openBetterDb(storePath: string): BackendHandle {
67
- const db = new BetterDatabase(storePath);
68
- db.pragma("journal_mode = WAL");
69
- db.pragma("synchronous = NORMAL");
70
- db.pragma(`busy_timeout = ${HASH_STORE_BUSY_TIMEOUT}`);
41
+ function openDb(storePath: string): { db: DatabaseSync; stmts: Prepared } {
42
+ const db = new DatabaseSync(storePath, {
43
+ timeout: HASH_STORE_BUSY_TIMEOUT,
44
+ defensive: false,
45
+ } as any);
46
+ db.exec("PRAGMA journal_mode = WAL");
47
+ db.exec("PRAGMA synchronous = NORMAL");
71
48
  db.exec(
72
49
  "CREATE TABLE IF NOT EXISTS snapshots (" +
73
50
  "path TEXT PRIMARY KEY, " +
@@ -93,169 +70,54 @@ function openBetterDb(storePath: string): BackendHandle {
93
70
  upsert: (...params) => { upsertStmt.run(...params); },
94
71
  };
95
72
 
96
- return {
97
- store: { stmts, engine: "better-sqlite3" },
98
- close: () => { db.close(); },
99
- transact: (fn) => { db.transaction(fn).immediate(); },
100
- prepare: (sql) => {
101
- const stmt = db.prepare(sql);
102
- return { run: (...params) => stmt.run(...params), free: () => {} };
103
- },
104
- };
105
- }
106
-
107
-
108
-
109
- let SqlJsDatabase: any = null;
110
- let sqlJsPromise: Promise<void> | null = null;
111
-
112
- async function ensureSqlJs(): Promise<void> {
113
- if (sqlJsPromise) return sqlJsPromise;
114
- sqlJsPromise = initSqlJs().then((SQL) => { SqlJsDatabase = SQL.Database; });
115
- return sqlJsPromise;
116
- }
117
-
118
- function openSqlJsDb(storePath: string): BackendHandle {
119
- const data = existsSync(storePath) ? new Uint8Array(readFileSync(storePath)) : undefined;
120
- const db = new SqlJsDatabase(data);
121
-
122
- db.run(
123
- "CREATE TABLE IF NOT EXISTS snapshots (" +
124
- "path TEXT PRIMARY KEY, " +
125
- "checksum TEXT NOT NULL, " +
126
- "line_count INTEGER NOT NULL, " +
127
- "hashes TEXT NOT NULL, " +
128
- "updated_at INTEGER NOT NULL" +
129
- ")"
130
- );
131
-
132
- function save() {
133
- writeFileSync(storePath, Buffer.from(db.export()));
134
- }
135
-
136
- save();
137
-
138
- const stmts: Prepared = {
139
- get: (...params) => {
140
- const stmt = db.prepare("SELECT hashes FROM snapshots WHERE path = ? AND checksum = ? AND line_count = ?");
141
- stmt.bind(params);
142
- let result: Record<string, unknown> | undefined;
143
- if (stmt.step()) result = stmt.getAsObject() as Record<string, unknown>;
144
- stmt.free();
145
- return result;
146
- },
147
- allPaths: (...params) => {
148
- const stmt = db.prepare("SELECT path FROM snapshots");
149
- if (params.length > 0) stmt.bind(params);
150
- const results: Record<string, unknown>[] = [];
151
- while (stmt.step()) results.push(stmt.getAsObject() as Record<string, unknown>);
152
- stmt.free();
153
- return results;
154
- },
155
- deleteOne: (...params) => {
156
- if (params.length > 0) db.run("DELETE FROM snapshots WHERE path = ?", params);
157
- else db.run("DELETE FROM snapshots WHERE path = ?");
158
- },
159
- upsert: (...params) => {
160
- db.run(
161
- "INSERT INTO snapshots (path, checksum, line_count, hashes, updated_at) VALUES (?, ?, ?, ?, ?) " +
162
- "ON CONFLICT(path) DO UPDATE SET checksum = excluded.checksum, line_count = excluded.line_count, hashes = excluded.hashes, updated_at = excluded.updated_at",
163
- params
164
- );
165
- },
166
- };
167
-
168
- return {
169
- store: { stmts, engine: "sql.js" },
170
- close: () => { db.close(); },
171
- transact: (fn) => {
172
- db.run("BEGIN IMMEDIATE");
173
- try { fn(); db.run("COMMIT"); save(); } catch (e) { db.run("ROLLBACK"); throw e; }
174
- },
175
- prepare: (sql) => {
176
- const stmt = db.prepare(sql);
177
- return {
178
- run: (...params) => { stmt.bind(params); stmt.step(); stmt.reset(); },
179
- free: () => { stmt.free(); },
180
- };
181
- },
182
- };
73
+ return { db, stmts };
183
74
  }
184
75
 
185
-
186
-
187
- let backendPromise: Promise<void> | null = null;
188
-
189
- async function initBackend(): Promise<void> {
190
- if (backendPromise) return backendPromise;
191
- backendPromise = (async () => {
192
- const hasBetter = await tryLoadBetter();
193
- if (!hasBetter) await ensureSqlJs();
194
- })();
195
- return backendPromise;
196
- }
197
-
198
-
199
-
200
76
  export async function loadHashStore(): Promise<HashStore> {
201
77
  const storePath = hashStorePath();
202
- if (cachedHandle && cachedHandle.path === storePath) {
203
- return cachedHandle.handle.store;
78
+ if (cachedDb && cachedDb.path === storePath && cachedDb.db.isOpen) {
79
+ return { stmts: cachedDb.stmts, engine: "node:sqlite" };
204
80
  }
205
81
 
206
82
  shutdownHashStore();
207
83
 
208
84
  await initHasher();
209
85
  await mkdir(hashStoreDir(), { recursive: true });
210
- await initBackend();
211
86
 
212
87
  const existed = existsSync(storePath);
213
-
214
- let handle: BackendHandle;
215
- if (BetterDatabase) {
216
- try {
217
- handle = openBetterDb(storePath);
218
- } catch {
219
- const debug = process.env.PI_HASHLINE_DEBUG === "1" || process.env.PI_HASHLINE_DEBUG === "true";
220
- if (debug) {
221
- console.error('better-sqlite3 native binding failed, falling back to sql.js');
222
- }
223
- BetterDatabase = null;
224
- await ensureSqlJs();
225
- handle = openSqlJsDb(storePath);
226
- }
227
- } else {
228
- await ensureSqlJs();
229
- handle = openSqlJsDb(storePath);
230
- }
88
+ const { db, stmts } = openDb(storePath);
231
89
 
232
90
  if (!existed) {
233
- await migrateLegacy(handle);
91
+ await migrateLegacy(db);
234
92
  }
235
93
 
236
- cachedHandle = { path: storePath, handle };
237
- return handle.store;
94
+ cachedDb = { path: storePath, db, stmts };
95
+ return { stmts, engine: "node:sqlite" };
238
96
  }
239
97
 
240
98
  export function shutdownHashStore(): void {
241
- if (cachedHandle) {
242
- cachedHandle.handle.close();
243
- cachedHandle = null;
99
+ if (cachedDb) {
100
+ cachedDb.db.close();
101
+ cachedDb = null;
244
102
  }
245
103
  }
246
104
 
247
-
248
-
249
- function withStore(store: HashStore, fn: () => void): void {
250
- const h = cachedHandle?.handle;
251
- if (h && h.store === store) {
252
- h.transact(fn);
105
+ function withStore(fn: () => void): void {
106
+ if (cachedDb) {
107
+ cachedDb.db.exec("BEGIN IMMEDIATE");
108
+ try {
109
+ fn();
110
+ cachedDb.db.exec("COMMIT");
111
+ } catch (e) {
112
+ cachedDb.db.exec("ROLLBACK");
113
+ throw e;
114
+ }
253
115
  } else {
254
116
  fn();
255
117
  }
256
118
  }
257
119
 
258
- async function migrateLegacy(handle: BackendHandle): Promise<void> {
120
+ async function migrateLegacy(db: DatabaseSync): Promise<void> {
259
121
  const legacyPath = legacyHashStorePath();
260
122
  let content: string;
261
123
  try {
@@ -290,13 +152,17 @@ async function migrateLegacy(handle: BackendHandle): Promise<void> {
290
152
  }
291
153
 
292
154
  if (rows.length > 0) {
293
- handle.transact(() => {
294
- const stmt = handle.prepare(
155
+ db.exec("BEGIN IMMEDIATE");
156
+ try {
157
+ const stmt = db.prepare(
295
158
  "INSERT OR REPLACE INTO snapshots (path, checksum, line_count, hashes, updated_at) VALUES (?, ?, ?, ?, ?)"
296
159
  );
297
160
  for (const row of rows) stmt.run(...row);
298
- stmt.free();
299
- });
161
+ db.exec("COMMIT");
162
+ } catch (e) {
163
+ db.exec("ROLLBACK");
164
+ throw e;
165
+ }
300
166
  }
301
167
 
302
168
  try {
@@ -306,8 +172,6 @@ async function migrateLegacy(handle: BackendHandle): Promise<void> {
306
172
  }
307
173
  }
308
174
 
309
-
310
-
311
175
  export function getSnapshot(
312
176
  store: HashStore,
313
177
  path: string,
@@ -327,13 +191,13 @@ export function upsertSnapshot(
327
191
  hashes: string[],
328
192
  ): void {
329
193
  const hashesJson = JSON.stringify(hashes);
330
- withStore(store, () => {
194
+ withStore(() => {
331
195
  store.stmts.upsert(path, checksum, lineCount, hashesJson, Date.now());
332
196
  });
333
197
  }
334
198
 
335
199
  export function deleteSnapshot(store: HashStore, path: string): void {
336
- withStore(store, () => {
200
+ withStore(() => {
337
201
  store.stmts.deleteOne(path);
338
202
  });
339
203
  }
@@ -349,7 +213,7 @@ export async function pruneMissing(store: HashStore): Promise<void> {
349
213
  }
350
214
  }
351
215
  if (missing.length === 0) return;
352
- withStore(store, () => {
216
+ withStore(() => {
353
217
  for (const path of missing) store.stmts.deleteOne(path);
354
218
  });
355
219
  }
@@ -1,21 +0,0 @@
1
- const fs = require("fs");
2
- const path = require("path");
3
-
4
- function cleanNodeModules(nm) {
5
- if (!fs.existsSync(nm)) return;
6
- const entries = fs.readdirSync(nm, { withFileTypes: true });
7
- for (const entry of entries) {
8
- if (entry.name.startsWith(".better-sqlite3-")) {
9
- const full = path.join(nm, entry.name);
10
- try {
11
- fs.rmSync(full, { recursive: true, force: true });
12
- console.error("Cleaned up stale better-sqlite3 build artifact:", entry.name);
13
- } catch {
14
- }
15
- }
16
- }
17
- }
18
-
19
- const pkgDir = path.resolve(__dirname, "..");
20
- cleanNodeModules(path.resolve(pkgDir, "node_modules"));
21
- cleanNodeModules(path.resolve(pkgDir, "..", "node_modules"));
@@ -1,60 +0,0 @@
1
- const { execSync } = require("child_process");
2
- const fs = require("fs");
3
- const path = require("path");
4
-
5
- const root = path.resolve(__dirname, "..");
6
- const bsqlDir = path.join(root, "node_modules", "better-sqlite3");
7
-
8
- function removeStaleArtifacts() {
9
- for (const dir of [root, bsqlDir]) {
10
- if (!fs.existsSync(dir)) continue;
11
- const entries = fs.readdirSync(dir, { withFileTypes: true });
12
- for (const entry of entries) {
13
- if (entry.name.startsWith(".better-sqlite3-")) {
14
- const full = path.join(dir, entry.name);
15
- try {
16
- fs.rmSync(full, { recursive: true, force: true });
17
- console.error("Removed stale artifact:", entry.name);
18
- } catch {
19
- }
20
- }
21
- }
22
- }
23
- const buildDir = path.join(bsqlDir, "build");
24
- if (fs.existsSync(buildDir)) {
25
- fs.rmSync(buildDir, { recursive: true, force: true });
26
- }
27
- }
28
-
29
- try {
30
- require(bsqlDir);
31
- process.exit(0);
32
- } catch (e) {
33
- const msg = e && typeof e.message === "string" ? e.message : String(e);
34
- if (/GLIBC|Cannot find module|dlo|not found/i.test(msg)) {
35
- console.error("better-sqlite3 prebuilt incompatible, rebuilding from source...");
36
- removeStaleArtifacts();
37
- const prebuildDir = path.join(bsqlDir, "prebuilds");
38
- if (fs.existsSync(prebuildDir)) {
39
- const platform = process.platform + "-" + process.arch;
40
- const prebuilt = path.join(prebuildDir, platform + ".node");
41
- if (fs.existsSync(prebuilt)) {
42
- fs.unlinkSync(prebuilt);
43
- console.error("Removed incompatible prebuilt:", platform + ".node");
44
- }
45
- }
46
-
47
- try {
48
- execSync("npx --yes node-gyp rebuild", {
49
- cwd: bsqlDir,
50
- stdio: "inherit",
51
- timeout: 300000,
52
- });
53
- console.error("better-sqlite3 rebuilt successfully from source.");
54
- } catch (rebuildErr) {
55
- console.error("better-sqlite3 rebuild failed:", rebuildErr.message);
56
- console.error("Will fall back to sql.js at runtime.");
57
- process.exit(0);
58
- }
59
- }
60
- }