@drewpayment/mink 0.11.0-beta.4 → 0.12.0-beta.1
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/dashboard/out/404.html +1 -1
- package/dashboard/out/action-log.html +1 -1
- package/dashboard/out/action-log.txt +1 -1
- package/dashboard/out/activity.html +1 -1
- package/dashboard/out/activity.txt +1 -1
- package/dashboard/out/bugs.html +1 -1
- package/dashboard/out/bugs.txt +1 -1
- package/dashboard/out/capture.html +1 -1
- package/dashboard/out/capture.txt +1 -1
- package/dashboard/out/config.html +1 -1
- package/dashboard/out/config.txt +1 -1
- package/dashboard/out/daemon.html +1 -1
- package/dashboard/out/daemon.txt +1 -1
- package/dashboard/out/design.html +1 -1
- package/dashboard/out/design.txt +1 -1
- package/dashboard/out/discord.html +1 -1
- package/dashboard/out/discord.txt +1 -1
- package/dashboard/out/file-index.html +1 -1
- package/dashboard/out/file-index.txt +1 -1
- package/dashboard/out/index.html +1 -1
- package/dashboard/out/index.txt +1 -1
- package/dashboard/out/insights.html +1 -1
- package/dashboard/out/insights.txt +1 -1
- package/dashboard/out/learning.html +1 -1
- package/dashboard/out/learning.txt +1 -1
- package/dashboard/out/overview.html +1 -1
- package/dashboard/out/overview.txt +1 -1
- package/dashboard/out/scheduler.html +1 -1
- package/dashboard/out/scheduler.txt +1 -1
- package/dashboard/out/sync.html +1 -1
- package/dashboard/out/sync.txt +1 -1
- package/dashboard/out/tokens.html +1 -1
- package/dashboard/out/tokens.txt +1 -1
- package/dashboard/out/waste.html +1 -1
- package/dashboard/out/waste.txt +1 -1
- package/dashboard/out/wiki.html +1 -1
- package/dashboard/out/wiki.txt +1 -1
- package/dist/cli.bun.js +90615 -0
- package/dist/{cli.js → cli.node.js} +2227 -758
- package/package.json +14 -4
- package/scripts/build.mjs +47 -0
- package/src/commands/bug-search.ts +2 -4
- package/src/commands/detect-waste.ts +24 -32
- package/src/commands/post-read.ts +10 -11
- package/src/commands/post-write.ts +13 -19
- package/src/commands/pre-read.ts +19 -24
- package/src/commands/scan.ts +103 -40
- package/src/commands/status.ts +45 -26
- package/src/core/bug-memory.ts +32 -34
- package/src/core/dashboard-api.ts +44 -22
- package/src/core/index-store.ts +23 -0
- package/src/core/paths.ts +7 -0
- package/src/core/scanner.ts +8 -4
- package/src/core/state-aggregator.ts +64 -7
- package/src/core/state-counters.ts +11 -31
- package/src/core/sync-merge-drivers.ts +164 -1
- package/src/core/sync.ts +9 -0
- package/src/core/token-ledger.ts +50 -4
- package/src/repositories/bug-memory-repo.ts +268 -0
- package/src/repositories/counters-repo.ts +88 -0
- package/src/repositories/file-index-repo.ts +238 -0
- package/src/repositories/token-ledger-repo.ts +412 -0
- package/src/storage/db.ts +121 -0
- package/src/storage/driver.bun.ts +99 -0
- package/src/storage/driver.node.ts +107 -0
- package/src/storage/driver.ts +76 -0
- package/src/storage/migrate-json.ts +415 -0
- package/src/storage/schema.ts +207 -0
- package/src/types/file-index.ts +9 -0
- /package/dashboard/out/_next/static/{fci7mSuW5y3ri6IlmLojm → 9ElzGFcXpcjLq-QSQslWY}/_buildManifest.js +0 -0
- /package/dashboard/out/_next/static/{fci7mSuW5y3ri6IlmLojm → 9ElzGFcXpcjLq-QSQslWY}/_ssgManifest.js +0 -0
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
// Project database lifecycle. The handle is opened lazily and cached per
|
|
2
|
+
// process (hook commands are short-lived; their first call to a repository
|
|
3
|
+
// triggers the open, and the handle is closed via the registered exit hook).
|
|
4
|
+
//
|
|
5
|
+
// On first open for a project that has on-disk JSON state, the lazy JSON
|
|
6
|
+
// importer runs (see `migrate-json.ts`). The importer is idempotent — once
|
|
7
|
+
// `meta.migrated_from_json_at` is set, it returns immediately.
|
|
8
|
+
|
|
9
|
+
import { mkdirSync } from "fs";
|
|
10
|
+
import { dirname } from "path";
|
|
11
|
+
import { projectDbPath } from "../core/paths";
|
|
12
|
+
import { openDriver, type DbDriver } from "./driver";
|
|
13
|
+
import { applySchema } from "./schema";
|
|
14
|
+
import { migrateJsonIfNeeded } from "./migrate-json";
|
|
15
|
+
|
|
16
|
+
export { projectDbPath } from "../core/paths";
|
|
17
|
+
|
|
18
|
+
interface ConnectionEntry {
|
|
19
|
+
driver: DbDriver;
|
|
20
|
+
closed: boolean;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
const handles = new Map<string, ConnectionEntry>();
|
|
24
|
+
let exitHookInstalled = false;
|
|
25
|
+
|
|
26
|
+
function installExitHook(): void {
|
|
27
|
+
if (exitHookInstalled) return;
|
|
28
|
+
exitHookInstalled = true;
|
|
29
|
+
const closeAll = (): void => {
|
|
30
|
+
for (const entry of handles.values()) {
|
|
31
|
+
if (entry.closed) continue;
|
|
32
|
+
try {
|
|
33
|
+
entry.driver.close();
|
|
34
|
+
} catch {
|
|
35
|
+
// best effort — process is shutting down
|
|
36
|
+
}
|
|
37
|
+
entry.closed = true;
|
|
38
|
+
}
|
|
39
|
+
};
|
|
40
|
+
process.on("exit", closeAll);
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
// Test-only — drop cached handles between tests that wipe MINK_ROOT_OVERRIDE.
|
|
44
|
+
// Production code never calls this; the exit hook handles real shutdown.
|
|
45
|
+
export function _resetDbCacheForTests(): void {
|
|
46
|
+
for (const entry of handles.values()) {
|
|
47
|
+
if (entry.closed) continue;
|
|
48
|
+
try {
|
|
49
|
+
entry.driver.close();
|
|
50
|
+
} catch {
|
|
51
|
+
// ignore
|
|
52
|
+
}
|
|
53
|
+
entry.closed = true;
|
|
54
|
+
}
|
|
55
|
+
handles.clear();
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
function applyPragmas(db: DbDriver): void {
|
|
59
|
+
// WAL: enables concurrent readers during a writer; survives crashes.
|
|
60
|
+
// synchronous=NORMAL: safe with WAL, ~2-5x faster than FULL.
|
|
61
|
+
// foreign_keys=ON: required for bug_tags / bug_related cascades.
|
|
62
|
+
// busy_timeout: matches the existing 5s hook safety timeout in
|
|
63
|
+
// src/core/runtime.ts — under contention SQLite will retry rather than
|
|
64
|
+
// throw SQLITE_BUSY immediately.
|
|
65
|
+
db.exec("PRAGMA journal_mode = WAL");
|
|
66
|
+
db.exec("PRAGMA synchronous = NORMAL");
|
|
67
|
+
db.exec("PRAGMA foreign_keys = ON");
|
|
68
|
+
db.exec("PRAGMA busy_timeout = 5000");
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
export function openProjectDb(cwd: string): DbDriver {
|
|
72
|
+
const path = projectDbPath(cwd);
|
|
73
|
+
const cached = handles.get(path);
|
|
74
|
+
if (cached && !cached.closed) return cached.driver;
|
|
75
|
+
|
|
76
|
+
mkdirSync(dirname(path), { recursive: true });
|
|
77
|
+
const driver = openDriver(path);
|
|
78
|
+
applyPragmas(driver);
|
|
79
|
+
applySchema(driver);
|
|
80
|
+
|
|
81
|
+
// Run migration AFTER applySchema so the importer can write into existing
|
|
82
|
+
// tables. The importer no-ops once `meta.migrated_from_json_at` is set.
|
|
83
|
+
try {
|
|
84
|
+
migrateJsonIfNeeded(driver, cwd);
|
|
85
|
+
} catch (err) {
|
|
86
|
+
// Migration failures should not block the process — log and continue
|
|
87
|
+
// with an empty DB. Phase 2 callers will fall back to legacy JSON reads.
|
|
88
|
+
// (We rethrow for tests via MINK_DB_STRICT_MIGRATE=1.)
|
|
89
|
+
if (process.env.MINK_DB_STRICT_MIGRATE === "1") throw err;
|
|
90
|
+
console.warn(
|
|
91
|
+
`[mink] JSON → SQLite migration failed for ${cwd}: ${
|
|
92
|
+
(err as Error).message
|
|
93
|
+
}`
|
|
94
|
+
);
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
installExitHook();
|
|
98
|
+
handles.set(path, { driver, closed: false });
|
|
99
|
+
return driver;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
// Force a WAL checkpoint and close the handle for the given cwd. Used by
|
|
103
|
+
// `mink sync` before pushing so the .db is self-contained (the -wal/-shm
|
|
104
|
+
// sidecars are not synced).
|
|
105
|
+
export function checkpointAndClose(cwd: string): void {
|
|
106
|
+
const path = projectDbPath(cwd);
|
|
107
|
+
const entry = handles.get(path);
|
|
108
|
+
if (!entry || entry.closed) return;
|
|
109
|
+
try {
|
|
110
|
+
entry.driver.exec("PRAGMA wal_checkpoint(TRUNCATE)");
|
|
111
|
+
} catch {
|
|
112
|
+
// best effort
|
|
113
|
+
}
|
|
114
|
+
try {
|
|
115
|
+
entry.driver.close();
|
|
116
|
+
} catch {
|
|
117
|
+
// best effort
|
|
118
|
+
}
|
|
119
|
+
entry.closed = true;
|
|
120
|
+
handles.delete(path);
|
|
121
|
+
}
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
// bun:sqlite implementation of the DbDriver interface.
|
|
2
|
+
// Selected at build time when MINK_RUNTIME === "bun" and via `typeof Bun`
|
|
3
|
+
// detection when running unbundled.
|
|
4
|
+
|
|
5
|
+
import type { DbDriver, DriverModule, SqlParam, Statement } from "./driver";
|
|
6
|
+
|
|
7
|
+
// Use require() so the type-only import path doesn't trip Node when this
|
|
8
|
+
// file is loaded under the wrong runtime by mistake (the runtime dispatcher
|
|
9
|
+
// in driver.ts is supposed to prevent that).
|
|
10
|
+
const { Database } = require("bun:sqlite") as typeof import("bun:sqlite");
|
|
11
|
+
|
|
12
|
+
class BunStatement implements Statement {
|
|
13
|
+
constructor(private readonly stmt: import("bun:sqlite").Statement) {}
|
|
14
|
+
|
|
15
|
+
run(...params: SqlParam[]): { changes: number | bigint; lastInsertRowid: number | bigint } {
|
|
16
|
+
const r = this.stmt.run(...(params as never[]));
|
|
17
|
+
return { changes: r.changes, lastInsertRowid: r.lastInsertRowid };
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
get(...params: SqlParam[]) {
|
|
21
|
+
const row = this.stmt.get(...(params as never[]));
|
|
22
|
+
return (row ?? undefined) as Record<string, unknown> | undefined;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
all(...params: SqlParam[]) {
|
|
26
|
+
return this.stmt.all(...(params as never[])) as Record<string, unknown>[];
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
class BunDriver implements DbDriver {
|
|
31
|
+
readonly filename: string;
|
|
32
|
+
private readonly db: import("bun:sqlite").Database;
|
|
33
|
+
private readonly txnDepth = { value: 0 };
|
|
34
|
+
|
|
35
|
+
constructor(filename: string) {
|
|
36
|
+
this.filename = filename;
|
|
37
|
+
this.db = new Database(filename, { create: true });
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
prepare(sql: string): Statement {
|
|
41
|
+
return new BunStatement(this.db.prepare(sql));
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
exec(sql: string): void {
|
|
45
|
+
this.db.exec(sql);
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
// We implement transactions manually (rather than using bun:sqlite's
|
|
49
|
+
// `db.transaction(fn)` wrapper) so semantics match node:sqlite exactly:
|
|
50
|
+
// synchronous, nestable via savepoints, IMMEDIATE locking to fail fast
|
|
51
|
+
// when another writer is mid-transaction.
|
|
52
|
+
transaction<T>(fn: () => T): T {
|
|
53
|
+
if (this.txnDepth.value > 0) {
|
|
54
|
+
const sp = `sp_${this.txnDepth.value}`;
|
|
55
|
+
this.db.exec(`SAVEPOINT ${sp}`);
|
|
56
|
+
this.txnDepth.value++;
|
|
57
|
+
try {
|
|
58
|
+
const result = fn();
|
|
59
|
+
this.db.exec(`RELEASE SAVEPOINT ${sp}`);
|
|
60
|
+
this.txnDepth.value--;
|
|
61
|
+
return result;
|
|
62
|
+
} catch (err) {
|
|
63
|
+
this.db.exec(`ROLLBACK TO SAVEPOINT ${sp}`);
|
|
64
|
+
this.db.exec(`RELEASE SAVEPOINT ${sp}`);
|
|
65
|
+
this.txnDepth.value--;
|
|
66
|
+
throw err;
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
this.db.exec("BEGIN IMMEDIATE");
|
|
70
|
+
this.txnDepth.value++;
|
|
71
|
+
try {
|
|
72
|
+
const result = fn();
|
|
73
|
+
this.db.exec("COMMIT");
|
|
74
|
+
this.txnDepth.value--;
|
|
75
|
+
return result;
|
|
76
|
+
} catch (err) {
|
|
77
|
+
this.db.exec("ROLLBACK");
|
|
78
|
+
this.txnDepth.value--;
|
|
79
|
+
throw err;
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
close(): void {
|
|
84
|
+
this.db.close();
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
pragma(stmt: string): unknown {
|
|
88
|
+
// bun:sqlite has no dedicated pragma() helper; route through exec/query.
|
|
89
|
+
// Pragmas that return a value (e.g. `journal_mode`) are SELECT-shaped.
|
|
90
|
+
if (/^[a-z_]+\s*=/i.test(stmt) || /^[a-z_]+\s*\([^)]*\)/i.test(stmt)) {
|
|
91
|
+
// Assignment or call form — no result expected, but the sqlite engine
|
|
92
|
+
// still returns the new value. Query so callers can read it.
|
|
93
|
+
return this.db.prepare(`PRAGMA ${stmt}`).all();
|
|
94
|
+
}
|
|
95
|
+
return this.db.prepare(`PRAGMA ${stmt}`).all();
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
export const open: DriverModule["open"] = (filename) => new BunDriver(filename);
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
// node:sqlite implementation of the DbDriver interface.
|
|
2
|
+
// Selected at build time when MINK_RUNTIME === "node" and via runtime
|
|
3
|
+
// detection when running unbundled under Node.
|
|
4
|
+
|
|
5
|
+
import type { DbDriver, DriverModule, SqlParam, Statement } from "./driver";
|
|
6
|
+
|
|
7
|
+
// Suppress Node 22's `ExperimentalWarning: SQLite is an experimental feature`
|
|
8
|
+
// the first (and only) time the module is required. We do it inline rather
|
|
9
|
+
// than via NODE_NO_WARNINGS so users don't lose warnings from other modules.
|
|
10
|
+
const originalEmit = process.emit;
|
|
11
|
+
process.emit = function patchedEmit(
|
|
12
|
+
this: NodeJS.Process,
|
|
13
|
+
event: string | symbol,
|
|
14
|
+
...args: unknown[]
|
|
15
|
+
): boolean {
|
|
16
|
+
if (
|
|
17
|
+
event === "warning" &&
|
|
18
|
+
args[0] instanceof Error &&
|
|
19
|
+
(args[0] as Error & { name: string }).name === "ExperimentalWarning" &&
|
|
20
|
+
/sqlite/i.test((args[0] as Error).message)
|
|
21
|
+
) {
|
|
22
|
+
return false;
|
|
23
|
+
}
|
|
24
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
25
|
+
return (originalEmit as any).call(this, event, ...args);
|
|
26
|
+
} as typeof process.emit;
|
|
27
|
+
|
|
28
|
+
const { DatabaseSync } = require("node:sqlite") as typeof import("node:sqlite");
|
|
29
|
+
|
|
30
|
+
class NodeStatement implements Statement {
|
|
31
|
+
// node:sqlite uses StatementSync from its types
|
|
32
|
+
constructor(private readonly stmt: import("node:sqlite").StatementSync) {}
|
|
33
|
+
|
|
34
|
+
run(...params: SqlParam[]): { changes: number | bigint; lastInsertRowid: number | bigint } {
|
|
35
|
+
const r = this.stmt.run(...(params as never[]));
|
|
36
|
+
return { changes: r.changes, lastInsertRowid: r.lastInsertRowid };
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
get(...params: SqlParam[]) {
|
|
40
|
+
const row = this.stmt.get(...(params as never[]));
|
|
41
|
+
return (row ?? undefined) as Record<string, unknown> | undefined;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
all(...params: SqlParam[]) {
|
|
45
|
+
return this.stmt.all(...(params as never[])) as Record<string, unknown>[];
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
class NodeDriver implements DbDriver {
|
|
50
|
+
readonly filename: string;
|
|
51
|
+
private readonly db: import("node:sqlite").DatabaseSync;
|
|
52
|
+
private readonly txnDepth = { value: 0 };
|
|
53
|
+
|
|
54
|
+
constructor(filename: string) {
|
|
55
|
+
this.filename = filename;
|
|
56
|
+
this.db = new DatabaseSync(filename);
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
prepare(sql: string): Statement {
|
|
60
|
+
return new NodeStatement(this.db.prepare(sql));
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
exec(sql: string): void {
|
|
64
|
+
this.db.exec(sql);
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
transaction<T>(fn: () => T): T {
|
|
68
|
+
if (this.txnDepth.value > 0) {
|
|
69
|
+
const sp = `sp_${this.txnDepth.value}`;
|
|
70
|
+
this.db.exec(`SAVEPOINT ${sp}`);
|
|
71
|
+
this.txnDepth.value++;
|
|
72
|
+
try {
|
|
73
|
+
const result = fn();
|
|
74
|
+
this.db.exec(`RELEASE SAVEPOINT ${sp}`);
|
|
75
|
+
this.txnDepth.value--;
|
|
76
|
+
return result;
|
|
77
|
+
} catch (err) {
|
|
78
|
+
this.db.exec(`ROLLBACK TO SAVEPOINT ${sp}`);
|
|
79
|
+
this.db.exec(`RELEASE SAVEPOINT ${sp}`);
|
|
80
|
+
this.txnDepth.value--;
|
|
81
|
+
throw err;
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
this.db.exec("BEGIN IMMEDIATE");
|
|
85
|
+
this.txnDepth.value++;
|
|
86
|
+
try {
|
|
87
|
+
const result = fn();
|
|
88
|
+
this.db.exec("COMMIT");
|
|
89
|
+
this.txnDepth.value--;
|
|
90
|
+
return result;
|
|
91
|
+
} catch (err) {
|
|
92
|
+
this.db.exec("ROLLBACK");
|
|
93
|
+
this.txnDepth.value--;
|
|
94
|
+
throw err;
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
close(): void {
|
|
99
|
+
this.db.close();
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
pragma(stmt: string): unknown {
|
|
103
|
+
return this.db.prepare(`PRAGMA ${stmt}`).all();
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
export const open: DriverModule["open"] = (filename) => new NodeDriver(filename);
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
// Build-time-selected SQLite driver adapter. The actual import of
|
|
2
|
+
// `bun:sqlite` or `node:sqlite` happens in the runtime-specific
|
|
3
|
+
// `driver.bun.ts` / `driver.node.ts` siblings; this file picks one based on
|
|
4
|
+
// the `MINK_RUNTIME` define injected by `bun build --define`. When neither
|
|
5
|
+
// define is set (e.g. running TypeScript directly under `bun test`), it
|
|
6
|
+
// falls back to `typeof Bun` detection.
|
|
7
|
+
//
|
|
8
|
+
// The adapter exposes a stable 5-method surface — `prepare`, `exec`,
|
|
9
|
+
// `transaction`, `close`, `pragma` — chosen as the minimal set that covers
|
|
10
|
+
// every call site in `src/repositories/`. Both backends' `Statement.run/get/all`
|
|
11
|
+
// methods are compatible enough that we pass them through unchanged.
|
|
12
|
+
|
|
13
|
+
declare const MINK_RUNTIME: string | undefined;
|
|
14
|
+
|
|
15
|
+
export type SqlParam = string | number | bigint | Uint8Array | null;
|
|
16
|
+
export type SqlRow = Record<string, unknown>;
|
|
17
|
+
|
|
18
|
+
export interface Statement {
|
|
19
|
+
run(...params: SqlParam[]): { changes: number | bigint; lastInsertRowid: number | bigint };
|
|
20
|
+
get(...params: SqlParam[]): SqlRow | undefined;
|
|
21
|
+
all(...params: SqlParam[]): SqlRow[];
|
|
22
|
+
iterate?(...params: SqlParam[]): IterableIterator<SqlRow>;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export interface DbDriver {
|
|
26
|
+
prepare(sql: string): Statement;
|
|
27
|
+
exec(sql: string): void;
|
|
28
|
+
transaction<T>(fn: () => T): T;
|
|
29
|
+
close(): void;
|
|
30
|
+
pragma(stmt: string): unknown;
|
|
31
|
+
readonly filename: string;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
export interface DriverModule {
|
|
35
|
+
open(filename: string): DbDriver;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
function pickRuntime(): "bun" | "node" {
|
|
39
|
+
// `MINK_RUNTIME` is replaced at bundle time. When running source directly
|
|
40
|
+
// (tests, `bun src/cli.ts`), the symbol is undefined and we fall back to
|
|
41
|
+
// feature detection.
|
|
42
|
+
try {
|
|
43
|
+
if (typeof MINK_RUNTIME !== "undefined") {
|
|
44
|
+
if (MINK_RUNTIME === "bun" || MINK_RUNTIME === "node") return MINK_RUNTIME;
|
|
45
|
+
}
|
|
46
|
+
} catch {
|
|
47
|
+
// ReferenceError when the symbol is not declared — proceed to detect.
|
|
48
|
+
}
|
|
49
|
+
return typeof Bun !== "undefined" ? "bun" : "node";
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
let cached: DriverModule | undefined;
|
|
53
|
+
|
|
54
|
+
function loadDriver(): DriverModule {
|
|
55
|
+
if (cached) return cached;
|
|
56
|
+
const runtime = pickRuntime();
|
|
57
|
+
// Conditional `require` keeps the unused branch out of the active bundle
|
|
58
|
+
// when `bun build` does constant-folding on `pickRuntime()`'s result via
|
|
59
|
+
// the `MINK_RUNTIME` define. At runtime, only the matching branch ever
|
|
60
|
+
// executes, so the other module's `import 'bun:sqlite'` /
|
|
61
|
+
// `import 'node:sqlite'` is never evaluated.
|
|
62
|
+
if (runtime === "bun") {
|
|
63
|
+
cached = require("./driver.bun") as DriverModule;
|
|
64
|
+
} else {
|
|
65
|
+
cached = require("./driver.node") as DriverModule;
|
|
66
|
+
}
|
|
67
|
+
return cached;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
export function openDriver(filename: string): DbDriver {
|
|
71
|
+
return loadDriver().open(filename);
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
export function currentRuntime(): "bun" | "node" {
|
|
75
|
+
return pickRuntime();
|
|
76
|
+
}
|