penambahan web socket

This commit is contained in:
2025-09-18 19:01:22 +07:00
parent 1d053646a9
commit d7bb2eb5bb
15070 changed files with 2402916 additions and 0 deletions

No files matched your search

+21
View File
@@ -0,0 +1,21 @@
MIT License
Copyright (c) Pooya Parsa <[email protected]>
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
+57
View File
@@ -0,0 +1,57 @@
# DB0
<!-- automd:badges color=yellow -->
[![npm version](https://img.shields.io/npm/v/db0?color=yellow)](https://npmjs.com/package/db0)
[![npm downloads](https://img.shields.io/npm/dm/db0?color=yellow)](https://npm.chart.dev/db0)
<!-- /automd -->
> [!IMPORTANT]
> DB0 development is in the early stages. Track progress via [GitHub issues](https://github.com/unjs/db0/issues).
DB0 is a lightweight SQL connector:
✅ Works with several SQL [connectors](https://db0.unjs.io/connectors).
✅ Can be [integrated](https://db0.unjs.io/integrations) with ORMs and embedded into frameworks.
✅ Provides a simple but elegant query API out of the box.
👉 Read [📚 Documentation](https://db0.unjs.io)
## Contribution
<details>
<summary>Local development</summary>
- Clone this repository
- Install the latest LTS version of [Node.js](https://nodejs.org/en/)
- Enable [Corepack](https://github.com/nodejs/corepack) using `corepack enable`
- Install dependencies using `pnpm install`
- Run tests using `pnpm dev` or `pnpm test`
</details>
<!-- /automd -->
## License
<!-- automd:contributors license=MIT author="pi0" -->
Published under the [MIT](https://github.com/unjs/db0/blob/main/LICENSE) license.
Made by [@pi0](https://github.com/pi0) and [community](https://github.com/unjs/db0/graphs/contributors) 💛
<br><br>
<a href="https://github.com/unjs/db0/graphs/contributors">
<img src="https://contrib.rocks/image?repo=unjs/db0" />
</a>
<!-- /automd -->
<!-- automd:with-automd -->
---
_🤖 auto updated with [automd](https://automd.unjs.io)_
<!-- /automd -->
@@ -0,0 +1,11 @@
import type { Primitive, Statement, PreparedStatement } from "db0";
export declare abstract class BoundableStatement<T> implements Statement {
_statement: T;
constructor(rawStmt: T);
bind(...params: Primitive[]): PreparedStatement;
abstract all(...params: Primitive[]): Promise<unknown[]>;
abstract run(...params: Primitive[]): Promise<{
success: boolean;
}>;
abstract get(...params: Primitive[]): Promise<unknown>;
}
@@ -0,0 +1,29 @@
export class BoundableStatement {
_statement;
constructor(rawStmt) {
this._statement = rawStmt;
}
bind(...params) {
return new BoundStatement(this, params);
}
}
class BoundStatement {
#statement;
#params;
constructor(statement, params) {
this.#statement = statement;
this.#params = params;
}
bind(...params) {
return new BoundStatement(this.#statement, params);
}
all() {
return this.#statement.all(...this.#params);
}
run() {
return this.#statement.run(...this.#params);
}
get() {
return this.#statement.get(...this.#params);
}
}
@@ -0,0 +1,8 @@
import Database from "better-sqlite3";
import type { Connector } from "db0";
export interface ConnectorOptions {
cwd?: string;
path?: string;
name?: string;
}
export default function sqliteConnector(opts: ConnectorOptions): Connector<Database.Database>;
@@ -0,0 +1,42 @@
import { resolve, dirname } from "node:path";
import { mkdirSync } from "node:fs";
import Database from "better-sqlite3";
import { BoundableStatement } from "./_internal/statement.mjs";
export default function sqliteConnector(opts) {
let _db;
const getDB = () => {
if (_db) {
return _db;
}
if (opts.name === ":memory:") {
_db = new Database(":memory:");
return _db;
}
const filePath = resolve(
opts.cwd || ".",
opts.path || `.data/${opts.name || "db"}.sqlite3`
);
mkdirSync(dirname(filePath), { recursive: true });
_db = new Database(filePath);
return _db;
};
return {
name: "sqlite",
dialect: "sqlite",
getInstance: () => getDB(),
exec: (sql) => getDB().exec(sql),
prepare: (sql) => new StatementWrapper(() => getDB().prepare(sql))
};
}
class StatementWrapper extends BoundableStatement {
async all(...params) {
return this._statement().all(...params);
}
async run(...params) {
const res = this._statement().run(...params);
return { success: res.changes > 0, ...res };
}
async get(...params) {
return this._statement().get(...params);
}
}
@@ -0,0 +1,8 @@
import { Database } from "bun:sqlite";
import type { Connector } from "db0";
export interface ConnectorOptions {
cwd?: string;
path?: string;
name?: string;
}
export default function bunSqliteConnector(opts: ConnectorOptions): Connector<Database>;
@@ -0,0 +1,42 @@
import { resolve, dirname } from "node:path";
import { mkdirSync } from "node:fs";
import { Database } from "bun:sqlite";
import { BoundableStatement } from "./_internal/statement.mjs";
export default function bunSqliteConnector(opts) {
let _db;
const getDB = () => {
if (_db) {
return _db;
}
if (opts.name === ":memory:") {
_db = new Database(":memory:");
} else {
const filePath = resolve(
opts.cwd || ".",
opts.path || `.data/${opts.name || "db"}.bun.sqlite`
);
mkdirSync(dirname(filePath), { recursive: true });
_db = new Database(filePath);
}
return _db;
};
return {
name: "sqlite",
dialect: "sqlite",
getInstance: () => getDB(),
exec: (sql) => getDB().exec(sql),
prepare: (sql) => new StatementWrapper(getDB().prepare(sql))
};
}
class StatementWrapper extends BoundableStatement {
all(...params) {
return Promise.resolve(this._statement.all(...params));
}
run(...params) {
const res = this._statement.run(...params);
return Promise.resolve({ success: true, ...res });
}
get(...params) {
return Promise.resolve(this._statement.get(...params));
}
}
@@ -0,0 +1,6 @@
import type { D1Database } from '@cloudflare/workers-types';
import type { Connector } from "db0";
export interface ConnectorOptions {
bindingName?: string;
}
export default function cloudflareD1Connector(options: ConnectorOptions): Connector<D1Database>;
@@ -0,0 +1,31 @@
import { BoundableStatement } from "./_internal/statement.mjs";
export default function cloudflareD1Connector(options) {
const getDB = () => {
const binding = globalThis.__env__?.[options.bindingName] || globalThis.__cf_env__?.[options.bindingName];
if (!binding) {
throw new Error(`[db0] [d1] binding \`${options.bindingName}\` not found`);
}
return binding;
};
return {
name: "cloudflare-d1",
dialect: "sqlite",
getInstance: () => getDB(),
exec: (sql) => getDB().exec(sql),
prepare: (sql) => new StatementWrapper(getDB().prepare(sql))
};
}
class StatementWrapper extends BoundableStatement {
async all(...params) {
const res = await this._statement.bind(...params).all();
return res.results;
}
async run(...params) {
const res = await this._statement.bind(...params).run();
return res;
}
async get(...params) {
const res = await this._statement.bind(...params).first();
return res;
}
}
@@ -0,0 +1,7 @@
import type { Client } from "@libsql/client";
import type { Connector } from "db0";
export type ConnectorOptions = {
getClient: () => Client;
name?: string;
};
export default function libSqlCoreConnector(opts: ConnectorOptions): Connector<Client>;
@@ -0,0 +1,34 @@
import { BoundableStatement } from "../_internal/statement.mjs";
export default function libSqlCoreConnector(opts) {
const query = (sql) => opts.getClient().execute(sql);
return {
name: opts.name || "libsql-core",
dialect: "libsql",
getInstance: async () => opts.getClient(),
exec: (sql) => query(sql),
prepare: (sql) => new StatementWrapper(sql, query)
};
}
class StatementWrapper extends BoundableStatement {
#query;
#sql;
constructor(sql, query) {
super();
this.#sql = sql;
this.#query = query;
}
async all(...params) {
const res = await this.#query({ sql: this.#sql, args: params });
return res.rows;
}
async run(...params) {
const res = await this.#query({ sql: this.#sql, args: params });
return {
...res
};
}
async get(...params) {
const res = await this.#query({ sql: this.#sql, args: params });
return res.rows[0];
}
}
@@ -0,0 +1,4 @@
import type { Config, Client } from "@libsql/client";
import type { Connector } from "db0";
export type ConnectorOptions = Config;
export default function libSqlConnector(opts: ConnectorOptions): Connector<Client>;
@@ -0,0 +1,15 @@
import { createClient } from "@libsql/client/http";
import libSqlCore from "./core.mjs";
export default function libSqlConnector(opts) {
let _client;
const getClient = () => {
if (!_client) {
_client = createClient(opts);
}
return _client;
};
return libSqlCore({
name: "libsql-http",
getClient
});
}
@@ -0,0 +1,4 @@
import type { Config, Client } from "@libsql/client";
import type { Connector } from "db0";
export type ConnectorOptions = Config;
export default function libSqlConnector(opts: ConnectorOptions): Connector<Client>;
@@ -0,0 +1,15 @@
import { createClient } from "@libsql/client";
import libSqlCore from "./core.mjs";
export default function libSqlConnector(opts) {
let _client;
const getClient = () => {
if (!_client) {
_client = createClient(opts);
}
return _client;
};
return libSqlCore({
name: "libsql-node",
getClient
});
}
@@ -0,0 +1,4 @@
import type { Config, Client } from "@libsql/client";
import type { Connector } from "db0";
export type ConnectorOptions = Config;
export default function libSqlConnector(opts: ConnectorOptions): Connector<Client>;
@@ -0,0 +1,15 @@
import { createClient } from "@libsql/client/http";
import libSqlCore from "./core.mjs";
export default function libSqlConnector(opts) {
let _client;
const getClient = () => {
if (!_client) {
_client = createClient(opts);
}
return _client;
};
return libSqlCore({
name: "libsql-web",
getClient
});
}
@@ -0,0 +1,4 @@
import mysql from "mysql2/promise";
import type { Connector } from "db0";
export type ConnectorOptions = mysql.ConnectionOptions;
export default function mysqlConnector(opts: ConnectorOptions): Connector<mysql.Connection>;
@@ -0,0 +1,46 @@
import mysql from "mysql2/promise";
import { BoundableStatement } from "./_internal/statement.mjs";
export default function mysqlConnector(opts) {
let _connection;
const getConnection = async () => {
if (_connection) {
return _connection;
}
_connection = await mysql.createConnection({
...opts
});
return _connection;
};
const query = (sql, params) => getConnection().then((c) => c.query(sql, params)).then((res) => res[0]);
return {
name: "mysql",
dialect: "mysql",
getInstance: () => getConnection(),
exec: (sql) => query(sql),
prepare: (sql) => new StatementWrapper(sql, query)
};
}
class StatementWrapper extends BoundableStatement {
#query;
#sql;
constructor(sql, query) {
super();
this.#sql = sql;
this.#query = query;
}
async all(...params) {
const res = await this.#query(this.#sql, params);
return res;
}
async run(...params) {
const res = await this.#query(this.#sql, params);
return {
success: true,
...res
};
}
async get(...params) {
const res = await this.#query(this.#sql, params);
return res[0];
}
}
@@ -0,0 +1,8 @@
import type { Connector } from "db0";
import type { DatabaseSync } from "node:sqlite";
export interface ConnectorOptions {
cwd?: string;
path?: string;
name?: string;
}
export default function nodeSqlite3Connector(opts: ConnectorOptions): Connector<DatabaseSync>;
@@ -0,0 +1,50 @@
import { resolve, dirname } from "node:path";
import { mkdirSync } from "node:fs";
import { BoundableStatement } from "./_internal/statement.mjs";
export default function nodeSqlite3Connector(opts) {
let _db;
const getDB = () => {
if (_db) {
return _db;
}
const nodeSqlite = globalThis.process?.getBuiltinModule?.("node:sqlite");
if (!nodeSqlite) {
throw new Error("`node:sqlite` module is not available. Please ensure you are running in Node.js >= 22.5 or Deno >= 2.2.");
}
if (opts.name === ":memory:") {
_db = new nodeSqlite.DatabaseSync(":memory:");
return _db;
}
const filePath = resolve(
opts.cwd || ".",
opts.path || `.data/${opts.name || "db"}.sqlite`
);
mkdirSync(dirname(filePath), { recursive: true });
_db = new nodeSqlite.DatabaseSync(filePath);
return _db;
};
return {
name: "node-sqlite",
dialect: "sqlite",
getInstance: () => getDB(),
exec(sql) {
getDB().exec(sql);
return { success: true };
},
prepare: (sql) => new StatementWrapper(() => getDB().prepare(sql))
};
}
class StatementWrapper extends BoundableStatement {
async all(...params) {
const raws = this._statement().all(...params);
return raws;
}
async run(...params) {
const res = this._statement().run(...params);
return { success: true, ...res };
}
async get(...params) {
const raw = this._statement().get(...params);
return raw;
}
}
@@ -0,0 +1,5 @@
import type { PGliteOptions, PGliteInterfaceExtensions } from "@electric-sql/pglite";
import { PGlite } from "@electric-sql/pglite";
import type { Connector } from "db0";
export type ConnectorOptions = PGliteOptions;
export default function pgliteConnector<TOptions extends ConnectorOptions>(opts?: TOptions): Connector<PGlite & PGliteInterfaceExtensions<TOptions["extensions"]>>;
@@ -0,0 +1,58 @@
import { PGlite } from "@electric-sql/pglite";
import { BoundableStatement } from "./_internal/statement.mjs";
export default function pgliteConnector(opts) {
let _client;
function getClient() {
return _client ||= PGlite.create(opts).then((res) => _client = res);
}
const query = async (sql, params) => {
const client = await getClient();
const normalizedSql = normalizeParams(sql);
const result = await client.query(normalizedSql, params);
return result;
};
return {
name: "pglite",
dialect: "postgresql",
getInstance: () => getClient(),
exec: (sql) => query(sql),
prepare: (sql) => new StatementWrapper(sql, query)
};
}
function normalizeParams(sql) {
let i = 0;
return sql.replace(/\?/g, () => `$${++i}`);
}
class StatementWrapper extends BoundableStatement {
#query;
#sql;
constructor(sql, query) {
super();
this.#sql = sql;
this.#query = query;
}
async all(...params) {
const result = await this.#query(
this.#sql,
params
);
return result.rows;
}
async run(...params) {
const result = await this.#query(
this.#sql,
params
);
return {
success: true,
...result
};
}
async get(...params) {
const result = await this.#query(
this.#sql,
params
);
return result.rows[0];
}
}
@@ -0,0 +1,4 @@
import { Client, type Config } from "@planetscale/database";
import type { Connector } from "db0";
export type ConnectorOptions = Config;
export default function planetscaleConnector(opts: ConnectorOptions): Connector<Client>;
@@ -0,0 +1,45 @@
import { Client } from "@planetscale/database";
import { BoundableStatement } from "./_internal/statement.mjs";
export default function planetscaleConnector(opts) {
let _client;
function getClient() {
if (_client) {
return _client;
}
const client = new Client(opts);
_client = client;
return client;
}
const query = (sql, params) => getClient().execute(sql, params);
return {
name: "planetscale",
dialect: "mysql",
getInstance: () => getClient(),
exec: (sql) => query(sql),
prepare: (sql) => new StatementWrapper(sql, query)
};
}
class StatementWrapper extends BoundableStatement {
#query;
#sql;
constructor(sql, query) {
super();
this.#sql = sql;
this.#query = query;
}
async all(...params) {
const res = await this.#query(this.#sql, params);
return res.rows;
}
async run(...params) {
const res = await this.#query(this.#sql, params);
return {
success: true,
...res
};
}
async get(...params) {
const res = await this.#query(this.#sql, params);
return res.rows[0];
}
}
@@ -0,0 +1,6 @@
import pg from "pg";
import type { Connector } from "db0";
export type ConnectorOptions = {
url: string;
} | pg.ClientConfig;
export default function postgresqlConnector(opts: ConnectorOptions): Connector<pg.Client>;
@@ -0,0 +1,55 @@
import pg from "pg";
import { BoundableStatement } from "./_internal/statement.mjs";
export default function postgresqlConnector(opts) {
let _client;
function getClient() {
if (_client) {
return _client;
}
const client = new pg.Client("url" in opts ? opts.url : opts);
_client = client.connect().then(() => {
_client = client;
return _client;
});
return _client;
}
const query = async (sql, params) => {
const client = await getClient();
return client.query(normalizeParams(sql), params);
};
return {
name: "postgresql",
dialect: "postgresql",
getInstance: () => getClient(),
exec: (sql) => query(sql),
prepare: (sql) => new StatementWrapper(sql, query)
};
}
function normalizeParams(sql) {
let i = 0;
return sql.replace(/\?/g, () => `$${++i}`);
}
class StatementWrapper extends BoundableStatement {
#query;
#sql;
constructor(sql, query) {
super();
this.#sql = sql;
this.#query = query;
}
async all(...params) {
const res = await this.#query(this.#sql, params);
return res.rows;
}
async run(...params) {
const res = await this.#query(this.#sql, params);
return {
success: true,
...res
};
}
async get(...params) {
const res = await this.#query(this.#sql, params);
return res.rows[0];
}
}
@@ -0,0 +1,8 @@
import sqlite3 from 'sqlite3';
import type { Connector } from 'db0';
export interface ConnectorOptions {
cwd?: string;
path?: string;
name?: string;
}
export default function nodeSqlite3Connector(opts: ConnectorOptions): Connector<sqlite3.Database>;
@@ -0,0 +1,70 @@
import { resolve, dirname } from "node:path";
import { mkdirSync } from "node:fs";
import sqlite3 from "sqlite3";
import { BoundableStatement } from "./_internal/statement.mjs";
export default function nodeSqlite3Connector(opts) {
let _db;
const getDB = () => {
if (_db) {
return _db;
}
if (opts.name === ":memory:") {
_db = new sqlite3.Database(":memory:");
return _db;
}
const filePath = resolve(
opts.cwd || ".",
opts.path || `.data/${opts.name || "db"}.sqlite3`
);
mkdirSync(dirname(filePath), { recursive: true });
_db = new sqlite3.Database(filePath);
return _db;
};
const query = (sql) => new Promise((resolve2, reject) => {
getDB().exec(sql, (err) => {
if (err) {
return reject(err);
}
resolve2({ success: true });
});
});
return {
name: "sqlite3",
dialect: "sqlite",
getInstance: () => getDB(),
exec: (sql) => query(sql),
prepare: (sql) => new StatementWrapper(sql, getDB())
};
}
class StatementWrapper extends BoundableStatement {
#onError;
// #162
constructor(sql, db) {
super(db.prepare(sql, (err) => {
if (err && this.#onError) {
return this.#onError(err);
}
}));
}
async all(...params) {
const rows = await new Promise((resolve2, reject) => {
this.#onError = reject;
this._statement.all(...params, (err, rows2) => err ? reject(err) : resolve2(rows2));
});
return rows;
}
async run(...params) {
await new Promise((resolve2, reject) => {
this.#onError = reject;
this._statement.run(...params, (err) => err ? reject(err) : resolve2());
});
return { success: true };
}
async get(...params) {
const row = await new Promise((resolve2, reject) => {
this.#onError = reject;
this._statement.get(...params, (err, row2) => err ? reject(err) : resolve2(row2));
});
return row;
}
}
+203
View File
@@ -0,0 +1,203 @@
import { ConnectorOptions as ConnectorOptions$1 } from 'db0/connectors/better-sqlite3';
import { ConnectorOptions as ConnectorOptions$2 } from 'db0/connectors/bun-sqlite';
import { ConnectorOptions as ConnectorOptions$3 } from 'db0/connectors/cloudflare-d1';
import { ConnectorOptions as ConnectorOptions$4 } from 'db0/connectors/libsql/core';
import { ConnectorOptions as ConnectorOptions$5 } from 'db0/connectors/libsql/http';
import { ConnectorOptions as ConnectorOptions$6 } from 'db0/connectors/libsql/node';
import { ConnectorOptions as ConnectorOptions$7 } from 'db0/connectors/libsql/web';
import { ConnectorOptions as ConnectorOptions$8 } from 'db0/connectors/mysql2';
import { ConnectorOptions as ConnectorOptions$9 } from 'db0/connectors/node-sqlite';
import { ConnectorOptions as ConnectorOptions$a } from 'db0/connectors/pglite';
import { ConnectorOptions as ConnectorOptions$b } from 'db0/connectors/planetscale';
import { ConnectorOptions as ConnectorOptions$c } from 'db0/connectors/postgresql';
import { ConnectorOptions as ConnectorOptions$d } from 'db0/connectors/sqlite3';
/**
* Represents primitive types that can be used in SQL operations.
*/
type Primitive = string | number | boolean | undefined | null;
type SQLDialect = "mysql" | "postgresql" | "sqlite" | "libsql";
type Statement = {
/**
* Binds parameters to the statement.
* @param {...Primitive[]} params - Parameters to bind to the SQL statement.
* @returns {PreparedStatement} The instance of the statement with bound parameters.
*/
bind(...params: Primitive[]): PreparedStatement;
/**
* Executes the statement and returns all resulting rows as an array.
* @param {...Primitive[]} params - Parameters to bind to the SQL statement.
* @returns {Promise<unknown[]>} A promise that resolves to an array of rows.
*/
all(...params: Primitive[]): Promise<unknown[]>;
/**
* Executes the statement as an action (e.g. insert, update, delete).
* @param {...Primitive[]} params - Parameters to bind to the SQL statement.
* @returns {Promise<{ success: boolean }>} A promise that resolves to the success state of the action.
*/
run(...params: Primitive[]): Promise<{
success: boolean;
}>;
/**
* Executes the statement and returns a single row.
* @param {...Primitive[]} params - Parameters to bind to the SQL statement.
* @returns {Promise<unknown>} A promise that resolves to the first row in the result set.
*/
get(...params: Primitive[]): Promise<unknown>;
};
type PreparedStatement = {
/**
* Binds parameters to the statement.
* @param {...Primitive[]} params - Parameters to bind to the SQL statement.
* @returns {PreparedStatement} The instance of the statement with bound parameters.
*/
bind(...params: Primitive[]): PreparedStatement;
/**
* Executes the statement and returns all resulting rows as an array.
* @returns {Promise<unknown[]>} A promise that resolves to an array of rows.
*/
all(): Promise<unknown[]>;
/**
* Executes the statement as an action (e.g. insert, update, delete).
* @returns {Promise<{ success: boolean }>} A promise that resolves to the success state of the action.
*/
run(): Promise<{
success: boolean;
}>;
/**
* Executes the statement and returns a single row.
* @returns {Promise<unknown>} A promise that resolves to the first row in the result set.
*/
get(): Promise<unknown>;
};
/**
* Represents the result of a database execution.
*/
type ExecResult = unknown;
/**
* Defines a database connector for executing SQL queries and preparing statements.
*/
type Connector<TInstance = unknown> = {
/**
* The name of the connector.
*/
name: string;
/**
* The SQL dialect used by the connector.
*/
dialect: SQLDialect;
/**
* The client instance used internally.
*/
getInstance: () => TInstance | Promise<TInstance>;
/**
* Executes an SQL query directly and returns the result.
* @param {string} sql - The SQL string to execute.
* @returns {ExecResult | Promise<ExecResult>} The result of the execution.
*/
exec: (sql: string) => ExecResult | Promise<ExecResult>;
/**
* Prepares an SQL statement for execution.
* @param {string} sql - The SQL string to prepare.
* @returns {statement} The prepared SQL statement.
*/
prepare: (sql: string) => Statement;
};
/**
* Represents default SQL results, including any error messages, row changes and rows returned.
*/
type DefaultSQLResult = {
lastInsertRowid?: number;
changes?: number;
error?: string;
rows?: {
id?: string | number;
[key: string]: unknown;
}[];
success?: boolean;
};
interface Database<TConnector extends Connector = Connector> {
readonly dialect: SQLDialect;
/**
* The client instance used internally.
* @returns {Promise<TInstance>} A promise that resolves with the client instance.
*/
getInstance: () => Promise<Awaited<ReturnType<TConnector["getInstance"]>>>;
/**
* Executes a raw SQL string.
* @param {string} sql - The SQL string to execute.
* @returns {Promise<ExecResult>} A promise that resolves with the execution result.
*/
exec: (sql: string) => Promise<ExecResult>;
/**
* Prepares an SQL statement from a raw SQL string.
* @param {string} sql - The SQL string to prepare.
* @returns {statement} The prepared SQL statement.
*/
prepare: (sql: string) => Statement;
/**
* Executes SQL queries using tagged template literals.
* @template T The expected type of query result.
* @param {TemplateStringsArray} strings - The segments of the SQL string.
* @param {...Primitive[]} values - The values to interpolate into the SQL string.
* @returns {Promise<T>} A promise that resolves with the typed result of the query.
*/
sql: <T = DefaultSQLResult>(strings: TemplateStringsArray, ...values: Primitive[]) => Promise<T>;
}
/**
* Creates and returns a database interface using the specified connector.
* This interface allows you to execute raw SQL queries, prepare SQL statements,
* and execute SQL queries with parameters using tagged template literals.
*
* @param {Connector} connector - The database connector used to execute and prepare SQL statements. See {@link Connector}.
* @returns {Database} The database interface that allows SQL operations. See {@link Database}.
*/
declare function createDatabase<TConnector extends Connector = Connector>(connector: TConnector): Database<TConnector>;
type ConnectorName = "better-sqlite3" | "bun-sqlite" | "bun" | "cloudflare-d1" | "libsql-core" | "libsql-http" | "libsql-node" | "libsql" | "libsql-web" | "mysql2" | "node-sqlite" | "sqlite" | "pglite" | "planetscale" | "postgresql" | "sqlite3";
type ConnectorOptions = {
"better-sqlite3": ConnectorOptions$1;
"bun-sqlite": ConnectorOptions$2;
/** alias of bun-sqlite */
"bun": ConnectorOptions$2;
"cloudflare-d1": ConnectorOptions$3;
"libsql-core": ConnectorOptions$4;
"libsql-http": ConnectorOptions$5;
"libsql-node": ConnectorOptions$6;
/** alias of libsql-node */
"libsql": ConnectorOptions$6;
"libsql-web": ConnectorOptions$7;
"mysql2": ConnectorOptions$8;
"node-sqlite": ConnectorOptions$9;
/** alias of node-sqlite */
"sqlite": ConnectorOptions$9;
"pglite": ConnectorOptions$a;
"planetscale": ConnectorOptions$b;
"postgresql": ConnectorOptions$c;
"sqlite3": ConnectorOptions$d;
};
declare const connectors: Readonly<{
readonly "better-sqlite3": "db0/connectors/better-sqlite3";
readonly "bun-sqlite": "db0/connectors/bun-sqlite";
/** alias of bun-sqlite */
readonly bun: "db0/connectors/bun-sqlite";
readonly "cloudflare-d1": "db0/connectors/cloudflare-d1";
readonly "libsql-core": "db0/connectors/libsql/core";
readonly "libsql-http": "db0/connectors/libsql/http";
readonly "libsql-node": "db0/connectors/libsql/node";
/** alias of libsql-node */
readonly libsql: "db0/connectors/libsql/node";
readonly "libsql-web": "db0/connectors/libsql/web";
readonly mysql2: "db0/connectors/mysql2";
readonly "node-sqlite": "db0/connectors/node-sqlite";
/** alias of node-sqlite */
readonly sqlite: "db0/connectors/node-sqlite";
readonly pglite: "db0/connectors/pglite";
readonly planetscale: "db0/connectors/planetscale";
readonly postgresql: "db0/connectors/postgresql";
readonly sqlite3: "db0/connectors/sqlite3";
}>;
export { connectors, createDatabase };
export type { Connector, ConnectorName, ConnectorOptions, Database, ExecResult, PreparedStatement, Primitive, SQLDialect, Statement };
+78
View File
@@ -0,0 +1,78 @@
function sqlTemplate(strings, ...values) {
if (!isTemplateStringsArray(strings) || !Array.isArray(values)) {
throw new Error("[db0] invalid template invocation");
}
const staticIndexes = [];
let result = strings[0] || "";
for (let i = 1; i < strings.length; i++) {
if (result.endsWith("{") && strings[i].startsWith("}")) {
result = result.slice(0, -1) + values[i - 1] + strings[i].slice(1);
staticIndexes.push(i - 1);
continue;
}
result += `?${strings[i] ?? ""}`;
}
const dynamicValues = values.filter((_, i) => !staticIndexes.includes(i));
return [result.trim(), dynamicValues];
}
function isTemplateStringsArray(strings) {
return Array.isArray(strings) && "raw" in strings && Array.isArray(strings.raw);
}
const SQL_SELECT_RE = /^select/i;
const SQL_RETURNING_RE = /[\s]returning[\s]/i;
const DIALECTS_WITH_RET = /* @__PURE__ */ new Set(["postgresql", "sqlite"]);
function createDatabase(connector) {
return {
get dialect() {
return connector.dialect;
},
getInstance() {
return connector.getInstance();
},
exec: (sql) => {
return Promise.resolve(connector.exec(sql));
},
prepare: (sql) => {
return connector.prepare(sql);
},
sql: async (strings, ...values) => {
const [sql, params] = sqlTemplate(strings, ...values);
if (SQL_SELECT_RE.test(sql) || // prettier-ignore
DIALECTS_WITH_RET.has(connector.dialect) && SQL_RETURNING_RE.test(sql)) {
const rows = await connector.prepare(sql).all(...params);
return {
rows,
success: true
};
} else {
const res = await connector.prepare(sql).run(...params);
return res;
}
}
};
}
const connectors = Object.freeze({
"better-sqlite3": "db0/connectors/better-sqlite3",
"bun-sqlite": "db0/connectors/bun-sqlite",
/** alias of bun-sqlite */
"bun": "db0/connectors/bun-sqlite",
"cloudflare-d1": "db0/connectors/cloudflare-d1",
"libsql-core": "db0/connectors/libsql/core",
"libsql-http": "db0/connectors/libsql/http",
"libsql-node": "db0/connectors/libsql/node",
/** alias of libsql-node */
"libsql": "db0/connectors/libsql/node",
"libsql-web": "db0/connectors/libsql/web",
"mysql2": "db0/connectors/mysql2",
"node-sqlite": "db0/connectors/node-sqlite",
/** alias of node-sqlite */
"sqlite": "db0/connectors/node-sqlite",
"pglite": "db0/connectors/pglite",
"planetscale": "db0/connectors/planetscale",
"postgresql": "db0/connectors/postgresql",
"sqlite3": "db0/connectors/sqlite3"
});
export { connectors, createDatabase };
@@ -0,0 +1,36 @@
import { entityKind, Logger, RelationalSchemaConfig, type Query, type TablesRelationalConfig } from "drizzle-orm";
import { SQLiteAsyncDialect, SQLiteSession, SQLitePreparedQuery } from "drizzle-orm/sqlite-core";
import type { PreparedQueryConfig, SelectedFieldsOrdered, SQLiteExecuteMethod, SQLiteTransactionConfig } from "drizzle-orm/sqlite-core";
import type { Database, Statement } from "db0";
export interface DB0SessionOptions {
logger?: Logger;
}
export declare class DB0Session<TFullSchema extends Record<string, unknown>, TSchema extends TablesRelationalConfig> extends SQLiteSession<"async", unknown, TFullSchema, TSchema> {
private db;
private schema;
private options;
dialect: SQLiteAsyncDialect;
private logger;
constructor(db: Database, dialect: SQLiteAsyncDialect, schema: RelationalSchemaConfig<TSchema> | undefined, options?: DB0SessionOptions);
prepareQuery(query: Query, fields: SelectedFieldsOrdered | undefined, executeMethod: SQLiteExecuteMethod, customResultMapper?: (rows: unknown[][]) => unknown): DB0PreparedQuery;
transaction<T>(transaction: (tx: any) => T | Promise<T>, config?: SQLiteTransactionConfig): Promise<T>;
}
export declare class DB0PreparedQuery<T extends PreparedQueryConfig = PreparedQueryConfig> extends SQLitePreparedQuery<{
type: "async";
run: Awaited<ReturnType<Statement["run"]>>;
all: T["all"];
get: T["get"];
values: T["values"];
execute: T["execute"];
}> {
private stmt;
private logger;
static readonly [entityKind]: string;
constructor(stmt: Statement, query: Query, logger: Logger, fields: SelectedFieldsOrdered | undefined, executeMethod: SQLiteExecuteMethod, customResultMapper?: (rows: unknown[][]) => unknown);
run(): Promise<{
success: boolean;
}>;
all(): Promise<unknown[]>;
get(): Promise<unknown>;
values(): Promise<never>;
}
@@ -0,0 +1,55 @@
import {
entityKind,
NoopLogger
} from "drizzle-orm";
import {
SQLiteSession,
SQLitePreparedQuery
} from "drizzle-orm/sqlite-core";
export class DB0Session extends SQLiteSession {
constructor(db, dialect, schema, options = {}) {
super(dialect);
this.db = db;
this.schema = schema;
this.options = options;
this.logger = options.logger ?? new NoopLogger();
}
dialect;
logger;
prepareQuery(query, fields, executeMethod, customResultMapper) {
const stmt = this.db.prepare(query.sql);
return new DB0PreparedQuery(
stmt,
query,
this.logger,
fields,
executeMethod,
customResultMapper
);
}
// TODO: Implement batch
// TODO: Implement transaction
transaction(transaction, config) {
throw new Error("transaction is not implemented!");
}
}
export class DB0PreparedQuery extends SQLitePreparedQuery {
constructor(stmt, query, logger, fields, executeMethod, customResultMapper) {
super("async", executeMethod, query);
this.stmt = stmt;
this.logger = logger;
}
static [entityKind] = "DB0PreparedQuery";
run() {
return this.stmt.run(...this.query.params);
}
all() {
return this.stmt.all(...this.query.params);
}
get() {
return this.stmt.get(...this.query.params);
}
values() {
return Promise.reject(new Error("values is not implemented!"));
}
}
@@ -0,0 +1,3 @@
import { AnyColumn, SelectedFieldsOrdered } from "drizzle-orm";
/** @internal */
export declare function mapResultRow<TResult>(columns: SelectedFieldsOrdered<AnyColumn>, row: unknown[], joinsNotNullableMap: Record<string, boolean> | undefined): TResult;
@@ -0,0 +1,51 @@
import {
getTableName,
is,
Column,
SQL
} from "drizzle-orm";
export function mapResultRow(columns, row, joinsNotNullableMap) {
const nullifyMap = {};
const result = columns.reduce(
(result2, { path, field }, columnIndex) => {
let decoder;
if (is(field, Column)) {
decoder = field;
} else if (is(field, SQL)) {
decoder = "decoder" in field && field.decoder;
} else {
decoder = "decoder" in field.sql && field.sql.decoder;
}
let node = result2;
for (const [pathChunkIndex, pathChunk] of path.entries()) {
if (pathChunkIndex < path.length - 1) {
if (!(pathChunk in node)) {
node[pathChunk] = {};
}
node = node[pathChunk];
} else {
const rawValue = row[columnIndex];
const value = node[pathChunk] = rawValue === null ? null : decoder.mapFromDriverValue(rawValue);
if (joinsNotNullableMap && is(field, Column) && path.length === 2) {
const objectName = path[0];
if (!(objectName in nullifyMap)) {
nullifyMap[objectName] = value === null ? getTableName(field.table) : false;
} else if (typeof nullifyMap[objectName] === "string" && nullifyMap[objectName] !== getTableName(field.table)) {
nullifyMap[objectName] = false;
}
}
}
}
return result2;
},
{}
);
if (joinsNotNullableMap && Object.keys(nullifyMap).length > 0) {
for (const [objectName, tableName] of Object.entries(nullifyMap)) {
if (typeof tableName === "string" && !joinsNotNullableMap[tableName]) {
result[objectName] = null;
}
}
}
return result;
}
@@ -0,0 +1,4 @@
import { BaseSQLiteDatabase } from "drizzle-orm/sqlite-core";
import type { Database } from "db0";
export type DrizzleDatabase<TSchema extends Record<string, unknown> = Record<string, never>> = BaseSQLiteDatabase<"async", any, TSchema>;
export declare function drizzle<TSchema extends Record<string, unknown> = Record<string, never>>(db: Database): DrizzleDatabase<TSchema>;
@@ -0,0 +1,16 @@
import {
BaseSQLiteDatabase,
SQLiteAsyncDialect
} from "drizzle-orm/sqlite-core";
import { DB0Session } from "./_session.mjs";
export function drizzle(db) {
const schema = void 0;
const dialect = new SQLiteAsyncDialect();
const session = new DB0Session(db, dialect, schema);
return new BaseSQLiteDatabase(
"async",
dialect,
session,
schema
);
}
+97
View File
@@ -0,0 +1,97 @@
{
"name": "db0",
"version": "0.3.2",
"description": "Lightweight SQL Connector",
"repository": "unjs/db0",
"license": "MIT",
"sideEffects": false,
"exports": {
".": {
"types": "./dist/index.d.mts",
"default": "./dist/index.mjs"
},
"./connectors/*": {
"types": "./dist/connectors/*.d.ts",
"default": "./dist/connectors/*.mjs"
},
"./integrations/*": {
"types": "./dist/integrations/*/index.d.ts",
"default": "./dist/integrations/*/index.mjs"
},
"./connectors/libsql/*": {
"types": "./dist/connectors/libsql/*.d.ts",
"default": "./dist/connectors/libsql/*.mjs"
}
},
"types": "./dist/index.d.mts",
"files": [
"dist"
],
"devDependencies": {
"@cloudflare/workers-types": "^4.20250418.0",
"@electric-sql/pglite": "^0.2.17",
"@libsql/client": "^0.15.4",
"@planetscale/database": "^1.19.0",
"@types/better-sqlite3": "^7.6.13",
"@types/bun": "^1.2.10",
"@types/pg": "^8.11.13",
"@vitest/coverage-v8": "^3.1.1",
"automd": "^0.4.0",
"better-sqlite3": "^11.9.1",
"changelogen": "^0.6.1",
"db0": "^0.3.1",
"dotenv": "^16.5.0",
"drizzle-orm": "^0.42.0",
"eslint": "^9.24.0",
"eslint-config-unjs": "^0.4.2",
"jiti": "^2.4.2",
"mlly": "^1.7.4",
"mysql2": "^3.14.0",
"pg": "^8.14.1",
"prettier": "^3.5.3",
"scule": "^1.3.0",
"typescript": "^5.8.3",
"unbuild": "^3.5.0",
"vitest": "^3.1.1",
"wrangler": "^4.12.0"
},
"peerDependencies": {
"@electric-sql/pglite": "*",
"@libsql/client": "*",
"better-sqlite3": "*",
"drizzle-orm": "*",
"mysql2": "*",
"sqlite3": "*"
},
"peerDependenciesMeta": {
"@libsql/client": {
"optional": true
},
"better-sqlite3": {
"optional": true
},
"drizzle-orm": {
"optional": true
},
"mysql2": {
"optional": true
},
"@electric-sql/pglite": {
"optional": true
},
"sqlite3": {
"optional": true
}
},
"scripts": {
"build": "pnpm gen-connectors && unbuild",
"gen-connectors": "jiti scripts/gen-connectors.ts",
"db0": "pnpm jiti src/cli",
"dev": "vitest",
"lint": "eslint . && prettier -c src test",
"lint:fix": "eslint . --fix && prettier -w src test",
"release": "pnpm test && changelogen --release --push && pnpm publish",
"test": "pnpm lint && vitest run --coverage && pnpm test:bun",
"test:bun": "bun test ./test/connectors/bun-test.ts"
}
}