
Research
SANDWORM_MODE: Shai-Hulud-Style npm Worm Hijacks CI Workflows and Poisons AI Toolchains
An emerging npm supply chain attack that infects repos, steals CI secrets, and targets developer AI toolchains for further compromise.
@loro-dev/unisqlite
Advanced tools
Universal SQLite adapter for Node.js, Cloudflare Workers, and browsers.
import { openStore } from "@loro-dev/unisqlite";
const store = await openStore({ path: "my-database.db" });
// Basic operations
await store.run("CREATE TABLE users (id INTEGER PRIMARY KEY, name TEXT)");
await store.run("INSERT INTO users (name) VALUES (?)", ["Alice"]);
const users = await store.query("SELECT * FROM users");
// Transactions
await store.transaction(async (txn) => {
await txn.run("INSERT INTO users (name) VALUES (?)", ["Bob"]);
await txn.run("INSERT INTO users (name) VALUES (?)", ["Charlie"]);
});
await store.close();
For Cloudflare Durable Objects with the new SQLite persistence API:
import { CloudflareDOAdapter, createCloudflareDOAdapter } from "@loro-dev/unisqlite";
export class MyDurableObject extends DurableObject {
private db: CloudflareDOAdapter;
constructor(ctx: DurableObjectState, env: Env) {
super(ctx, env);
// Use the factory function for easy setup
this.db = createCloudflareDOAdapter(ctx.storage.sql);
}
async fetch(request: Request) {
// Use the adapter like any other UniSQLite connection
await this.db.run("CREATE TABLE IF NOT EXISTS users (id INTEGER PRIMARY KEY, name TEXT)");
await this.db.run("INSERT INTO users (name) VALUES (?)", ["Alice"]);
const users = await this.db.query("SELECT * FROM users");
return Response.json({ users });
}
}
UniSQLite provides three types of connections with different transaction capabilities:
"direct")transaction() and asyncTransaction()const store = await openStore({ path: "database.db" });
console.log(store.getConnectionType()); // "direct"
// Both transaction types are supported
await store.transaction(async (txn) => {
/* sync transaction */
});
await store.asyncTransaction(async (txn) => {
/* async transaction */
});
"syncTxn")transaction() callstransaction() using itself (for nested operations)asyncTransaction() - will throw an errorawait store.transaction(async (txn) => {
console.log(txn.getConnectionType()); // "syncTxn"
// ✅ Allowed: nested transaction using same connection
await txn.transaction(async (nested) => {
// nested === txn (same connection)
await nested.run("INSERT INTO users (name) VALUES (?)", ["user"]);
});
// ❌ Not allowed: will throw error
await txn.asyncTransaction(async (nested) => {
// Error: "asyncTransaction is not supported in syncTxn connections"
});
});
"asyncTxn")asyncTransaction() callstransaction() and asyncTransaction() using itselfawait store.asyncTransaction(
async (txn) => {
console.log(txn.getConnectionType()); // "asyncTxn"
// ✅ Both transaction types are supported
await txn.transaction(async (nested) => {
// nested === txn (same connection)
});
await txn.asyncTransaction(async (nested) => {
// nested === txn (same connection)
});
},
{ timeoutMs: 30000 }
);
The default unisqlite export stays minimal and uses dynamic imports so optional peers are only loaded when their platform adapter is actually needed. To make bundling and installations more intentional, use platform-specific entrypoints:
better-sqlite3): import { openNodeStore, NodeAdapter } from "unisqlite/node";broadcast-channel, and @sqlite.org/sqlite-wasm if you load SQLite from npm): import { openStore } from "unisqlite/browser";import { openCloudflareStore, CloudflareDOAdapter, createCloudflareDOAdapter } from "unisqlite/cloudflare";These entrypoints avoid pulling in other platform adapters, keeping browser bundles slim and Node installs free from WASM/downloaded assets.
interface UniStoreConnection {
getConnectionType(): "direct" | "syncTxn" | "asyncTxn";
// ... other methods
}
// Synchronous transaction - function cannot return Promise
transaction<T>(fn: (tx: UniStoreConnection) => T extends Promise<unknown> ? never : T): T extends Promise<unknown> ? never : T;
// Asynchronous transaction - function can return Promise
asyncTransaction<T>(fn: (tx: UniStoreConnection) => Promise<T>, options?: { timeoutMs?: number }): Promise<T>;
| Connection Type | transaction() | asyncTransaction() |
|---|---|---|
"direct" | ✅ Supported | ✅ Supported |
"syncTxn" | ✅ Supported | ❌ Throws Error |
"asyncTxn" | ✅ Supported | ✅ Supported |
The transaction() method enforces synchronous functions at compile time:
// ✅ Valid - returns non-Promise value
const result = store.transaction((txn) => {
return "sync-result";
});
// ❌ TypeScript Error - returns Promise
const invalid = store.transaction((txn) => {
return Promise.resolve("async-result"); // Type error!
});
// ❌ TypeScript Error - async function
const invalid2 = store.transaction(async (txn) => {
return "result"; // Type error!
});
This design ensures:
FAQs
Cross-platform concurrent SQLite access layer
The npm package @loro-dev/unisqlite receives a total of 30 weekly downloads. As such, @loro-dev/unisqlite popularity was classified as not popular.
We found that @loro-dev/unisqlite demonstrated a healthy version release cadence and project activity because the last version was released less than a year ago. It has 2 open source maintainers collaborating on the project.
Did you know?

Socket for GitHub automatically highlights issues in each pull request and monitors the health of all your open source dependencies. Discover the contents of your packages and block harmful activity before you install or update your dependencies.

Research
An emerging npm supply chain attack that infects repos, steals CI secrets, and targets developer AI toolchains for further compromise.

Company News
Socket is proud to join the OpenJS Foundation as a Silver Member, deepening our commitment to the long-term health and security of the JavaScript ecosystem.

Security News
npm now links to Socket's security analysis on every package page. Here's what you'll find when you click through.