UNPKG

@storecraft/database-cloudflare-d1

Version:

`Storecraft` database driver for `cloudflare` D1 (cloud sqlite)

240 lines (202 loc) 4.99 kB
/** * @import { Driver, Dialect, DatabaseConnection, QueryResult } from 'kysely'; * @import { D1ConfigHTTP as Config } from './types.public.js'; */ import { CompiledQuery, Kysely, SqliteAdapter, SqliteQueryCompiler, } from 'kysely'; import { Client } from './d1-http-api/api.js'; import { prepare_and_bind } from '@storecraft/database-sql-base/utils.js'; import { D1Introspector } from './kysely.d1.http.introspector.js'; /** * @description Official Storecraft <-> Cloudflare D1 HTTP adapter * * @implements {Dialect} */ export class D1_HTTP_Dialect { /** @type {Config} */ #config; /** @param {Config} config */ constructor(config={}) { this.#config = config; } get config() { return this.#config; } createAdapter() { return new SqliteAdapter(); } createDriver() { return new D1Driver(this.#config); } createQueryCompiler() { return new SqliteQueryCompiler(); } /** * @param {Kysely<any>} db */ createIntrospector(db){ return new D1Introspector(db); } } /** * @implements {Driver} */ class D1Driver { /** @type {Config} */ #config; /** @type {Client} */ #client; /** @param {Config} config */ constructor(config) { this.#config = config; this.#client = new Client( config.account_id, config.database_id, config.api_token ); } async init() {} async acquireConnection() { return new D1Connection(this.#config, this.#client); } /** * * @param {D1Connection} conn */ async beginTransaction(conn) { return await conn.beginTransaction(); } /** * * @param {D1Connection} conn */ async commitTransaction(conn) { return await conn.commitTransaction(); } /** * * @param {D1Connection} conn */ async rollbackTransaction(conn){ return await conn.rollbackTransaction(); } /** * * @param {D1Connection} _conn */ async releaseConnection(_conn) {} async destroy() {} } /** * @implements {DatabaseConnection} */ class D1Connection { /** @type {Config} */ #config; /** @type {Client} */ #client; isBatch = false; /** @type {CompiledQuery[]} */ batch = [] /** * @param {Config} config * @param {Client} client */ constructor(config, client) { this.#config = config; this.#client = client; } /** * @template R result type * * @param {CompiledQuery[]} compiledQueries * * @returns {Promise<QueryResult<R>>} */ async _internal_execute(compiledQueries) { // Transactions are not supported yet. /** @type {string} */ let sql; /** @type {string[]} */ let params; if(compiledQueries?.length > 1) { sql = compiledQueries.map( cq => prepare_and_bind(cq.sql, cq.parameters) ).join(';'); params = undefined; console.log('sql', sql) } else { sql = compiledQueries?.at(0)?.sql; params = ( /** @type {string[]} */(compiledQueries?.at(0)?.parameters)); } const results = await this.#client.query( { sql, params } ); // console.log('q', JSON.stringify({sql, params}, null, 2)) // console.log('result', JSON.stringify(results, null, 2)) if (!results?.success) { const is_auth_declined_error = params?.at(0)==='_cf_KV'; if(!is_auth_declined_error) { console.log( 'compiledQueries', JSON.stringify( compiledQueries.map(c => ({sql: c.sql, params: c.parameters})), null, 2 ) ) throw new Error(results?.errors?.join(', ')); } } const last_result = results?.result?.at(-1); const meta = last_result?.meta; const numAffectedRows = meta?.changes > 0 ? BigInt(meta?.changes) : undefined; return { insertId: meta?.last_row_id === undefined || meta?.last_row_id === null ? undefined : BigInt(meta?.last_row_id), rows: (last_result?.results) || [], numAffectedRows, }; } /** * @template R result type * * @param {CompiledQuery} compiledQuery * * @returns {Promise<QueryResult<R>>} */ async executeQuery(compiledQuery) { // console.log('this.isBatch', this.isBatch) if(this.isBatch) { this.batch.push(compiledQuery); return Promise.resolve( { rows: [] } ) } else { return this._internal_execute([compiledQuery]); } } async beginTransaction() { console.log('beginTransaction') // console.trace() this.isBatch = true; this.batch = []; } async commitTransaction() { // console.log('commitTransaction') // console.trace() this.isBatch = false; await this._internal_execute(this.batch); } async rollbackTransaction() { return; } /** * @type {DatabaseConnection["streamQuery"]} */ async *streamQuery(compiledQuery, chunkSize) { throw new Error('D1 Driver does not support streaming'); } }