refactor: migrate from better-sqlite3 to node:sqlite, npm glob to node:fs
Since Node >= 24 is the minimum engine, remove the better-sqlite3 fallback
chain from sf-db.ts, unit-ownership.ts, and cli-stats.ts. Use DatabaseSync
from node:sqlite directly. Also replace the `glob` npm package with built-in
node:fs/promises.glob and node:fs.globSync in pi-coding-agent LSP utils.
- Remove createRequire boilerplate and suppressSqliteWarning helper
- Simplify loadProvider() and openRawDb()
- Net -177 lines of fallback/middleware code
💘 Generated with Crush
Assisted-by: GLM-5.1 via Crush <crush@charm.land>
This commit is contained in:
parent
040bdf4eb8
commit
980772cc90
5 changed files with 15 additions and 177 deletions
|
|
@ -4,7 +4,7 @@ import * as os from "node:os";
|
|||
import * as path from "node:path";
|
||||
import { spawnSync } from "node:child_process";
|
||||
import YAML from "yaml";
|
||||
import { globSync } from "glob";
|
||||
import { globSync } from "node:fs";
|
||||
import { CONFIG_DIR_NAME } from "../../config.js";
|
||||
import { isRecord } from "./helpers.js";
|
||||
import type { ServerConfig } from "./types.js";
|
||||
|
|
@ -151,7 +151,7 @@ export function hasRootMarkers(cwd: string, markers: string[]): boolean {
|
|||
for (const marker of markers) {
|
||||
if (marker.includes("*")) {
|
||||
try {
|
||||
const matches = globSync(marker, { cwd, nodir: false });
|
||||
const matches = globSync(marker, { cwd });
|
||||
if (matches.length > 0) {
|
||||
return true;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
import * as fsPromises from "node:fs/promises";
|
||||
import path from "node:path";
|
||||
import { glob } from "glob";
|
||||
import { glob } from "node:fs/promises";
|
||||
import { isEnoent } from "./helpers.js";
|
||||
import type {
|
||||
CallHierarchyItem,
|
||||
|
|
@ -533,7 +533,7 @@ export async function collectGlobMatches(
|
|||
maxMatches: number,
|
||||
): Promise<{ matches: string[]; truncated: boolean }> {
|
||||
const normalizedLimit = Number.isFinite(maxMatches) ? Math.max(1, Math.trunc(maxMatches)) : 1;
|
||||
const allMatches = await glob(pattern, { cwd });
|
||||
const allMatches: string[] = []; for await (const p of glob(pattern, { cwd })) allMatches.push(p);
|
||||
if (allMatches.length > normalizedLimit) {
|
||||
return { matches: allMatches.slice(0, normalizedLimit), truncated: true };
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
import { existsSync } from "node:fs";
|
||||
import { createRequire } from "node:module";
|
||||
import { join } from "node:path";
|
||||
import { DatabaseSync } from "node:sqlite";
|
||||
|
||||
export interface ModelStatsRow {
|
||||
model_id: string;
|
||||
|
|
@ -33,7 +33,6 @@ interface ParsedStatsArgs {
|
|||
unitType?: string;
|
||||
}
|
||||
|
||||
const require = createRequire(import.meta.url);
|
||||
|
||||
export function parseDurationSeconds(value: string): number {
|
||||
const match = value.trim().match(/^(\d+(?:\.\d+)?)([smhdw])$/i);
|
||||
|
|
@ -203,42 +202,7 @@ function usage(): string {
|
|||
}
|
||||
|
||||
function openSqliteDb(dbPath: string): SqliteDb {
|
||||
try {
|
||||
const sqlite = require("node:sqlite") as {
|
||||
DatabaseSync?: new (
|
||||
path: string,
|
||||
options?: Record<string, unknown>,
|
||||
) => SqliteDb;
|
||||
};
|
||||
if (sqlite.DatabaseSync) {
|
||||
return new sqlite.DatabaseSync(dbPath, { readOnly: true });
|
||||
}
|
||||
} catch {
|
||||
// Try better-sqlite3 below.
|
||||
}
|
||||
|
||||
try {
|
||||
const mod = require("better-sqlite3") as
|
||||
| {
|
||||
default?: new (
|
||||
path: string,
|
||||
options?: Record<string, unknown>,
|
||||
) => SqliteDb;
|
||||
}
|
||||
| (new (
|
||||
path: string,
|
||||
options?: Record<string, unknown>,
|
||||
) => SqliteDb);
|
||||
const Database = typeof mod === "function" ? mod : mod.default;
|
||||
if (Database)
|
||||
return new Database(dbPath, { readonly: true, fileMustExist: true });
|
||||
} catch {
|
||||
// Report a single actionable error below.
|
||||
}
|
||||
|
||||
throw new Error(
|
||||
"No SQLite provider available (tried node:sqlite, better-sqlite3)",
|
||||
);
|
||||
return new DatabaseSync(dbPath, { readOnly: true }) as SqliteDb;
|
||||
}
|
||||
|
||||
export async function runStatsCli(
|
||||
|
|
|
|||
|
|
@ -1,6 +1,5 @@
|
|||
// SF Database Abstraction Layer
|
||||
// Provides a SQLite database with provider fallback chain:
|
||||
// node:sqlite (built-in) → better-sqlite3 (npm) → null (unavailable)
|
||||
// Provides a SQLite database via node:sqlite (Node >= 24 built-in).
|
||||
//
|
||||
// Exposes a unified sync API for decisions and requirements storage.
|
||||
// Schema is initialized on first open with WAL mode for file-backed DBs.
|
||||
|
|
@ -21,8 +20,8 @@
|
|||
// excluded from this invariant.
|
||||
|
||||
import { copyFileSync, existsSync, mkdirSync, realpathSync } from "node:fs";
|
||||
import { createRequire } from "node:module";
|
||||
import { dirname } from "node:path";
|
||||
import { DatabaseSync } from "node:sqlite";
|
||||
import { SF_STALE_STATE, SFError } from "./errors.js";
|
||||
import { getGateIdsForTurn, type OwnerTurn } from "./gate-registry.js";
|
||||
import type { VisionAlignmentMeetingRecord } from "./milestone-quality.js";
|
||||
|
|
@ -42,7 +41,6 @@ import { logError, logWarning } from "./workflow-logger.js";
|
|||
// pure structure with no runtime coupling.
|
||||
import type { StateManifest } from "./workflow-manifest.js";
|
||||
|
||||
const _require = createRequire(import.meta.url);
|
||||
|
||||
interface DbStatement {
|
||||
run(...params: unknown[]): unknown;
|
||||
|
|
@ -56,69 +54,14 @@ interface DbAdapter {
|
|||
close(): void;
|
||||
}
|
||||
|
||||
type ProviderName = "node:sqlite" | "better-sqlite3";
|
||||
|
||||
let providerName: ProviderName | null = null;
|
||||
let providerModule: unknown = null;
|
||||
let loadAttempted = false;
|
||||
|
||||
function suppressSqliteWarning(): void {
|
||||
const origEmit = process.emit;
|
||||
// Override via loose cast: Node's overloaded emit signature is not directly assignable.
|
||||
(process as any).emit = (event: string, ...args: unknown[]): boolean => {
|
||||
if (
|
||||
event === "warning" &&
|
||||
args[0] &&
|
||||
typeof args[0] === "object" &&
|
||||
"name" in args[0] &&
|
||||
(args[0] as { name: string }).name === "ExperimentalWarning" &&
|
||||
"message" in args[0] &&
|
||||
typeof (args[0] as { message: string }).message === "string" &&
|
||||
(args[0] as { message: string }).message.includes("SQLite")
|
||||
) {
|
||||
return false;
|
||||
}
|
||||
return origEmit.apply(process, [event, ...args] as Parameters<
|
||||
typeof process.emit
|
||||
>) as unknown as boolean;
|
||||
};
|
||||
}
|
||||
|
||||
function loadProvider(): void {
|
||||
if (loadAttempted) return;
|
||||
loadAttempted = true;
|
||||
|
||||
try {
|
||||
suppressSqliteWarning();
|
||||
const mod = _require("node:sqlite");
|
||||
if (mod.DatabaseSync) {
|
||||
providerModule = mod;
|
||||
providerName = "node:sqlite";
|
||||
return;
|
||||
}
|
||||
} catch {
|
||||
// unavailable
|
||||
}
|
||||
|
||||
try {
|
||||
const mod = _require("better-sqlite3");
|
||||
if (typeof mod === "function" || (mod && mod.default)) {
|
||||
providerModule = mod.default || mod;
|
||||
providerName = "better-sqlite3";
|
||||
return;
|
||||
}
|
||||
} catch {
|
||||
// unavailable
|
||||
}
|
||||
|
||||
const nodeMajor = parseInt(process.versions.node.split(".")[0], 10);
|
||||
const versionHint =
|
||||
nodeMajor < 22
|
||||
? ` SF requires Node >= 22.0.0 (current: v${process.versions.node}). Upgrade Node to fix this.`
|
||||
: "";
|
||||
process.stderr.write(
|
||||
`sf-db: No SQLite provider available (tried node:sqlite, better-sqlite3).${versionHint}\n`,
|
||||
);
|
||||
// node:sqlite is built-in in Node >= 24
|
||||
}
|
||||
|
||||
function normalizeRow(row: unknown): Record<string, unknown> | undefined {
|
||||
|
|
@ -184,17 +127,7 @@ function createAdapter(rawDb: unknown): DbAdapter {
|
|||
|
||||
function openRawDb(path: string): unknown {
|
||||
loadProvider();
|
||||
if (!providerModule || !providerName) return null;
|
||||
|
||||
if (providerName === "node:sqlite") {
|
||||
const { DatabaseSync } = providerModule as {
|
||||
DatabaseSync: new (path: string) => unknown;
|
||||
};
|
||||
return new DatabaseSync(path);
|
||||
}
|
||||
|
||||
const Database = providerModule as new (path: string) => unknown;
|
||||
return new Database(path);
|
||||
return new DatabaseSync(path);
|
||||
}
|
||||
|
||||
const SCHEMA_VERSION = 21;
|
||||
|
|
@ -1479,9 +1412,9 @@ let _dbOpenAttempted = false;
|
|||
/**
|
||||
* Get the name of the SQLite provider currently loaded (or null if unavailable).
|
||||
*/
|
||||
export function getDbProvider(): ProviderName | null {
|
||||
export function getDbProvider(): string | null {
|
||||
loadProvider();
|
||||
return providerName;
|
||||
return "node:sqlite";
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -1830,8 +1763,6 @@ export function _getAdapter(): DbAdapter | null {
|
|||
|
||||
export function _resetProvider(): void {
|
||||
loadAttempted = false;
|
||||
providerModule = null;
|
||||
providerName = null;
|
||||
}
|
||||
|
||||
export function upsertDecision(d: Omit<Decision, "seq">): void {
|
||||
|
|
|
|||
|
|
@ -13,10 +13,9 @@
|
|||
// Copyright (c) 2026 Jeremy McSpadden <jeremy@fluxlabs.net>
|
||||
|
||||
import { mkdirSync } from "node:fs";
|
||||
import { createRequire } from "node:module";
|
||||
import { join } from "node:path";
|
||||
import { DatabaseSync } from "node:sqlite";
|
||||
|
||||
const _require = createRequire(import.meta.url);
|
||||
|
||||
// ─── Types ───────────────────────────────────────────────────────────────
|
||||
|
||||
|
|
@ -38,60 +37,14 @@ interface DbLike {
|
|||
close(): void;
|
||||
}
|
||||
|
||||
type ProviderName = "node:sqlite" | "better-sqlite3";
|
||||
|
||||
let providerName: ProviderName | null = null;
|
||||
let providerModule: unknown = null;
|
||||
let loadAttempted = false;
|
||||
|
||||
function suppressSqliteWarning(): void {
|
||||
const origEmit = process.emit;
|
||||
// Override via loose cast: Node's overloaded emit signature is not directly assignable.
|
||||
(process as any).emit = (event: string, ...args: unknown[]): boolean => {
|
||||
if (
|
||||
event === "warning" &&
|
||||
args[0] &&
|
||||
typeof args[0] === "object" &&
|
||||
"name" in args[0] &&
|
||||
(args[0] as { name: string }).name === "ExperimentalWarning" &&
|
||||
"message" in args[0] &&
|
||||
typeof (args[0] as { message: string }).message === "string" &&
|
||||
(args[0] as { message: string }).message.includes("SQLite")
|
||||
) {
|
||||
return false;
|
||||
}
|
||||
return origEmit.apply(process, [event, ...args] as Parameters<
|
||||
typeof process.emit
|
||||
>) as unknown as boolean;
|
||||
};
|
||||
}
|
||||
|
||||
function loadProvider(): void {
|
||||
if (loadAttempted) return;
|
||||
loadAttempted = true;
|
||||
|
||||
try {
|
||||
suppressSqliteWarning();
|
||||
const mod = _require("node:sqlite");
|
||||
if (mod.DatabaseSync) {
|
||||
providerModule = mod;
|
||||
providerName = "node:sqlite";
|
||||
return;
|
||||
}
|
||||
} catch {
|
||||
// unavailable
|
||||
}
|
||||
|
||||
try {
|
||||
const mod = _require("better-sqlite3");
|
||||
if (typeof mod === "function" || (mod && mod.default)) {
|
||||
providerModule = mod.default || mod;
|
||||
providerName = "better-sqlite3";
|
||||
return;
|
||||
}
|
||||
} catch {
|
||||
// unavailable
|
||||
}
|
||||
// node:sqlite is built-in in Node >= 24
|
||||
}
|
||||
|
||||
function normalizeRow(row: unknown): Record<string, unknown> | undefined {
|
||||
|
|
@ -104,17 +57,7 @@ function normalizeRow(row: unknown): Record<string, unknown> | undefined {
|
|||
|
||||
function openRawDb(path: string): unknown {
|
||||
loadProvider();
|
||||
if (!providerModule || !providerName) return null;
|
||||
|
||||
if (providerName === "node:sqlite") {
|
||||
const { DatabaseSync } = providerModule as {
|
||||
DatabaseSync: new (path: string) => unknown;
|
||||
};
|
||||
return new DatabaseSync(path);
|
||||
}
|
||||
|
||||
const Database = providerModule as new (path: string) => unknown;
|
||||
return new Database(path);
|
||||
return new DatabaseSync(path);
|
||||
}
|
||||
|
||||
function wrapDb(rawDb: unknown): DbLike {
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue