pi-para 0.7.1 → 0.7.3
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/CHANGELOG.md +14 -0
- package/CONFIGURATION.md +2 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +47 -23
- package/dist/index.js.map +1 -1
- package/dist/scheduler/controls.d.ts.map +1 -1
- package/dist/scheduler/controls.js +2 -1
- package/dist/scheduler/controls.js.map +1 -1
- package/dist/scheduler/index.d.ts +10 -0
- package/dist/scheduler/index.d.ts.map +1 -1
- package/dist/scheduler/index.js +62 -2
- package/dist/scheduler/index.js.map +1 -1
- package/dist/scheduler/session-capture.d.ts +8 -0
- package/dist/scheduler/session-capture.d.ts.map +1 -1
- package/dist/scheduler/session-capture.js +58 -4
- package/dist/scheduler/session-capture.js.map +1 -1
- package/dist/scheduler/state.d.ts +3 -0
- package/dist/scheduler/state.d.ts.map +1 -1
- package/dist/scheduler/state.js +18 -0
- package/dist/scheduler/state.js.map +1 -1
- package/dist/state.d.ts +6 -1
- package/dist/state.d.ts.map +1 -1
- package/dist/state.js +55 -11
- package/dist/state.js.map +1 -1
- package/package.json +1 -1
- package/src/index.ts +45 -22
- package/src/scheduler/controls.ts +2 -1
- package/src/scheduler/index.ts +60 -2
- package/src/scheduler/session-capture.ts +68 -6
- package/src/scheduler/state.ts +21 -0
- package/src/state.ts +54 -10
package/src/state.ts
CHANGED
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
|
|
7
7
|
import Database from "better-sqlite3";
|
|
8
8
|
import { createHash } from "node:crypto";
|
|
9
|
-
import { readFileSync } from "node:fs";
|
|
9
|
+
import { readFileSync, statSync } from "node:fs";
|
|
10
10
|
import { join } from "node:path";
|
|
11
11
|
|
|
12
12
|
export interface PageSummary {
|
|
@@ -62,17 +62,48 @@ export class StateDB {
|
|
|
62
62
|
updated_at TEXT NOT NULL
|
|
63
63
|
);
|
|
64
64
|
`);
|
|
65
|
+
// Stat fast path for isProcessed(): when size+mtime are unchanged since
|
|
66
|
+
// the recorded hash, skip re-hashing the whole session file. Critical for
|
|
67
|
+
// startup catch-up scans over registries with very large session JSONLs.
|
|
68
|
+
try {
|
|
69
|
+
this.db.exec("ALTER TABLE processed_sessions ADD COLUMN file_size INTEGER");
|
|
70
|
+
} catch { /* column exists */ }
|
|
71
|
+
try {
|
|
72
|
+
this.db.exec("ALTER TABLE processed_sessions ADD COLUMN file_mtime_ms INTEGER");
|
|
73
|
+
} catch { /* column exists */ }
|
|
65
74
|
}
|
|
66
75
|
|
|
67
|
-
/**
|
|
76
|
+
/**
|
|
77
|
+
* Check if a session file has already been processed with the same content.
|
|
78
|
+
* Uses a stat (size+mtime) fast path before falling back to a full content
|
|
79
|
+
* hash, so registry-wide scans stay cheap even with multi-hundred-MB
|
|
80
|
+
* session files. A hash match under a changed stat refreshes the cache.
|
|
81
|
+
*/
|
|
68
82
|
isProcessed(sessionPath: string): boolean {
|
|
69
83
|
const row = this.db
|
|
70
|
-
.prepare("SELECT content_hash FROM processed_sessions WHERE session_path = ?")
|
|
71
|
-
.get(sessionPath) as { content_hash: string } | undefined;
|
|
84
|
+
.prepare("SELECT content_hash, file_size, file_mtime_ms FROM processed_sessions WHERE session_path = ?")
|
|
85
|
+
.get(sessionPath) as { content_hash: string; file_size: number | null; file_mtime_ms: number | null } | undefined;
|
|
72
86
|
if (!row) return false;
|
|
73
87
|
|
|
88
|
+
let stat: { size: number; mtimeMs: number };
|
|
89
|
+
try {
|
|
90
|
+
stat = statSync(sessionPath);
|
|
91
|
+
} catch {
|
|
92
|
+
return false;
|
|
93
|
+
}
|
|
94
|
+
// mtimeMs is a float and loses sub-millisecond precision across
|
|
95
|
+
// utimes/copy round-trips; compare with a small tolerance. Size is exact.
|
|
96
|
+
if (row.file_size !== null && row.file_mtime_ms !== null &&
|
|
97
|
+
row.file_size === stat.size && Math.abs(row.file_mtime_ms - stat.mtimeMs) <= 2) {
|
|
98
|
+
return true;
|
|
99
|
+
}
|
|
100
|
+
|
|
74
101
|
const currentHash = hashFile(sessionPath);
|
|
75
|
-
|
|
102
|
+
if (row.content_hash !== currentHash) return false;
|
|
103
|
+
this.db
|
|
104
|
+
.prepare("UPDATE processed_sessions SET file_size = ?, file_mtime_ms = ? WHERE session_path = ?")
|
|
105
|
+
.run(stat.size, Math.floor(stat.mtimeMs), sessionPath);
|
|
106
|
+
return true;
|
|
76
107
|
}
|
|
77
108
|
|
|
78
109
|
/** Get the stored hash for a session (or null if not processed). */
|
|
@@ -91,11 +122,12 @@ export class StateDB {
|
|
|
91
122
|
pagesUpdated: string[],
|
|
92
123
|
): void {
|
|
93
124
|
const hash = hashFile(sessionPath);
|
|
125
|
+
const stat = statFile(sessionPath);
|
|
94
126
|
this.db
|
|
95
127
|
.prepare(`
|
|
96
128
|
INSERT OR REPLACE INTO processed_sessions
|
|
97
|
-
(session_path, content_hash, processed_at, scope, pages_created, pages_updated, error)
|
|
98
|
-
VALUES (?, ?, ?, ?, ?, ?, NULL)
|
|
129
|
+
(session_path, content_hash, processed_at, scope, pages_created, pages_updated, error, file_size, file_mtime_ms)
|
|
130
|
+
VALUES (?, ?, ?, ?, ?, ?, NULL, ?, ?)
|
|
99
131
|
`)
|
|
100
132
|
.run(
|
|
101
133
|
sessionPath,
|
|
@@ -104,19 +136,23 @@ export class StateDB {
|
|
|
104
136
|
scope,
|
|
105
137
|
JSON.stringify(pagesCreated),
|
|
106
138
|
JSON.stringify(pagesUpdated),
|
|
139
|
+
stat?.size ?? null,
|
|
140
|
+
stat ? Math.floor(stat.mtimeMs) : null,
|
|
107
141
|
);
|
|
108
142
|
}
|
|
109
143
|
|
|
110
144
|
/** Record a failed processing attempt. */
|
|
111
145
|
recordFailure(sessionPath: string, scope: string, error: string): void {
|
|
112
146
|
const hash = hashFile(sessionPath);
|
|
147
|
+
const stat = statFile(sessionPath);
|
|
113
148
|
this.db
|
|
114
149
|
.prepare(`
|
|
115
150
|
INSERT OR REPLACE INTO processed_sessions
|
|
116
|
-
(session_path, content_hash, processed_at, scope, pages_created, pages_updated, error)
|
|
117
|
-
VALUES (?, ?, ?, ?, '[]', '[]', ?)
|
|
151
|
+
(session_path, content_hash, processed_at, scope, pages_created, pages_updated, error, file_size, file_mtime_ms)
|
|
152
|
+
VALUES (?, ?, ?, ?, '[]', '[]', ?, ?, ?)
|
|
118
153
|
`)
|
|
119
|
-
.run(sessionPath, hash, new Date().toISOString(), scope, error
|
|
154
|
+
.run(sessionPath, hash, new Date().toISOString(), scope, error,
|
|
155
|
+
stat?.size ?? null, stat ? Math.floor(stat.mtimeMs) : null);
|
|
120
156
|
}
|
|
121
157
|
|
|
122
158
|
/** Get all failed sessions for retry. */
|
|
@@ -241,6 +277,14 @@ export class StateDB {
|
|
|
241
277
|
}
|
|
242
278
|
}
|
|
243
279
|
|
|
280
|
+
function statFile(filePath: string): { size: number; mtimeMs: number } | null {
|
|
281
|
+
try {
|
|
282
|
+
return statSync(filePath);
|
|
283
|
+
} catch {
|
|
284
|
+
return null;
|
|
285
|
+
}
|
|
286
|
+
}
|
|
287
|
+
|
|
244
288
|
function hashFile(filePath: string): string {
|
|
245
289
|
try {
|
|
246
290
|
const content = readFileSync(filePath);
|