mirror of
https://github.com/diegosouzapw/OmniRoute.git
synced 2026-07-31 12:22:14 +03:00
- preInitSqlJs() now memoizes an in-flight Promise (not just the resolved adapter) per filePath, so concurrent BATCH/STARTUP/HealthCheck/ ProviderLimitsSync callers at boot share one full-file read+WASM decode instead of each independently reloading the whole database — the thundering-herd amplifier of the OOM condition #6632 already partly fixed, left un-implemented by the reporter's own proposed fix (#6628). - sqljsAdapter's run/get/all now unwrap a lone named-parameter object (e.g. .all({ isActive: 1 }) for "WHERE is_active = @isActive", the same call shape getProviderConnections() already uses against better-sqlite3) before calling sql.js's stmt.bind(), expanding it to the @/:/$ sigil variants sql.js's own named-bind path requires. Previously the object was wrapped into an array and sql.js took the positional-bind path, throwing "Wrong API use : tried to bind a value of an unknown type ([object Object])." whenever the sql.js WASM fallback driver was active — exactly the error #6802 reported (misattributed to better-sqlite3). Regression tests added to tests/unit/db-adapters/driverFactory.test.ts and tests/unit/db-adapters/sqljsAdapter.test.ts, both proven RED against the prior code and GREEN after the fix.
179 lines
6.8 KiB
TypeScript
179 lines
6.8 KiB
TypeScript
import { test, describe } from "node:test";
|
|
import assert from "node:assert/strict";
|
|
|
|
const { createSqlJsAdapter } = await import("../../../src/lib/db/adapters/sqljsAdapter.ts");
|
|
|
|
describe("sqljsAdapter", () => {
|
|
test("abre DB in-memory e executa CRUD básico", async () => {
|
|
const adapter = await createSqlJsAdapter(":memory:");
|
|
|
|
adapter.exec("CREATE TABLE test (id INTEGER PRIMARY KEY, val TEXT)");
|
|
const result = adapter.prepare("INSERT INTO test (val) VALUES (?)").run("hello");
|
|
assert.equal(result.changes, 1);
|
|
|
|
const row = adapter
|
|
.prepare("SELECT val FROM test WHERE id = ?")
|
|
.get(result.lastInsertRowid) as { val: string };
|
|
assert.equal(row.val, "hello");
|
|
|
|
const rows = adapter.prepare("SELECT * FROM test").all();
|
|
assert.equal(rows.length, 1);
|
|
|
|
adapter.close();
|
|
});
|
|
|
|
test("driver é 'sql.js'", async () => {
|
|
const adapter = await createSqlJsAdapter(":memory:");
|
|
assert.equal(adapter.driver, "sql.js");
|
|
adapter.close();
|
|
});
|
|
|
|
test("pragma retorna valor", async () => {
|
|
const adapter = await createSqlJsAdapter(":memory:");
|
|
const mode = adapter.pragma("journal_mode", { simple: true });
|
|
assert.ok(mode !== null && mode !== undefined);
|
|
adapter.close();
|
|
});
|
|
|
|
test("transaction é atômica — rollback em erro", async () => {
|
|
const adapter = await createSqlJsAdapter(":memory:");
|
|
adapter.exec("CREATE TABLE tx_test (id INTEGER PRIMARY KEY, val TEXT NOT NULL)");
|
|
|
|
const insert = adapter.transaction(() => {
|
|
adapter.prepare("INSERT INTO tx_test (val) VALUES (?)").run("ok");
|
|
throw new Error("rollback!");
|
|
});
|
|
|
|
assert.throws(() => insert(), /rollback/);
|
|
|
|
const count = adapter.prepare("SELECT COUNT(*) as cnt FROM tx_test").get() as { cnt: number };
|
|
assert.equal(count.cnt, 0, "Rollback deve ter desfeito o insert");
|
|
adapter.close();
|
|
});
|
|
|
|
test("transaction confirma quando não lança erro", async () => {
|
|
const adapter = await createSqlJsAdapter(":memory:");
|
|
adapter.exec("CREATE TABLE commit_test (id INTEGER PRIMARY KEY, val TEXT)");
|
|
|
|
const insert = adapter.transaction(() => {
|
|
adapter.prepare("INSERT INTO commit_test (val) VALUES (?)").run("committed");
|
|
});
|
|
insert();
|
|
|
|
const count = adapter.prepare("SELECT COUNT(*) as cnt FROM commit_test").get() as {
|
|
cnt: number;
|
|
};
|
|
assert.equal(count.cnt, 1);
|
|
adapter.close();
|
|
});
|
|
|
|
test("escreve em arquivo e relê corretamente", async (t) => {
|
|
const os = await import("node:os");
|
|
const path = await import("node:path");
|
|
const fs = await import("node:fs");
|
|
|
|
const tmpFile = path.join(os.tmpdir(), `sqljs_test_${Date.now()}.sqlite`);
|
|
t.after(() => {
|
|
try {
|
|
fs.unlinkSync(tmpFile);
|
|
} catch {}
|
|
});
|
|
|
|
const writer = await createSqlJsAdapter(tmpFile);
|
|
writer.exec("CREATE TABLE items (id INTEGER PRIMARY KEY, name TEXT)");
|
|
writer.prepare("INSERT INTO items (name) VALUES (?)").run("test-value");
|
|
writer.close();
|
|
|
|
const reader = await createSqlJsAdapter(tmpFile);
|
|
const row = reader.prepare("SELECT name FROM items WHERE id = 1").get() as { name: string };
|
|
assert.equal(row.name, "test-value");
|
|
reader.close();
|
|
});
|
|
|
|
// #6802: named-parameter object binds ("... WHERE is_active = @isActive"
|
|
// called as .all({ isActive: 1 })) must work the same way they do against
|
|
// better-sqlite3, instead of throwing sql.js's own
|
|
// "Wrong API use : tried to bind a value of an unknown type (...)." error.
|
|
describe("named-parameter object bind (#6802)", () => {
|
|
test("all() with a single named-params object mirrors getProviderConnections", async () => {
|
|
const adapter = await createSqlJsAdapter(":memory:");
|
|
adapter.exec(
|
|
"CREATE TABLE provider_connections (id INTEGER PRIMARY KEY, provider TEXT, is_active INTEGER)"
|
|
);
|
|
adapter
|
|
.prepare("INSERT INTO provider_connections (provider, is_active) VALUES (?, ?)")
|
|
.run("glm", 1);
|
|
adapter
|
|
.prepare("INSERT INTO provider_connections (provider, is_active) VALUES (?, ?)")
|
|
.run("openai", 0);
|
|
|
|
const sql =
|
|
"SELECT * FROM provider_connections WHERE is_active = @isActive ORDER BY id ASC";
|
|
const rows = adapter.prepare(sql).all({ isActive: 1 }) as Array<{ provider: string }>;
|
|
|
|
assert.equal(rows.length, 1, "expected exactly 1 active provider connection");
|
|
assert.equal(rows[0].provider, "glm");
|
|
adapter.close();
|
|
});
|
|
|
|
test("get() with a single named-params object resolves the row", async () => {
|
|
const adapter = await createSqlJsAdapter(":memory:");
|
|
adapter.exec("CREATE TABLE t (id INTEGER PRIMARY KEY, val TEXT)");
|
|
adapter.prepare("INSERT INTO t (val) VALUES (?)").run("named-get");
|
|
|
|
const row = adapter.prepare("SELECT val FROM t WHERE id = @id").get({ id: 1 }) as {
|
|
val: string;
|
|
};
|
|
assert.equal(row.val, "named-get");
|
|
adapter.close();
|
|
});
|
|
|
|
test("run() with a single named-params object binds correctly", async () => {
|
|
const adapter = await createSqlJsAdapter(":memory:");
|
|
adapter.exec("CREATE TABLE t (id INTEGER PRIMARY KEY, val TEXT)");
|
|
const result = adapter
|
|
.prepare("INSERT INTO t (val) VALUES (@val)")
|
|
.run({ val: "named-run" });
|
|
assert.equal(result.changes, 1);
|
|
|
|
const row = adapter
|
|
.prepare("SELECT val FROM t WHERE id = ?")
|
|
.get(result.lastInsertRowid) as { val: string };
|
|
assert.equal(row.val, "named-run");
|
|
adapter.close();
|
|
});
|
|
|
|
test("supports :name and $name sigils in addition to @name", async () => {
|
|
const adapter = await createSqlJsAdapter(":memory:");
|
|
adapter.exec("CREATE TABLE t (id INTEGER PRIMARY KEY, val TEXT)");
|
|
adapter.prepare("INSERT INTO t (val) VALUES (?)").run("colon-sigil");
|
|
adapter.prepare("INSERT INTO t (val) VALUES (?)").run("dollar-sigil");
|
|
|
|
const colonRow = adapter.prepare("SELECT val FROM t WHERE val = :val").get({
|
|
val: "colon-sigil",
|
|
}) as { val: string };
|
|
assert.equal(colonRow.val, "colon-sigil");
|
|
|
|
const dollarRow = adapter.prepare("SELECT val FROM t WHERE val = $val").get({
|
|
val: "dollar-sigil",
|
|
}) as { val: string };
|
|
assert.equal(dollarRow.val, "dollar-sigil");
|
|
adapter.close();
|
|
});
|
|
|
|
test("existing positional-array binding still works unchanged", async () => {
|
|
const adapter = await createSqlJsAdapter(":memory:");
|
|
adapter.exec("CREATE TABLE t (id INTEGER PRIMARY KEY, a TEXT, b TEXT)");
|
|
const result = adapter.prepare("INSERT INTO t (a, b) VALUES (?, ?)").run("x", "y");
|
|
assert.equal(result.changes, 1);
|
|
|
|
const row = adapter
|
|
.prepare("SELECT a, b FROM t WHERE id = ?")
|
|
.get(result.lastInsertRowid) as { a: string; b: string };
|
|
assert.equal(row.a, "x");
|
|
assert.equal(row.b, "y");
|
|
adapter.close();
|
|
});
|
|
});
|
|
});
|