refactor: dedupe small async primitives (#105961)

* refactor(sqlite): remove unused async Kysely driver

Co-authored-by: Sarah Fortune <sarah.fortune@gmail.com>

* refactor(media): share promise memoization

* chore(sqlite): prune stale Kysely guardrails

* chore(media): lower runner LOC baseline

* style(shared): format promise memo tests

* chore(deadcode): refresh main export baseline

---------

Co-authored-by: Sarah Fortune <sarah.fortune@gmail.com>
This commit is contained in:
Peter Steinberger
2026-07-12 22:34:53 -07:00
committed by GitHub
parent bd7f3cffd3
commit 5cb074ece5
11 changed files with 114 additions and 458 deletions

View File

@@ -19,15 +19,9 @@ const ts = require("typescript");
const repoRoot = resolveRepoRoot(import.meta.url);
const sourceRoots = [path.join(repoRoot, "src")];
const kyselyRawAllowPaths = new Set([
"src/infra/kysely-node-sqlite.test.ts",
"src/infra/kysely-sync.ts",
]);
const kyselyRawAllowPaths = new Set(["src/infra/kysely-sync.ts"]);
const compiledRawAllowPaths = new Set([
"src/infra/kysely-node-sqlite.ts",
"src/infra/kysely-node-sqlite.test.ts",
]);
const compiledRawAllowPaths = new Set(["src/infra/kysely-node-sqlite.ts"]);
const rawSqliteAllowPathGroups = {
"native Kysely adapter and sync execution": [

View File

@@ -3114,6 +3114,7 @@ export const KNIP_UNUSED_EXPORT_BASELINE = [
"src/gateway/session-transcript-readers.ts: readRecentSessionUsageFromTranscriptAsync",
"src/gateway/session-transcript-readers.ts: readSessionMessageCount",
"src/gateway/session-transcript-readers.ts: readSessionMessages",
"src/gateway/session-transcript-readers.ts: ResolvedTranscriptReadTarget",
"src/gateway/session-transcript-readers.ts: visitSessionMessages",
"src/gateway/session-utils.fs.ts: archiveFileOnDisk",
"src/gateway/session-utils.fs.ts: archiveSessionTranscripts",

View File

@@ -1025,7 +1025,7 @@
"src/media-understanding/attachments.cache.ts": 523,
"src/media-understanding/image.ts": 678,
"src/media-understanding/runner.entries.ts": 1122,
"src/media-understanding/runner.ts": 1117,
"src/media-understanding/runner.ts": 1106,
"src/media-understanding/shared.ts": 689,
"src/media/fetch.ts": 689,
"src/media/parse.ts": 725,

View File

@@ -1,170 +0,0 @@
// Covers the Kysely dialect backed by Node sqlite.
import { DatabaseSync } from "node:sqlite";
import { CompiledQuery, Kysely, sql, type Generated } from "kysely";
import { afterEach, describe, expect, it, vi } from "vitest";
import { NodeSqliteKyselyDialect } from "./kysely-node-sqlite.js";
type TestDatabase = {
person: {
id: Generated<number>;
name: string;
};
};
describe("NodeSqliteKyselyDialect", () => {
let db: Kysely<TestDatabase> | undefined;
afterEach(async () => {
await db?.destroy();
db = undefined;
});
it("uses node:sqlite with raw row-returning queries and returning clauses", async () => {
db = await createTestDb();
await expect(db.selectFrom("person").selectAll().execute()).resolves.toEqual([
{ id: 1, name: "Ada" },
]);
await expect(sql`select name from person where id = ${1}`.execute(db)).resolves.toEqual({
rows: [{ name: "Ada" }],
});
await expect(
db.insertInto("person").values({ name: "Grace" }).returning(["id", "name"]).execute(),
).resolves.toEqual([{ id: 2, name: "Grace" }]);
await expect(
sql`insert into person (name) values ('Lin') returning *`.execute(db),
).resolves.toEqual({
rows: [{ id: 3, name: "Lin" }],
});
const ignoredInsert = await sql`
insert or ignore into person (id, name) values (${1}, ${"Ada Again"})
`.execute(db);
expect(ignoredInsert.insertId).toBeUndefined();
expect(ignoredInsert.numAffectedRows).toBe(0n);
const update = await sql`update person set name = ${"Ada Lovelace"} where id = ${1}`.execute(
db,
);
expect(update.insertId).toBeUndefined();
expect(update.numAffectedRows).toBe(1n);
});
it("creates the database lazily and runs the connection hook once", async () => {
const sqlite = new DatabaseSync(":memory:");
const createDatabase = vi.fn(() => sqlite);
const onCreateConnection = vi.fn(async (connection) => {
await connection.executeQuery(CompiledQuery.raw("pragma user_version = 7"));
});
db = new Kysely<TestDatabase>({
dialect: new NodeSqliteKyselyDialect({
database: createDatabase,
onCreateConnection,
}),
});
await expect(sql<{ user_version: number }>`pragma user_version`.execute(db)).resolves.toEqual({
rows: [{ user_version: 7 }],
});
expect(createDatabase).toHaveBeenCalledTimes(1);
expect(onCreateConnection).toHaveBeenCalledTimes(1);
});
it("returns insert metadata only for changed insert statements", async () => {
db = new Kysely<TestDatabase>({
dialect: new NodeSqliteKyselyDialect({
database: new DatabaseSync(":memory:"),
}),
});
await createPersonTable(db);
const insertResult = await db
.insertInto("person")
.values({ name: "Ada" })
.executeTakeFirstOrThrow();
expect(insertResult.insertId).toBe(1n);
expect(insertResult.numInsertedOrUpdatedRows).toBe(1n);
const updateResult = await db
.updateTable("person")
.set({ name: "Ada Lovelace" })
.where("id", "=", 1)
.executeTakeFirstOrThrow();
expect(updateResult.numUpdatedRows).toBe(1n);
const ignoredInsert = await sql`
insert or ignore into person (id, name) values (${1}, ${"Ada Again"})
`.execute(db);
expect(ignoredInsert.insertId).toBeUndefined();
expect(ignoredInsert.numAffectedRows).toBe(0n);
});
it("rolls back transactions and controlled savepoints", async () => {
db = new Kysely<TestDatabase>({
dialect: new NodeSqliteKyselyDialect({
database: new DatabaseSync(":memory:"),
}),
});
await createPersonTable(db);
await expect(
db.transaction().execute(async (trx) => {
await trx.insertInto("person").values({ name: "Rollback" }).execute();
throw new Error("rollback outer");
}),
).rejects.toThrow("rollback outer");
await expect(db.selectFrom("person").selectAll().execute()).resolves.toStrictEqual([]);
const trx = await db.startTransaction().execute();
await trx.insertInto("person").values({ name: "Ada" }).execute();
const afterAda = await trx.savepoint("after_ada").execute();
await afterAda.insertInto("person").values({ name: "Grace" }).execute();
const afterRollback = await afterAda.rollbackToSavepoint("after_ada").execute();
await afterRollback.insertInto("person").values({ name: "Lin" }).execute();
await afterRollback.commit().execute();
await expect(db.selectFrom("person").select("name").orderBy("id").execute()).resolves.toEqual([
{ name: "Ada" },
{ name: "Lin" },
]);
});
it("streams selected rows through node:sqlite iteration", async () => {
db = await createTestDb();
await db
.insertInto("person")
.values([{ name: "Grace" }, { name: "Lin" }])
.execute();
const rows: Array<{ id: number; name: string }> = [];
for await (const row of db.selectFrom("person").selectAll().orderBy("id").stream(1)) {
rows.push(row);
}
expect(rows).toEqual([
{ id: 1, name: "Ada" },
{ id: 2, name: "Grace" },
{ id: 3, name: "Lin" },
]);
});
});
async function createTestDb(): Promise<Kysely<TestDatabase>> {
const testDb = new Kysely<TestDatabase>({
dialect: new NodeSqliteKyselyDialect({
database: new DatabaseSync(":memory:"),
}),
});
await createPersonTable(testDb);
await testDb.insertInto("person").values({ name: "Ada" }).execute();
return testDb;
}
async function createPersonTable(testDb: Kysely<TestDatabase>): Promise<void> {
await testDb.schema
.createTable("person")
.addColumn("id", "integer", (col) => col.primaryKey().autoIncrement())
.addColumn("name", "text", (col) => col.notNull())
.execute();
}

View File

@@ -1,5 +1,4 @@
// Adapts Node's sync sqlite API to Kysely.
import type { DatabaseSync, SQLInputValue } from "node:sqlite";
// Build-only node:sqlite dialect for the synchronous execution helpers.
import type {
DatabaseConnection,
DatabaseIntrospector,
@@ -8,40 +7,14 @@ import type {
Driver,
Kysely,
QueryCompiler,
QueryResult,
TransactionSettings,
} from "kysely";
import {
CompiledQuery,
IdentifierNode,
RawNode,
SqliteAdapter,
SqliteIntrospector,
SqliteQueryCompiler,
createQueryId,
} from "kysely";
import { SqliteAdapter, SqliteIntrospector, SqliteQueryCompiler } from "kysely";
// Kysely dialect for Node's synchronous node:sqlite API. The driver serializes
// connection use because DatabaseSync is single-connection and blocking.
type MaybePromise<T> = T | Promise<T>;
/** Configuration for the node:sqlite Kysely dialect. */
export type NodeSqliteKyselyDialectConfig = {
database: DatabaseSync | (() => MaybePromise<DatabaseSync>);
onCreateConnection?: (connection: DatabaseConnection) => MaybePromise<void>;
transactionMode?: "deferred" | "immediate" | "exclusive";
};
/** Kysely dialect backed by a node:sqlite DatabaseSync instance. */
/** Kysely dialect that compiles node:sqlite queries without executing them. */
export class NodeSqliteKyselyDialect implements Dialect {
readonly #config: NodeSqliteKyselyDialectConfig;
constructor(config: NodeSqliteKyselyDialectConfig) {
this.#config = Object.freeze({ ...config });
}
createDriver(): Driver {
return new NodeSqliteKyselyDriver(this.#config);
return new CompileOnlySqliteDriver();
}
createQueryCompiler(): QueryCompiler {
@@ -49,7 +22,7 @@ export class NodeSqliteKyselyDialect implements Dialect {
}
createAdapter(): DialectAdapter {
return new SqliteAdapter();
return new CompileOnlySqliteAdapter();
}
createIntrospector(db: Kysely<unknown>): DatabaseIntrospector {
@@ -57,163 +30,44 @@ export class NodeSqliteKyselyDialect implements Dialect {
}
}
class NodeSqliteKyselyDriver implements Driver {
readonly #config: NodeSqliteKyselyDialectConfig;
readonly #mutex = new ConnectionMutex();
#db?: DatabaseSync;
#connection?: DatabaseConnection;
constructor(config: NodeSqliteKyselyDialectConfig) {
this.#config = Object.freeze({ ...config });
}
async init(): Promise<void> {
this.#db =
typeof this.#config.database === "function"
? await this.#config.database()
: this.#config.database;
this.#connection = new NodeSqliteKyselyConnection(this.#db);
await this.#config.onCreateConnection?.(this.#connection);
}
class CompileOnlySqliteDriver implements Driver {
async init(): Promise<void> {}
async acquireConnection(): Promise<DatabaseConnection> {
// Kysely expects async acquisition even though node:sqlite is sync; the
// mutex preserves transaction ordering across concurrent callers.
await this.#mutex.lock();
return this.#connection!;
throw createCompileOnlyExecutionError();
}
async beginTransaction(
connection: DatabaseConnection,
_connection: DatabaseConnection,
_settings: TransactionSettings,
): Promise<void> {
const mode = this.#config.transactionMode ?? "deferred";
await connection.executeQuery(CompiledQuery.raw(`begin ${mode}`));
throw createCompileOnlyExecutionError();
}
async commitTransaction(connection: DatabaseConnection): Promise<void> {
await connection.executeQuery(CompiledQuery.raw("commit"));
async commitTransaction(_connection: DatabaseConnection): Promise<void> {
throw createCompileOnlyExecutionError();
}
async rollbackTransaction(connection: DatabaseConnection): Promise<void> {
await connection.executeQuery(CompiledQuery.raw("rollback"));
async rollbackTransaction(_connection: DatabaseConnection): Promise<void> {
throw createCompileOnlyExecutionError();
}
async savepoint(
connection: DatabaseConnection,
savepointName: string,
compileQuery: QueryCompiler["compileQuery"],
): Promise<void> {
await connection.executeQuery(
compileQuery(createSavepointCommand("savepoint", savepointName), createQueryId()),
);
}
async releaseConnection(_connection: DatabaseConnection): Promise<void> {}
async rollbackToSavepoint(
connection: DatabaseConnection,
savepointName: string,
compileQuery: QueryCompiler["compileQuery"],
): Promise<void> {
await connection.executeQuery(
compileQuery(createSavepointCommand("rollback to", savepointName), createQueryId()),
);
}
async releaseSavepoint(
connection: DatabaseConnection,
savepointName: string,
compileQuery: QueryCompiler["compileQuery"],
): Promise<void> {
await connection.executeQuery(
compileQuery(createSavepointCommand("release", savepointName), createQueryId()),
);
}
async releaseConnection(): Promise<void> {
this.#mutex.unlock();
}
async destroy(): Promise<void> {
this.#db?.close();
this.#db = undefined;
this.#connection = undefined;
}
async destroy(): Promise<void> {}
}
class NodeSqliteKyselyConnection implements DatabaseConnection {
readonly #db: DatabaseSync;
constructor(db: DatabaseSync) {
this.#db = db;
}
executeQuery<O>(compiledQuery: CompiledQuery): Promise<QueryResult<O>> {
const { sql, parameters } = compiledQuery;
const stmt = this.#db.prepare(sql);
const sqliteParameters = parameters as SQLInputValue[];
if (stmt.columns().length > 0) {
return Promise.resolve({ rows: stmt.all(...sqliteParameters) as O[] });
}
const { changes, lastInsertRowid } = stmt.run(...sqliteParameters);
const baseResult: QueryResult<O> = {
numAffectedRows: BigInt(changes),
rows: [],
};
if (isInsertStatement(sql) && changes > 0) {
return Promise.resolve({
...baseResult,
insertId: BigInt(lastInsertRowid),
});
}
return Promise.resolve(baseResult);
}
async *streamQuery<O>(
compiledQuery: CompiledQuery,
_chunkSize?: number,
): AsyncIterableIterator<QueryResult<O>> {
const { sql, parameters } = compiledQuery;
const stmt = this.#db.prepare(sql);
for (const row of stmt.iterate(...(parameters as SQLInputValue[]))) {
yield { rows: [row as O] };
}
}
function createCompileOnlyExecutionError(): Error {
return new Error(
"getNodeSqliteKysely() returns a compile-only Kysely facade; use executeSqliteQuerySync() to execute node:sqlite queries.",
);
}
function isInsertStatement(sql: string): boolean {
return sql.trimStart().toLowerCase().startsWith("insert");
}
function createSavepointCommand(command: string, savepointName: string): RawNode {
return RawNode.createWithChildren([
RawNode.createWithSql(`${command} `),
IdentifierNode.create(savepointName),
]);
}
class ConnectionMutex {
#promise?: Promise<void>;
#resolve?: () => void;
async lock(): Promise<void> {
while (this.#promise) {
await this.#promise;
}
this.#promise = new Promise((resolve) => {
this.#resolve = resolve;
});
}
unlock(): void {
const resolve = this.#resolve;
this.#promise = undefined;
this.#resolve = undefined;
resolve?.();
class CompileOnlySqliteAdapter extends SqliteAdapter {
override get supportsMultipleConnections(): boolean {
// Kysely's SQLite adapter installs a single-connection mutex. This facade
// never opens a real connection, so direct execution should reject from
// acquisition without leaving controlled transaction calls wedged.
return true;
}
}

View File

@@ -1,15 +1,18 @@
// Covers the compile-only Kysely facade used by sync node:sqlite helpers.
import { DatabaseSync } from "node:sqlite";
import type { Generated } from "kysely";
import { afterEach, describe, expect, it } from "vitest";
import {
clearNodeSqliteKyselyCacheForDatabase,
executeSqliteQuerySync,
executeSqliteQueryTakeFirstSync,
getNodeSqliteKysely,
iterateSqliteQuerySync,
} from "./kysely-sync.js";
type SyncHelperTestDatabase = {
items: {
id: number;
id: Generated<number>;
name: string;
};
};
@@ -26,15 +29,41 @@ describe("kysely sync helpers", () => {
database = undefined;
});
it("executes compiled queries through the sync helpers", () => {
database = new DatabaseSync(":memory:");
database.exec(
"create table items (id integer primary key autoincrement, name text not null unique)",
);
const db = getNodeSqliteKysely<SyncHelperTestDatabase>(database);
const insertResult = executeSqliteQuerySync(
database,
db.insertInto("items").values({ name: "Ada" }),
);
expect(insertResult.insertId).toBe(1n);
expect(insertResult.numAffectedRows).toBe(1n);
expect(
executeSqliteQuerySync(
database,
db.insertInto("items").values({ name: "Grace" }).returning(["id", "name"]),
).rows,
).toEqual([{ id: 2, name: "Grace" }]);
const select = db.selectFrom("items").selectAll().orderBy("id");
expect(executeSqliteQueryTakeFirstSync(database, select)).toEqual({ id: 1, name: "Ada" });
expect([...iterateSqliteQuerySync(database, select)]).toEqual([
{ id: 1, name: "Ada" },
{ id: 2, name: "Grace" },
]);
});
it("keeps the builder facade compile-only and fails direct execution", async () => {
database = new DatabaseSync(":memory:");
database.exec("create table items (id integer primary key, name text not null)");
const db = getNodeSqliteKysely<SyncHelperTestDatabase>(database);
const insertQuery = db.insertInto("items").values({ id: 1, name: "Ada" });
expect(insertQuery.compile().sql).toContain("insert into");
executeSqliteQuerySync(database, insertQuery);
executeSqliteQuerySync(database, db.insertInto("items").values({ id: 1, name: "Ada" }));
expect(executeSqliteQuerySync(database, db.selectFrom("items").selectAll()).rows).toEqual([
{ id: 1, name: "Ada" },
]);

View File

@@ -1,14 +1,7 @@
// Adapts node:sqlite sync database calls for Kysely-style query execution.
import type { DatabaseSync, SQLInputValue } from "node:sqlite";
import type {
CompiledQuery,
DatabaseConnection,
Driver,
Kysely,
QueryResult,
TransactionSettings,
} from "kysely";
import { InsertQueryNode, Kysely as KyselyInstance, SqliteAdapter } from "kysely";
import type { CompiledQuery, Kysely, QueryResult } from "kysely";
import { InsertQueryNode, Kysely as KyselyInstance } from "kysely";
import { NodeSqliteKyselyDialect } from "./kysely-node-sqlite.js";
// Sync query helpers execute compiled Kysely SQL against node:sqlite without
@@ -25,7 +18,7 @@ export function getNodeSqliteKysely<Database>(db: DatabaseSync): Kysely<Database
return existing as Kysely<Database>;
}
const kysely = new KyselyInstance<Database>({
dialect: new CompileOnlyNodeSqliteKyselyDialect(),
dialect: new NodeSqliteKyselyDialect(),
});
kyselyByDatabase.set(db, kysely as Kysely<unknown>);
return kysely;
@@ -91,63 +84,3 @@ export function executeSqliteQueryTakeFirstSync<Row>(
export function clearNodeSqliteKyselyCacheForDatabase(db: DatabaseSync): void {
kyselyByDatabase.delete(db);
}
class CompileOnlyNodeSqliteKyselyDialect extends NodeSqliteKyselyDialect {
constructor() {
super({ database: createUnavailableDatabase });
}
override createDriver(): Driver {
return new CompileOnlySqliteDriver();
}
override createAdapter(): SqliteAdapter {
return new CompileOnlySqliteAdapter();
}
}
class CompileOnlySqliteDriver implements Driver {
async init(): Promise<void> {}
async acquireConnection(): Promise<DatabaseConnection> {
throw createCompileOnlyExecutionError();
}
async beginTransaction(
_connection: DatabaseConnection,
_settings: TransactionSettings,
): Promise<void> {
throw createCompileOnlyExecutionError();
}
async commitTransaction(_connection: DatabaseConnection): Promise<void> {
throw createCompileOnlyExecutionError();
}
async rollbackTransaction(_connection: DatabaseConnection): Promise<void> {
throw createCompileOnlyExecutionError();
}
async releaseConnection(_connection: DatabaseConnection): Promise<void> {}
async destroy(): Promise<void> {}
}
function createCompileOnlyExecutionError(): Error {
return new Error(
"getNodeSqliteKysely() returns a compile-only Kysely facade; use executeSqliteQuerySync() to execute node:sqlite queries.",
);
}
function createUnavailableDatabase(): never {
throw createCompileOnlyExecutionError();
}
class CompileOnlySqliteAdapter extends SqliteAdapter {
override get supportsMultipleConnections(): boolean {
// Kysely's SQLite adapter installs a single-connection mutex. This facade
// never opens a real connection, so direct execution should reject from
// acquisition without leaving controlled transaction calls wedged.
return true;
}
}

View File

@@ -3,6 +3,7 @@ import fs from "node:fs/promises";
import path from "node:path";
import type { MediaUnderstandingModelConfig } from "../config/types.tools.js";
import { runExec } from "../process/exec.js";
import { getOrCreatePromise } from "../shared/lazy-promise.js";
import { fileExists } from "./fs.js";
export type LocalAudioCandidate = {
@@ -154,11 +155,7 @@ async function findBinary(
checkExecutable: (filePath: string, platform: NodeJS.Platform) => Promise<boolean> = isExecutable,
): Promise<string | null> {
const key = `${platform}\0${env.PATH ?? ""}\0${env.PATHEXT ?? ""}\0${name}`;
const cached = binaryCache.get(key);
if (cached) {
return await cached;
}
const lookup = (async () => {
return await getOrCreatePromise(binaryCache, key, async () => {
const direct = name.trim();
const candidates = binaryNames(direct, platform, env);
if (direct.includes("/") || direct.includes("\\")) {
@@ -186,9 +183,7 @@ async function findBinary(
}
}
return null;
})();
binaryCache.set(key, lookup);
return await lookup;
});
}
async function inspectLinkedLibraries(
@@ -196,11 +191,7 @@ async function inspectLinkedLibraries(
platform: NodeJS.Platform,
): Promise<string | null> {
const key = `${platform}\0${filePath}`;
const cached = libraryCache.get(key);
if (cached) {
return await cached;
}
const inspection = (async () => {
return await getOrCreatePromise(libraryCache, key, async () => {
const command = platform === "darwin" ? "otool" : platform === "linux" ? "readelf" : null;
if (!command) {
return null;
@@ -212,9 +203,7 @@ async function inspectLinkedLibraries(
} catch {
return null;
}
})();
libraryCache.set(key, inspection);
return await inspection;
});
}
async function inspectWhisperBackend(params: {

View File

@@ -41,6 +41,7 @@ import { logWarn } from "../logger.js";
import { resolveChannelInboundAttachmentRoots } from "../media/channel-inbound-roots.js";
import { getDefaultMediaLocalRoots } from "../media/local-roots.js";
import { runExec } from "../process/exec.js";
import { getOrCreatePromise } from "../shared/lazy-promise.js";
import { createLazyRuntimeModule, createLazyRuntimeNamedExport } from "../shared/lazy-runtime.js";
import { MediaAttachmentCache, selectAttachments } from "./attachments.js";
import {
@@ -390,11 +391,7 @@ async function isExecutable(filePath: string): Promise<boolean> {
}
async function findBinary(name: string): Promise<string | null> {
const cached = binaryCache.get(name);
if (cached) {
return cached;
}
const resolved = (async () => {
return await getOrCreatePromise(binaryCache, name, async () => {
const direct = expandHomeDir(name.trim());
if (direct && hasPathSeparator(direct)) {
for (const candidate of candidateBinaryNames(direct)) {
@@ -424,9 +421,7 @@ async function findBinary(name: string): Promise<string | null> {
}
return null;
})();
binaryCache.set(name, resolved);
return resolved;
});
}
async function probeAntigravityCliCandidate(command: string): Promise<string | null> {
@@ -455,11 +450,7 @@ async function probeAntigravityCliCandidate(command: string): Promise<string | n
}
async function resolveAntigravityCliBinary(): Promise<string | null> {
const cached = antigravityCliCache.get("agy");
if (cached) {
return cached;
}
const resolved = (async () => {
return await getOrCreatePromise(antigravityCliCache, "agy", async () => {
const configured = process.env.OPENCLAW_ANTIGRAVITY_CLI?.trim();
const candidates = [configured, "agy", "antigravity"].filter((value): value is string =>
Boolean(value),
@@ -471,9 +462,7 @@ async function resolveAntigravityCliBinary(): Promise<string | null> {
}
}
return null;
})();
antigravityCliCache.set("agy", resolved);
return resolved;
});
}
async function resolveAntigravityCliEntry(

View File

@@ -4,8 +4,30 @@ import {
createLazyImportLoader,
createLazyPromise,
createLazyPromiseLoader,
getOrCreatePromise,
} from "./lazy-promise.js";
describe("getOrCreatePromise", () => {
it("dedupes each key and leaves cache lifecycle to the caller", async () => {
const cache = new Map<string, Promise<string>>();
const create = vi.fn(async (key: string) => `loaded-${key}`);
const first = getOrCreatePromise(cache, "a", async () => await create("a"));
expect(getOrCreatePromise(cache, "a", async () => await create("a"))).toBe(first);
await expect(first).resolves.toBe("loaded-a");
await expect(getOrCreatePromise(cache, "b", async () => await create("b"))).resolves.toBe(
"loaded-b",
);
expect(create).toHaveBeenCalledTimes(2);
cache.clear();
await expect(getOrCreatePromise(cache, "a", async () => await create("a"))).resolves.toBe(
"loaded-a",
);
expect(create).toHaveBeenCalledTimes(3);
});
});
describe("createLazyPromise", () => {
it("returns a reusable single-flight loader", async () => {
let calls = 0;

View File

@@ -8,6 +8,21 @@ export type LazyPromiseLoader<T> = {
clear: () => void;
};
/** Returns the cached promise for a key, creating and storing it when absent. */
export function getOrCreatePromise<K, V>(
cache: Map<K, Promise<V>>,
key: K,
create: () => Promise<V>,
): Promise<V> {
const cached = cache.get(key);
if (cached) {
return cached;
}
const created = create();
cache.set(key, created);
return created;
}
/** Options for controlling lazy promise cache behavior. */
type LazyPromiseLoaderOptions = {
/** Keep rejected promises cached instead of allowing the next caller to retry. */