@npm-safe/core-dsh 1.0.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.
Files changed (86) hide show
  1. package/LICENSE +204 -0
  2. package/dist/index.d.ts +513 -0
  3. package/dist/index.d.ts.map +1 -0
  4. package/dist/index.js +711 -0
  5. package/dist/index.js.map +1 -0
  6. package/dist/llm/anthropic.d.ts +47 -0
  7. package/dist/llm/anthropic.d.ts.map +1 -0
  8. package/dist/llm/anthropic.js +161 -0
  9. package/dist/llm/anthropic.js.map +1 -0
  10. package/dist/llm/gemini.d.ts +47 -0
  11. package/dist/llm/gemini.d.ts.map +1 -0
  12. package/dist/llm/gemini.js +165 -0
  13. package/dist/llm/gemini.js.map +1 -0
  14. package/dist/llm/llm-config.d.ts +97 -0
  15. package/dist/llm/llm-config.d.ts.map +1 -0
  16. package/dist/llm/llm-config.js +188 -0
  17. package/dist/llm/llm-config.js.map +1 -0
  18. package/dist/llm/parse.d.ts +95 -0
  19. package/dist/llm/parse.d.ts.map +1 -0
  20. package/dist/llm/parse.js +158 -0
  21. package/dist/llm/parse.js.map +1 -0
  22. package/dist/llm/provider.d.ts +122 -0
  23. package/dist/llm/provider.d.ts.map +1 -0
  24. package/dist/llm/provider.js +206 -0
  25. package/dist/llm/provider.js.map +1 -0
  26. package/dist/registry/client.d.ts +164 -0
  27. package/dist/registry/client.d.ts.map +1 -0
  28. package/dist/registry/client.js +378 -0
  29. package/dist/registry/client.js.map +1 -0
  30. package/dist/registry/types.d.ts +226 -0
  31. package/dist/registry/types.d.ts.map +1 -0
  32. package/dist/registry/types.js +32 -0
  33. package/dist/registry/types.js.map +1 -0
  34. package/dist/registry/validator.d.ts +87 -0
  35. package/dist/registry/validator.d.ts.map +1 -0
  36. package/dist/registry/validator.js +214 -0
  37. package/dist/registry/validator.js.map +1 -0
  38. package/dist/scanner/ci-scan.d.ts +82 -0
  39. package/dist/scanner/ci-scan.d.ts.map +1 -0
  40. package/dist/scanner/ci-scan.js +130 -0
  41. package/dist/scanner/ci-scan.js.map +1 -0
  42. package/dist/scanner/rule-config.d.ts +61 -0
  43. package/dist/scanner/rule-config.d.ts.map +1 -0
  44. package/dist/scanner/rule-config.js +103 -0
  45. package/dist/scanner/rule-config.js.map +1 -0
  46. package/dist/scanner/rule-loader.d.ts +28 -0
  47. package/dist/scanner/rule-loader.d.ts.map +1 -0
  48. package/dist/scanner/rule-loader.js +67 -0
  49. package/dist/scanner/rule-loader.js.map +1 -0
  50. package/dist/scanner/static-rules.d.ts +88 -0
  51. package/dist/scanner/static-rules.d.ts.map +1 -0
  52. package/dist/scanner/static-rules.js +723 -0
  53. package/dist/scanner/static-rules.js.map +1 -0
  54. package/dist/scanner/types.d.ts +177 -0
  55. package/dist/scanner/types.d.ts.map +1 -0
  56. package/dist/scanner/types.js +53 -0
  57. package/dist/scanner/types.js.map +1 -0
  58. package/dist/scheduler/rate-limiter.d.ts +74 -0
  59. package/dist/scheduler/rate-limiter.d.ts.map +1 -0
  60. package/dist/scheduler/rate-limiter.js +182 -0
  61. package/dist/scheduler/rate-limiter.js.map +1 -0
  62. package/dist/scheduler/refresh-scheduler.d.ts +201 -0
  63. package/dist/scheduler/refresh-scheduler.d.ts.map +1 -0
  64. package/dist/scheduler/refresh-scheduler.js +295 -0
  65. package/dist/scheduler/refresh-scheduler.js.map +1 -0
  66. package/dist/store/cache-manager.d.ts +166 -0
  67. package/dist/store/cache-manager.d.ts.map +1 -0
  68. package/dist/store/cache-manager.js +356 -0
  69. package/dist/store/cache-manager.js.map +1 -0
  70. package/dist/store/database.d.ts +81 -0
  71. package/dist/store/database.d.ts.map +1 -0
  72. package/dist/store/database.js +182 -0
  73. package/dist/store/database.js.map +1 -0
  74. package/dist/store/schema.d.ts +42 -0
  75. package/dist/store/schema.d.ts.map +1 -0
  76. package/dist/store/schema.js +126 -0
  77. package/dist/store/schema.js.map +1 -0
  78. package/dist/translator/provider.d.ts +152 -0
  79. package/dist/translator/provider.d.ts.map +1 -0
  80. package/dist/translator/provider.js +159 -0
  81. package/dist/translator/provider.js.map +1 -0
  82. package/dist/translator/types.d.ts +83 -0
  83. package/dist/translator/types.d.ts.map +1 -0
  84. package/dist/translator/types.js +58 -0
  85. package/dist/translator/types.js.map +1 -0
  86. package/package.json +42 -0
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cache-manager.d.ts","sourceRoot":"","sources":["../../src/store/cache-manager.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,356 @@
1
+ /**
2
+ * Cache read/write layer for @npm-safe/core.
3
+ *
4
+ * {@link CacheManager} sits on top of a {@link DatabaseManager} connection and
5
+ * provides typed accessors for the cached npm metadata, security scan reports,
6
+ * watchlist, and key-value settings tables. It owns the TTL policy for the
7
+ * `packages` table: every {@link CacheManager.setPackage} call stamps the row
8
+ * with an expiry timestamp (`ttl_until`) computed as `now + cacheTtlMs`, and
9
+ * {@link CacheManager.getPackage} refuses to return rows whose TTL has
10
+ * elapsed. Scheduling of refreshes is intentionally out of scope — that is
11
+ * the job of the refresh-scheduler; this module only reads and writes.
12
+ *
13
+ * All methods return `Promise`s for API consistency and future async backing
14
+ * stores, even though the underlying `better-sqlite3` calls are synchronous.
15
+ *
16
+ * @module store/cache-manager
17
+ */
18
+ import { SecurityLevel, } from "../scanner/types.js";
19
+ /**
20
+ * Default cache time-to-live for package metadata: 1 hour in milliseconds.
21
+ */
22
+ export const DEFAULT_CACHE_TTL_MS = 3_600_000;
23
+ /**
24
+ * Derives a {@link SecurityLevel} from a numeric static-scan score using the
25
+ * same thresholds as the static analyzer: `>=80` Safe, `>=50` Suspicious,
26
+ * `>=20` Dangerous, otherwise Unknown. The `security_reports` table persists
27
+ * only the numeric score (not the enum), so the level is reconstructed on
28
+ * read to satisfy the {@link StaticScanReport} shape.
29
+ *
30
+ * @param score - Numeric score from 0 to 100 (higher is safer).
31
+ * @returns The security level matching the score band.
32
+ */
33
+ function scoreToLevel(score) {
34
+ if (score >= 80)
35
+ return SecurityLevel.Safe;
36
+ if (score >= 50)
37
+ return SecurityLevel.Suspicious;
38
+ if (score >= 20)
39
+ return SecurityLevel.Dangerous;
40
+ return SecurityLevel.Unknown;
41
+ }
42
+ /**
43
+ * Normalizes a {@link PackageRepository} value to a plain string for storage
44
+ * in the `packages.repository` TEXT column. Structured descriptors are
45
+ * rendered as `type:url`; shorthand strings are stored verbatim.
46
+ *
47
+ * @param repo - The repository descriptor from registry metadata.
48
+ * @returns A string representation suitable for storage.
49
+ */
50
+ function repositoryToString(repo) {
51
+ if (repo === undefined)
52
+ return "";
53
+ if (typeof repo === "string")
54
+ return repo;
55
+ return `${repo.type}:${repo.url}`;
56
+ }
57
+ /**
58
+ * Cache read/write manager backed by a {@link DatabaseManager} connection.
59
+ *
60
+ * The manager is stateless beyond the TTL configuration and the injected
61
+ * database handle, so it is safe to construct one per request or to share a
62
+ * long-lived instance. All public methods are async-returning for API
63
+ * consistency; the underlying `better-sqlite3` calls are synchronous.
64
+ */
65
+ export class CacheManager {
66
+ /** Underlying better-sqlite3 connection accessor. */
67
+ db;
68
+ /** Cache TTL in milliseconds applied to newly written package rows. */
69
+ cacheTtlMs;
70
+ /**
71
+ * @param database - The {@link DatabaseManager} supplying the connection.
72
+ * @param options - Optional configuration; see {@link CacheManagerOptions}.
73
+ */
74
+ constructor(database, options) {
75
+ this.db = database.getDb();
76
+ this.cacheTtlMs = options?.cacheTtlMs ?? DEFAULT_CACHE_TTL_MS;
77
+ }
78
+ /**
79
+ * Returns cached package metadata for `name` if the row is still fresh
80
+ * (i.e. `ttl_until >= now`). Returns `null` when the row is missing or
81
+ * stale; callers are expected to re-fetch from the registry and call
82
+ * {@link CacheManager.setPackage} to refresh the cache.
83
+ *
84
+ * @param name - Fully-qualified package name (scope included when scoped).
85
+ * @returns The cached {@link PackageMetadata}, or `null` if absent/stale.
86
+ */
87
+ async getPackage(name) {
88
+ const row = this.db
89
+ .prepare("SELECT name, latest_version, description, homepage, repository, registry_data, cached_at, ttl_until FROM packages WHERE name = ? AND ttl_until >= strftime('%Y-%m-%d %H:%M:%f', 'now')")
90
+ .get(name);
91
+ if (row === undefined)
92
+ return null;
93
+ try {
94
+ const parsed = JSON.parse(row.registry_data);
95
+ return parsed;
96
+ }
97
+ catch {
98
+ return null;
99
+ }
100
+ }
101
+ /**
102
+ * Upserts a package metadata row, stamping it with a fresh TTL computed as
103
+ * `now + cacheTtlMs`. The full packument is serialized into the
104
+ * `registry_data` JSON column; the scalar columns (`latest_version`,
105
+ * `description`, `homepage`, `repository`) are denormalized for cheap
106
+ * listing queries.
107
+ *
108
+ * @param meta - The packument returned by the npm registry.
109
+ */
110
+ async setPackage(meta) {
111
+ const latest = meta["dist-tags"].latest;
112
+ const description = meta.description ?? "";
113
+ const homepage = meta.homepage ?? "";
114
+ const repository = repositoryToString(meta.repository);
115
+ const registryData = JSON.stringify(meta);
116
+ // Fractional (sub-second) TTLs are supported: `datetime('now', ...)` would
117
+ // truncate fractional seconds to whole seconds, so the expiry is stamped
118
+ // via `strftime('%Y-%m-%d %H:%M:%f', ...)` which preserves milliseconds.
119
+ // The value is a number derived from configuration, not user input, so
120
+ // interpolating it into the SQL string is safe.
121
+ const ttlSeconds = this.cacheTtlMs / 1000;
122
+ this.db
123
+ .prepare(`INSERT INTO packages (name, latest_version, description, homepage, repository, registry_data, cached_at, ttl_until)
124
+ VALUES (?, ?, ?, ?, ?, ?, datetime('now'), strftime('%Y-%m-%d %H:%M:%f', 'now', '+${ttlSeconds} seconds'))
125
+ ON CONFLICT(name) DO UPDATE SET
126
+ latest_version = excluded.latest_version,
127
+ description = excluded.description,
128
+ homepage = excluded.homepage,
129
+ repository = excluded.repository,
130
+ registry_data = excluded.registry_data,
131
+ cached_at = datetime('now'),
132
+ ttl_until = strftime('%Y-%m-%d %H:%M:%f', 'now', '+${ttlSeconds} seconds')`)
133
+ .run(meta.name, latest, description, homepage, repository, registryData);
134
+ }
135
+ /**
136
+ * Returns the most recent static security report for `pkg`@`version`, or
137
+ * `null` if none is cached. The {@link SecurityLevel} is reconstructed from
138
+ * the persisted numeric score because the `security_reports` table stores
139
+ * only the score, not the enum.
140
+ *
141
+ * @param pkg - Fully-qualified package name.
142
+ * @param version - Semver version string.
143
+ * @returns The cached {@link StaticScanReport}, or `null` if absent.
144
+ */
145
+ async getSecurityReport(pkg, version) {
146
+ const row = this.db
147
+ .prepare("SELECT id, package_name, version, scan_type, overall_score, findings_json, summary, scanned_at FROM security_reports WHERE package_name = ? AND version = ? AND scan_type = 'static'")
148
+ .get(pkg, version);
149
+ if (row === undefined)
150
+ return null;
151
+ let findings = [];
152
+ try {
153
+ const parsed = JSON.parse(row.findings_json);
154
+ if (Array.isArray(parsed)) {
155
+ findings = parsed;
156
+ }
157
+ }
158
+ catch {
159
+ findings = [];
160
+ }
161
+ return {
162
+ packageName: row.package_name,
163
+ version: row.version,
164
+ overallLevel: scoreToLevel(row.overall_score),
165
+ score: row.overall_score,
166
+ findings,
167
+ scannedAt: row.scanned_at,
168
+ };
169
+ }
170
+ /**
171
+ * Upserts a static security report into the `security_reports` table. The
172
+ * findings array is serialized to JSON in the `findings_json` column; the
173
+ * numeric score is stored in `overall_score`. The row is keyed by
174
+ * `(package_name, version, scan_type)` via the table's UNIQUE constraint.
175
+ *
176
+ * @param report - The static scan report to persist.
177
+ */
178
+ async setSecurityReport(report) {
179
+ const findingsJson = JSON.stringify(report.findings);
180
+ this.db
181
+ .prepare(`INSERT INTO security_reports (package_name, version, scan_type, overall_score, findings_json, summary, scanned_at)
182
+ VALUES (?, ?, 'static', ?, ?, '', ?)
183
+ ON CONFLICT(package_name, version, scan_type) DO UPDATE SET
184
+ overall_score = excluded.overall_score,
185
+ findings_json = excluded.findings_json,
186
+ scanned_at = excluded.scanned_at`)
187
+ .run(report.packageName, report.version, report.score, findingsJson, report.scannedAt);
188
+ }
189
+ async getLlmScanReport(pkg, version) {
190
+ const row = this.db
191
+ .prepare("SELECT findings_json, summary, scanned_at FROM security_reports WHERE package_name = ? AND version = ? AND scan_type = 'llm'")
192
+ .get(pkg, version);
193
+ if (!row)
194
+ return null;
195
+ let findings = [];
196
+ try {
197
+ const parsed = JSON.parse(row.findings_json);
198
+ if (Array.isArray(parsed))
199
+ findings = parsed;
200
+ }
201
+ catch {
202
+ findings = [];
203
+ }
204
+ let details = {};
205
+ try {
206
+ const parsed = JSON.parse(row.summary);
207
+ if (parsed && typeof parsed === "object") {
208
+ details = parsed;
209
+ }
210
+ }
211
+ catch {
212
+ details = { reason: row.summary };
213
+ }
214
+ return {
215
+ enabled: details.enabled ?? false,
216
+ reason: details.reason,
217
+ summary: details.summary,
218
+ functionalMatch: details.functionalMatch,
219
+ suspiciousScore: details.suspiciousScore,
220
+ findings,
221
+ scannedAt: row.scanned_at,
222
+ };
223
+ }
224
+ async setLlmScanReport(packageName, version, report) {
225
+ const summary = JSON.stringify({
226
+ enabled: report.enabled,
227
+ summary: report.summary,
228
+ reason: report.reason,
229
+ functionalMatch: report.functionalMatch,
230
+ suspiciousScore: report.suspiciousScore,
231
+ });
232
+ this.db
233
+ .prepare(`INSERT INTO security_reports (package_name, version, scan_type, overall_score, findings_json, summary, scanned_at)
234
+ VALUES (?, ?, 'llm', ?, ?, ?, ?)
235
+ ON CONFLICT(package_name, version, scan_type) DO UPDATE SET
236
+ overall_score = excluded.overall_score,
237
+ findings_json = excluded.findings_json,
238
+ summary = excluded.summary,
239
+ scanned_at = excluded.scanned_at`)
240
+ .run(packageName, version, report.suspiciousScore ?? 0, JSON.stringify(report.findings ?? []), summary, report.scannedAt ?? new Date().toISOString());
241
+ }
242
+ /**
243
+ * Returns the list of package names currently on the user's watchlist.
244
+ *
245
+ * @returns All watched package names, in insertion order.
246
+ */
247
+ async getWatchlist() {
248
+ const rows = this.db
249
+ .prepare("SELECT package_name FROM watchlist ORDER BY added_at ASC")
250
+ .all();
251
+ return rows.map((r) => r.package_name);
252
+ }
253
+ /**
254
+ * Adds `name` to the watchlist. Idempotent: inserting a name that is
255
+ * already watched is a no-op (INSERT OR IGNORE).
256
+ *
257
+ * @param name - Fully-qualified package name to watch.
258
+ */
259
+ async addToWatchlist(name) {
260
+ this.db
261
+ .prepare("INSERT OR IGNORE INTO watchlist (package_name) VALUES (?)")
262
+ .run(name);
263
+ }
264
+ /**
265
+ * Removes `name` from the watchlist. No-op if the name was not watched.
266
+ *
267
+ * @param name - Fully-qualified package name to stop watching.
268
+ */
269
+ async removeFromWatchlist(name) {
270
+ this.db
271
+ .prepare("DELETE FROM watchlist WHERE package_name = ?")
272
+ .run(name);
273
+ }
274
+ /**
275
+ * Returns the value stored under `key` in the settings table, or `null`
276
+ * if the key is unset.
277
+ *
278
+ * @param key - Settings key.
279
+ * @returns The stored value, or `null` if absent.
280
+ */
281
+ async getSetting(key) {
282
+ const row = this.db
283
+ .prepare("SELECT key, value FROM settings WHERE key = ?")
284
+ .get(key);
285
+ if (row === undefined)
286
+ return null;
287
+ return row.value;
288
+ }
289
+ /**
290
+ * Upserts `value` under `key` in the settings table (INSERT OR REPLACE).
291
+ *
292
+ * @param key - Settings key.
293
+ * @param value - Settings value to persist.
294
+ */
295
+ async setSetting(key, value) {
296
+ this.db
297
+ .prepare("INSERT OR REPLACE INTO settings (key, value) VALUES (?, ?)")
298
+ .run(key, value);
299
+ }
300
+ /**
301
+ * Returns the names of all cached package rows whose TTL has elapsed
302
+ * (`ttl_until < now`). These are candidates for an incremental refresh by
303
+ * the refresh-scheduler.
304
+ *
305
+ * @returns Package names with expired cache entries.
306
+ */
307
+ async getStalePackages() {
308
+ const rows = this.db
309
+ .prepare("SELECT name FROM packages WHERE ttl_until < strftime('%Y-%m-%d %H:%M:%f', 'now')")
310
+ .all();
311
+ return rows.map((r) => r.name);
312
+ }
313
+ // --------------------------------------------------------------------------
314
+ // Check history
315
+ // --------------------------------------------------------------------------
316
+ /**
317
+ * Append an entry to the persistent check history, keeping only the most
318
+ * recent {@link MAX_CHECK_HISTORY} entries.
319
+ */
320
+ async addHistoryEntry(entry) {
321
+ this.db
322
+ .prepare(`INSERT INTO check_history (package_name, level, score, timestamp)
323
+ VALUES (?, ?, ?, ?)`)
324
+ .run(entry.packageName, entry.level, entry.score, entry.timestamp);
325
+ this.db
326
+ .prepare(`DELETE FROM check_history WHERE id NOT IN (
327
+ SELECT id FROM check_history ORDER BY timestamp DESC, id DESC LIMIT ${MAX_CHECK_HISTORY}
328
+ )`)
329
+ .run();
330
+ }
331
+ /**
332
+ * Return the persistent check history, newest first.
333
+ */
334
+ async getHistory(limit) {
335
+ const capped = Math.max(1, Math.min(limit ?? MAX_CHECK_HISTORY, MAX_CHECK_HISTORY));
336
+ const rows = this.db
337
+ .prepare(`SELECT package_name, level, score, timestamp FROM check_history
338
+ ORDER BY timestamp DESC, id DESC LIMIT ${capped}`)
339
+ .all();
340
+ return rows.map((r) => ({
341
+ packageName: r.package_name,
342
+ level: r.level,
343
+ score: r.score,
344
+ timestamp: r.timestamp,
345
+ }));
346
+ }
347
+ /**
348
+ * Remove every entry from the persistent check history.
349
+ */
350
+ async clearHistory() {
351
+ this.db.prepare("DELETE FROM check_history").run();
352
+ }
353
+ }
354
+ /** Maximum number of check-history entries retained in the database. */
355
+ export const MAX_CHECK_HISTORY = 1000;
356
+ //# sourceMappingURL=cache-manager.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cache-manager.js","sourceRoot":"","sources":["../../src/store/cache-manager.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,81 @@
1
+ /**
2
+ * SQLite connection manager for @npm-safe/core.
3
+ *
4
+ * Owns a single `better-sqlite3` connection, configures WAL-mode pragmas for
5
+ * safe concurrent reads, and runs the migration list declared in
6
+ * {@link ./schema.ts} on construction. This module is pure connection +
7
+ * migration management — no query helpers, no caching, no business logic.
8
+ *
9
+ * @module store/database
10
+ */
11
+ import Database from "better-sqlite3";
12
+ /**
13
+ * Error thrown by {@link DatabaseManager} when the underlying `better-sqlite3`
14
+ * operation fails (open, pragma, migration). Wraps the original error so
15
+ * callers can branch on a single typed error while still inspecting the cause.
16
+ */
17
+ export declare class DatabaseManagerError extends Error {
18
+ readonly cause?: unknown | undefined;
19
+ /**
20
+ * @param message - Human-readable error message.
21
+ * @param cause - The underlying error, if any.
22
+ */
23
+ constructor(message: string, cause?: unknown | undefined);
24
+ }
25
+ /**
26
+ * Manages the lifecycle of a single SQLite database connection.
27
+ *
28
+ * On construction it opens the database file (creating it if missing),
29
+ * applies WAL-mode and safety pragmas, then runs any unapplied migrations
30
+ * from {@link getMigrationList} inside a transaction. The connection is
31
+ * kept open and accessible via {@link DatabaseManager.getDb} until
32
+ * {@link DatabaseManager.close} is called.
33
+ */
34
+ export declare class DatabaseManager {
35
+ /** Underlying better-sqlite3 connection, or `null` after `close()`. */
36
+ private db;
37
+ /**
38
+ * Opens (or creates) the database at `dbPath`, configures pragmas, and
39
+ * runs pending migrations. Throws {@link DatabaseManagerError} on any
40
+ * failure during open, pragma, or migration.
41
+ *
42
+ * @param dbPath - Filesystem path to the SQLite database file.
43
+ */
44
+ constructor(dbPath: string);
45
+ /**
46
+ * Apply the connection-level pragmas: WAL journal mode, a 5s busy timeout,
47
+ * NORMAL synchronous (safe under WAL), and foreign-key enforcement.
48
+ */
49
+ private applyPragmas;
50
+ /**
51
+ * Run all migrations from {@link getMigrationList} that have not yet been
52
+ * recorded in the `_migrations` table. The `_migrations` tracking table is
53
+ * created idempotently first (it is also part of {@link SCHEMA_SQL}, but
54
+ * we create it here so the very first migration can be recorded even when
55
+ * the schema is empty).
56
+ *
57
+ * Each migration is applied in its own transaction; on success its name is
58
+ * inserted into `_migrations`.
59
+ */
60
+ private runMigrations;
61
+ /**
62
+ * Returns the underlying `better-sqlite3` connection. Throws if the
63
+ * connection has been closed.
64
+ *
65
+ * @returns The active better-sqlite3 `Database` instance.
66
+ */
67
+ getDb(): Database.Database;
68
+ /**
69
+ * Closes the database connection. Safe to call multiple times; subsequent
70
+ * calls are no-ops. Throws {@link DatabaseManagerError} if the underlying
71
+ * close fails.
72
+ */
73
+ close(): void;
74
+ /**
75
+ * Whether the database connection is currently open.
76
+ *
77
+ * @returns `true` if the connection has not been closed.
78
+ */
79
+ isOpen(): boolean;
80
+ }
81
+ //# sourceMappingURL=database.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"database.d.ts","sourceRoot":"","sources":["../../src/store/database.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAEH,OAAO,QAAQ,MAAM,gBAAgB,CAAC;AAQtC;;;;GAIG;AACH,qBAAa,oBAAqB,SAAQ,KAAK;aAO3B,KAAK,CAAC,EAAE,OAAO;IANjC;;;OAGG;gBAED,OAAO,EAAE,MAAM,EACC,KAAK,CAAC,EAAE,OAAO,YAAA;CAKlC;AAoBD;;;;;;;;GAQG;AACH,qBAAa,eAAe;IAC1B,uEAAuE;IACvE,OAAO,CAAC,EAAE,CAA2B;IAErC;;;;;;OAMG;gBACS,MAAM,EAAE,MAAM;IAc1B;;;OAGG;IACH,OAAO,CAAC,YAAY;IAmBpB;;;;;;;;;OASG;IACH,OAAO,CAAC,aAAa;IAiDrB;;;;;OAKG;IACH,KAAK,IAAI,QAAQ,CAAC,QAAQ;IAO1B;;;;OAIG;IACH,KAAK,IAAI,IAAI;IAgBb;;;;OAIG;IACH,MAAM,IAAI,OAAO;CAGlB"}
@@ -0,0 +1,182 @@
1
+ /**
2
+ * SQLite connection manager for @npm-safe/core.
3
+ *
4
+ * Owns a single `better-sqlite3` connection, configures WAL-mode pragmas for
5
+ * safe concurrent reads, and runs the migration list declared in
6
+ * {@link ./schema.ts} on construction. This module is pure connection +
7
+ * migration management — no query helpers, no caching, no business logic.
8
+ *
9
+ * @module store/database
10
+ */
11
+ import Database from "better-sqlite3";
12
+ import { getMigrationList, getInitialMigration, getCheckHistoryMigration, } from "./schema.js";
13
+ /**
14
+ * Error thrown by {@link DatabaseManager} when the underlying `better-sqlite3`
15
+ * operation fails (open, pragma, migration). Wraps the original error so
16
+ * callers can branch on a single typed error while still inspecting the cause.
17
+ */
18
+ export class DatabaseManagerError extends Error {
19
+ cause;
20
+ /**
21
+ * @param message - Human-readable error message.
22
+ * @param cause - The underlying error, if any.
23
+ */
24
+ constructor(message, cause) {
25
+ super(message);
26
+ this.cause = cause;
27
+ this.name = "DatabaseManagerError";
28
+ }
29
+ }
30
+ /**
31
+ * Maps a migration file name to the SQL it should execute. Today only the
32
+ * initial migration exists; future migrations add entries here.
33
+ *
34
+ * @param name - Migration file name from {@link getMigrationList}.
35
+ * @returns The SQL string to run for that migration.
36
+ */
37
+ function getMigrationSql(name) {
38
+ switch (name) {
39
+ case "001_initial.sql":
40
+ return getInitialMigration();
41
+ case "002_check_history.sql":
42
+ return getCheckHistoryMigration();
43
+ default:
44
+ throw new DatabaseManagerError(`Unknown migration: ${name}`);
45
+ }
46
+ }
47
+ /**
48
+ * Manages the lifecycle of a single SQLite database connection.
49
+ *
50
+ * On construction it opens the database file (creating it if missing),
51
+ * applies WAL-mode and safety pragmas, then runs any unapplied migrations
52
+ * from {@link getMigrationList} inside a transaction. The connection is
53
+ * kept open and accessible via {@link DatabaseManager.getDb} until
54
+ * {@link DatabaseManager.close} is called.
55
+ */
56
+ export class DatabaseManager {
57
+ /** Underlying better-sqlite3 connection, or `null` after `close()`. */
58
+ db;
59
+ /**
60
+ * Opens (or creates) the database at `dbPath`, configures pragmas, and
61
+ * runs pending migrations. Throws {@link DatabaseManagerError} on any
62
+ * failure during open, pragma, or migration.
63
+ *
64
+ * @param dbPath - Filesystem path to the SQLite database file.
65
+ */
66
+ constructor(dbPath) {
67
+ try {
68
+ this.db = new Database(dbPath, { fileMustExist: false });
69
+ }
70
+ catch (err) {
71
+ throw new DatabaseManagerError(`Failed to open database at ${dbPath}: ${err instanceof Error ? err.message : String(err)}`, err);
72
+ }
73
+ this.applyPragmas();
74
+ this.runMigrations();
75
+ }
76
+ /**
77
+ * Apply the connection-level pragmas: WAL journal mode, a 5s busy timeout,
78
+ * NORMAL synchronous (safe under WAL), and foreign-key enforcement.
79
+ */
80
+ applyPragmas() {
81
+ const pragmas = [
82
+ "journal_mode=WAL",
83
+ "busy_timeout=5000",
84
+ "synchronous=NORMAL",
85
+ "foreign_keys=ON",
86
+ ];
87
+ for (const stmt of pragmas) {
88
+ try {
89
+ this.db.pragma(stmt);
90
+ }
91
+ catch (err) {
92
+ throw new DatabaseManagerError(`Failed to apply pragma "${stmt}": ${err instanceof Error ? err.message : String(err)}`, err);
93
+ }
94
+ }
95
+ }
96
+ /**
97
+ * Run all migrations from {@link getMigrationList} that have not yet been
98
+ * recorded in the `_migrations` table. The `_migrations` tracking table is
99
+ * created idempotently first (it is also part of {@link SCHEMA_SQL}, but
100
+ * we create it here so the very first migration can be recorded even when
101
+ * the schema is empty).
102
+ *
103
+ * Each migration is applied in its own transaction; on success its name is
104
+ * inserted into `_migrations`.
105
+ */
106
+ runMigrations() {
107
+ const db = this.db;
108
+ try {
109
+ db.exec(`
110
+ CREATE TABLE IF NOT EXISTS _migrations (
111
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
112
+ name TEXT NOT NULL UNIQUE,
113
+ applied_at TEXT NOT NULL DEFAULT (datetime('now'))
114
+ );
115
+ `);
116
+ }
117
+ catch (err) {
118
+ throw new DatabaseManagerError(`Failed to create _migrations tracking table: ${err instanceof Error ? err.message : String(err)}`, err);
119
+ }
120
+ const selectApplied = db.prepare("SELECT name FROM _migrations WHERE name = ?");
121
+ const insertMigration = db.prepare("INSERT INTO _migrations (name) VALUES (?)");
122
+ const applyOne = (name, sql) => {
123
+ const tx = db.transaction(() => {
124
+ db.exec(sql);
125
+ insertMigration.run(name);
126
+ });
127
+ tx();
128
+ };
129
+ for (const name of getMigrationList()) {
130
+ const existing = selectApplied.get(name);
131
+ if (existing !== undefined) {
132
+ continue;
133
+ }
134
+ try {
135
+ applyOne(name, getMigrationSql(name));
136
+ }
137
+ catch (err) {
138
+ throw new DatabaseManagerError(`Failed to apply migration "${name}": ${err instanceof Error ? err.message : String(err)}`, err);
139
+ }
140
+ }
141
+ }
142
+ /**
143
+ * Returns the underlying `better-sqlite3` connection. Throws if the
144
+ * connection has been closed.
145
+ *
146
+ * @returns The active better-sqlite3 `Database` instance.
147
+ */
148
+ getDb() {
149
+ if (this.db === null) {
150
+ throw new DatabaseManagerError("Database is not open");
151
+ }
152
+ return this.db;
153
+ }
154
+ /**
155
+ * Closes the database connection. Safe to call multiple times; subsequent
156
+ * calls are no-ops. Throws {@link DatabaseManagerError} if the underlying
157
+ * close fails.
158
+ */
159
+ close() {
160
+ if (this.db === null) {
161
+ return;
162
+ }
163
+ try {
164
+ this.db.close();
165
+ }
166
+ catch (err) {
167
+ throw new DatabaseManagerError(`Failed to close database: ${err instanceof Error ? err.message : String(err)}`, err);
168
+ }
169
+ finally {
170
+ this.db = null;
171
+ }
172
+ }
173
+ /**
174
+ * Whether the database connection is currently open.
175
+ *
176
+ * @returns `true` if the connection has not been closed.
177
+ */
178
+ isOpen() {
179
+ return this.db !== null;
180
+ }
181
+ }
182
+ //# sourceMappingURL=database.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"database.js","sourceRoot":"","sources":["../../src/store/database.ts"],"names":[],"mappings":"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"}