mirror of
https://github.com/shishantbiswas/bknd.git
synced 2026-03-17 04:46:05 +00:00
inlined libsql dialect, rewrote d1 to use generic sqlite
This commit is contained in:
@@ -1,4 +1,4 @@
|
||||
import type { KyselyPlugin } from "kysely";
|
||||
import type { KyselyPlugin, QueryResult } from "kysely";
|
||||
import {
|
||||
type IGenericSqlite,
|
||||
type OnCreateConnection,
|
||||
@@ -8,11 +8,16 @@ import {
|
||||
GenericSqliteDialect,
|
||||
} from "kysely-generic-sqlite";
|
||||
import { SqliteConnection } from "./SqliteConnection";
|
||||
import type { Features } from "../Connection";
|
||||
import type { ConnQuery, ConnQueryResults, Features } from "../Connection";
|
||||
|
||||
export type { IGenericSqlite };
|
||||
export type TStatement = { sql: string; parameters?: any[] | readonly any[] };
|
||||
export interface IGenericCustomSqlite<DB = unknown> extends IGenericSqlite<DB> {
|
||||
batch?: (stmts: TStatement[]) => Promisable<QueryResult<any>[]>;
|
||||
}
|
||||
|
||||
export type GenericSqliteConnectionConfig = {
|
||||
name: string;
|
||||
name?: string;
|
||||
additionalPlugins?: KyselyPlugin[];
|
||||
excludeTables?: string[];
|
||||
onCreateConnection?: OnCreateConnection;
|
||||
@@ -21,10 +26,11 @@ export type GenericSqliteConnectionConfig = {
|
||||
|
||||
export class GenericSqliteConnection<DB = unknown> extends SqliteConnection<DB> {
|
||||
override name = "generic-sqlite";
|
||||
#executor: IGenericCustomSqlite<DB> | undefined;
|
||||
|
||||
constructor(
|
||||
db: DB,
|
||||
executor: () => Promisable<IGenericSqlite>,
|
||||
public db: DB,
|
||||
private executor: () => Promisable<IGenericCustomSqlite<DB>>,
|
||||
config?: GenericSqliteConnectionConfig,
|
||||
) {
|
||||
super({
|
||||
@@ -39,18 +45,43 @@ export class GenericSqliteConnection<DB = unknown> extends SqliteConnection<DB>
|
||||
}
|
||||
if (config?.supports) {
|
||||
for (const [key, value] of Object.entries(config.supports)) {
|
||||
if (value) {
|
||||
if (value !== undefined) {
|
||||
this.supported[key] = value;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
private async getExecutor() {
|
||||
if (!this.#executor) {
|
||||
this.#executor = await this.executor();
|
||||
}
|
||||
return this.#executor;
|
||||
}
|
||||
|
||||
override async executeQueries<O extends ConnQuery[]>(...qbs: O): Promise<ConnQueryResults<O>> {
|
||||
const executor = await this.getExecutor();
|
||||
if (!executor.batch) {
|
||||
console.warn("Batching is not supported by this database");
|
||||
return super.executeQueries(...qbs);
|
||||
}
|
||||
|
||||
const compiled = this.getCompiled(...qbs);
|
||||
const stms: TStatement[] = compiled.map((q) => {
|
||||
return {
|
||||
sql: q.sql,
|
||||
parameters: q.parameters as any[],
|
||||
};
|
||||
});
|
||||
|
||||
const results = await executor.batch(stms);
|
||||
return this.withTransformedRows(results) as any;
|
||||
}
|
||||
}
|
||||
|
||||
export function genericSqlite<DB>(
|
||||
name: string,
|
||||
db: DB,
|
||||
executor: (utils: typeof genericSqliteUtils) => Promisable<IGenericSqlite<DB>>,
|
||||
executor: (utils: typeof genericSqliteUtils) => Promisable<IGenericCustomSqlite<DB>>,
|
||||
config?: GenericSqliteConnectionConfig,
|
||||
) {
|
||||
return new GenericSqliteConnection(db, () => executor(genericSqliteUtils), {
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { connectionTestSuite } from "../connection-test-suite";
|
||||
import { connectionTestSuite } from "../../connection-test-suite";
|
||||
import { LibsqlConnection } from "./LibsqlConnection";
|
||||
import { bunTestRunner } from "adapter/bun/test";
|
||||
import { describe } from "bun:test";
|
||||
@@ -1,23 +1,14 @@
|
||||
import type { Client, Config, InStatement } from "@libsql/client";
|
||||
import { createClient } from "libsql-stateless-easy";
|
||||
import { LibsqlDialect } from "@libsql/kysely-libsql";
|
||||
import { LibsqlDialect } from "./LibsqlDialect";
|
||||
import { FilterNumericKeysPlugin } from "data/plugins/FilterNumericKeysPlugin";
|
||||
import { type ConnQuery, type ConnQueryResults, SqliteConnection } from "bknd/data";
|
||||
|
||||
export const LIBSQL_PROTOCOLS = ["wss", "https", "libsql"] as const;
|
||||
export type LibSqlCredentials = Config & {
|
||||
protocol?: (typeof LIBSQL_PROTOCOLS)[number];
|
||||
};
|
||||
export type LibSqlCredentials = Config;
|
||||
|
||||
function getClient(clientOrCredentials: Client | LibSqlCredentials): Client {
|
||||
if (clientOrCredentials && "url" in clientOrCredentials) {
|
||||
let { url, authToken, protocol } = clientOrCredentials;
|
||||
if (protocol && LIBSQL_PROTOCOLS.includes(protocol)) {
|
||||
console.info("changing protocol to", protocol);
|
||||
const [, rest] = url.split("://");
|
||||
url = `${protocol}://${rest}`;
|
||||
}
|
||||
|
||||
const { url, authToken } = clientOrCredentials;
|
||||
return createClient({ url, authToken });
|
||||
}
|
||||
|
||||
145
app/src/data/connection/sqlite/libsql/LibsqlDialect.ts
Normal file
145
app/src/data/connection/sqlite/libsql/LibsqlDialect.ts
Normal file
@@ -0,0 +1,145 @@
|
||||
import type { Client, Transaction, InValue } from "@libsql/client";
|
||||
import {
|
||||
SqliteAdapter,
|
||||
SqliteIntrospector,
|
||||
SqliteQueryCompiler,
|
||||
type Kysely,
|
||||
type Dialect,
|
||||
type DialectAdapter,
|
||||
type Driver,
|
||||
type DatabaseIntrospector,
|
||||
type QueryCompiler,
|
||||
type TransactionSettings,
|
||||
type DatabaseConnection,
|
||||
type QueryResult,
|
||||
type CompiledQuery,
|
||||
} from "kysely";
|
||||
|
||||
export type LibsqlDialectConfig = {
|
||||
client: Client;
|
||||
};
|
||||
|
||||
export class LibsqlDialect implements Dialect {
|
||||
#config: LibsqlDialectConfig;
|
||||
|
||||
constructor(config: LibsqlDialectConfig) {
|
||||
this.#config = config;
|
||||
}
|
||||
|
||||
createAdapter(): DialectAdapter {
|
||||
return new SqliteAdapter();
|
||||
}
|
||||
|
||||
createDriver(): Driver {
|
||||
let client: Client;
|
||||
let closeClient: boolean;
|
||||
if ("client" in this.#config) {
|
||||
client = this.#config.client;
|
||||
closeClient = false;
|
||||
} else {
|
||||
throw new Error("Please specify either `client` or `url` in the LibsqlDialect config");
|
||||
}
|
||||
|
||||
return new LibsqlDriver(client, closeClient);
|
||||
}
|
||||
|
||||
createIntrospector(db: Kysely<any>): DatabaseIntrospector {
|
||||
return new SqliteIntrospector(db);
|
||||
}
|
||||
|
||||
createQueryCompiler(): QueryCompiler {
|
||||
return new SqliteQueryCompiler();
|
||||
}
|
||||
}
|
||||
|
||||
export class LibsqlDriver implements Driver {
|
||||
client: Client;
|
||||
#closeClient: boolean;
|
||||
|
||||
constructor(client: Client, closeClient: boolean) {
|
||||
this.client = client;
|
||||
this.#closeClient = closeClient;
|
||||
}
|
||||
|
||||
async init(): Promise<void> {}
|
||||
|
||||
async acquireConnection(): Promise<LibsqlConnection> {
|
||||
return new LibsqlConnection(this.client);
|
||||
}
|
||||
|
||||
async beginTransaction(
|
||||
connection: LibsqlConnection,
|
||||
_settings: TransactionSettings,
|
||||
): Promise<void> {
|
||||
await connection.beginTransaction();
|
||||
}
|
||||
|
||||
async commitTransaction(connection: LibsqlConnection): Promise<void> {
|
||||
await connection.commitTransaction();
|
||||
}
|
||||
|
||||
async rollbackTransaction(connection: LibsqlConnection): Promise<void> {
|
||||
await connection.rollbackTransaction();
|
||||
}
|
||||
|
||||
async releaseConnection(_conn: LibsqlConnection): Promise<void> {}
|
||||
|
||||
async destroy(): Promise<void> {
|
||||
if (this.#closeClient) {
|
||||
this.client.close();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export class LibsqlConnection implements DatabaseConnection {
|
||||
client: Client;
|
||||
#transaction?: Transaction;
|
||||
|
||||
constructor(client: Client) {
|
||||
this.client = client;
|
||||
}
|
||||
|
||||
async executeQuery<R>(compiledQuery: CompiledQuery): Promise<QueryResult<R>> {
|
||||
const target = this.#transaction ?? this.client;
|
||||
const result = await target.execute({
|
||||
sql: compiledQuery.sql,
|
||||
args: compiledQuery.parameters as Array<InValue>,
|
||||
});
|
||||
return {
|
||||
insertId: result.lastInsertRowid,
|
||||
numAffectedRows: BigInt(result.rowsAffected),
|
||||
rows: result.rows as Array<R>,
|
||||
};
|
||||
}
|
||||
|
||||
async beginTransaction() {
|
||||
if (this.#transaction) {
|
||||
throw new Error("Transaction already in progress");
|
||||
}
|
||||
this.#transaction = await this.client.transaction();
|
||||
}
|
||||
|
||||
async commitTransaction() {
|
||||
if (!this.#transaction) {
|
||||
throw new Error("No transaction to commit");
|
||||
}
|
||||
await this.#transaction.commit();
|
||||
this.#transaction = undefined;
|
||||
}
|
||||
|
||||
async rollbackTransaction() {
|
||||
if (!this.#transaction) {
|
||||
throw new Error("No transaction to rollback");
|
||||
}
|
||||
await this.#transaction.rollback();
|
||||
this.#transaction = undefined;
|
||||
}
|
||||
|
||||
// biome-ignore lint/correctness/useYield: <explanation>
|
||||
async *streamQuery<R>(
|
||||
_compiledQuery: CompiledQuery,
|
||||
_chunkSize: number,
|
||||
): AsyncIterableIterator<QueryResult<R>> {
|
||||
throw new Error("Libsql Driver does not support streaming yet");
|
||||
}
|
||||
}
|
||||
@@ -57,7 +57,7 @@ export class Repository<TBD extends object = DefaultDB, TB extends keyof TBD = a
|
||||
}
|
||||
}
|
||||
|
||||
getValidOptions(options?: RepoQuery): RepoQuery {
|
||||
getValidOptions(options?: Partial<RepoQuery>): RepoQuery {
|
||||
const entity = this.entity;
|
||||
// @todo: if not cloned deep, it will keep references and error if multiple requests come in
|
||||
const validated = {
|
||||
|
||||
@@ -30,7 +30,7 @@ export * as DataPermissions from "./permissions";
|
||||
|
||||
export { MediaField, type MediaFieldConfig, type MediaItem } from "media/MediaField";
|
||||
|
||||
export { libsql } from "./connection/sqlite/LibsqlConnection";
|
||||
export { libsql } from "./connection/sqlite/libsql/LibsqlConnection";
|
||||
export {
|
||||
genericSqlite,
|
||||
genericSqliteUtils,
|
||||
|
||||
@@ -150,4 +150,6 @@ export type RepoQueryIn = {
|
||||
join?: string[];
|
||||
where?: WhereQuery;
|
||||
};
|
||||
export type RepoQuery = s.StaticCoerced<typeof repoQuery>;
|
||||
export type RepoQuery = s.StaticCoerced<typeof repoQuery> & {
|
||||
sort: SortSchema;
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user