diff --git a/.gitignore b/.gitignore index b5f3ab6..4e3e49e 100644 --- a/.gitignore +++ b/.gitignore @@ -1,6 +1,8 @@ node_modules/ test-db/ *.db +*.db-wal +*.db-shm lib/ tsconfig.tsbuildinfo benchmarks/db diff --git a/packages/stdext-sql-adapter/README.md b/packages/stdext-sql-adapter/README.md new file mode 100644 index 0000000..c40c7a4 --- /dev/null +++ b/packages/stdext-sql-adapter/README.md @@ -0,0 +1,5 @@ +# stdext-sql-adapter + +POC implementation of @stdext/sql on top of the @sqlite-js/driver API. + +Loosly based on the implementation in https://github.com/denodrivers/sqlite3/tree/b1d0b0731323179648663761e134e1dc25b05324 diff --git a/packages/stdext-sql-adapter/package.json b/packages/stdext-sql-adapter/package.json new file mode 100644 index 0000000..7928030 --- /dev/null +++ b/packages/stdext-sql-adapter/package.json @@ -0,0 +1,27 @@ +{ + "name": "stdext-sql-adapter", + "version": "0.0.1", + "description": "", + "type": "module", + "scripts": { + "build": "tsc -b", + "clean": "tsc -b --clean && rm -rf lib", + "test": "vitest" + }, + "exports": { + ".": "./lib/mod.js" + }, + "keywords": [], + "author": "", + "license": "MIT", + "dependencies": { + "@sqlite-js/better-sqlite3-driver": "workspace:^", + "@sqlite-js/driver": "workspace:^", + "@stdext/sql": "workspace:^", + "@types/node": "^22.5.1" + }, + "devDependencies": { + "typescript": "^5.5.4", + "vitest": "^2.0.5" + } +} diff --git a/packages/stdext-sql-adapter/src/adapter.ts b/packages/stdext-sql-adapter/src/adapter.ts new file mode 100644 index 0000000..e69de29 diff --git a/packages/stdext-sql-adapter/src/better-sqlite-client.ts b/packages/stdext-sql-adapter/src/better-sqlite-client.ts new file mode 100644 index 0000000..918735b --- /dev/null +++ b/packages/stdext-sql-adapter/src/better-sqlite-client.ts @@ -0,0 +1,23 @@ +import { BetterSqliteDriver } from '@sqlite-js/better-sqlite3-driver'; +import { SqliteClientPool, SqliteClientOptions, SqliteClient } from './core.js'; +import { fileURLToPath } from 'node:url'; + +export class BetterSqliteClient extends SqliteClient { + constructor(connectionUrl: string | URL, options: SqliteClientOptions = {}) { + if (typeof connectionUrl != 'string') { + connectionUrl = fileURLToPath(connectionUrl); + } + const pool = BetterSqliteDriver.openInProcess(connectionUrl); + super(connectionUrl, pool, options); + } +} + +export class BetterSqliteClientPool extends SqliteClientPool { + constructor(connectionUrl: string | URL, options: SqliteClientOptions = {}) { + if (typeof connectionUrl != 'string') { + connectionUrl = fileURLToPath(connectionUrl); + } + const pool = BetterSqliteDriver.open(connectionUrl); + super(connectionUrl, pool, options); + } +} diff --git a/packages/stdext-sql-adapter/src/connection.ts b/packages/stdext-sql-adapter/src/connection.ts new file mode 100644 index 0000000..84eb114 --- /dev/null +++ b/packages/stdext-sql-adapter/src/connection.ts @@ -0,0 +1,167 @@ +// deno-lint-ignore-file require-await +import { + ReservedConnection, + SqliteDriverConnection, + SqliteDriverConnectionPool +} from '@sqlite-js/driver'; +import type { + ArrayRow, + Row, + SqlConnectable, + SqlConnection, + SqlConnectionOptions +} from '@stdext/sql'; +import type { SqliteParameterType, SqliteQueryOptions } from './core.js'; +import { type DatabaseOpenOptions } from './database.js'; + +/** Various options that can be configured when opening Database connection. */ +export interface SqliteConnectionOptions + extends SqlConnectionOptions, + DatabaseOpenOptions {} + +export class SqliteConnection implements SqlConnection { + public driver: SqliteDriverConnection | undefined; + + public readonly connectionUrl: string; + + get connected(): boolean { + return this.driver != null; + } + + public readonly options: SqliteConnectionOptions; + + constructor( + connectionUrl: string, + driver: SqliteDriverConnection | undefined, + options?: SqliteConnectionOptions + ) { + this.connectionUrl = connectionUrl; + this.driver = driver; + this.options = options ?? {}; + } + + async connect(): Promise { + // No-op + } + + async close(): Promise { + await this.driver?.close(); + } + + async execute( + sql: string, + params?: SqliteParameterType[], + _options?: SqliteQueryOptions + ): Promise { + using statement = this.driver!.prepare(sql); + if (params != null) { + statement.bind(params); + } + const results = await statement.run(); + return results.changes; + } + + async *queryMany = Row>( + sql: string, + params?: SqliteParameterType[], + options?: SqliteQueryOptions + ): AsyncGenerator { + using statement = this.driver!.prepare(sql, { + bigint: this.options.int64 ?? false + }); + if (params != null) { + statement.bind(params); + } + const chunkSize = 100; + + while (true) { + const { rows, done } = await statement.step(chunkSize); + if (rows != null) { + const castRows = rows as T[]; + for (let row of castRows) { + yield row; + } + } + if (done) { + break; + } + } + } + + async *queryManyArray = ArrayRow>( + sql: string, + params?: SqliteParameterType[], + options?: SqliteQueryOptions + ): AsyncGenerator { + using statement = this.driver!.prepare(sql, { + bigint: this.options.int64 ?? false, + rawResults: true + }); + if (params != null) { + statement.bind(params); + } + const chunkSize = 100; + + while (true) { + const { rows, done } = await statement.step(chunkSize); + if (rows != null) { + const castRows = rows as T[]; + for (let row of castRows) { + yield row; + } + } + if (done) { + break; + } + } + } + + async [Symbol.asyncDispose](): Promise { + await this.close(); + } +} + +export class SqliteReservedConnection extends SqliteConnection { + #connect: () => Promise; + #reserved: ReservedConnection | undefined; + + constructor( + connectionUrl: string, + connect: () => Promise, + options?: SqliteConnectionOptions + ) { + super(connectionUrl, undefined, options); + this.#connect = connect; + } + + async connect(): Promise { + this.#reserved = await this.#connect(); + this.driver = this.#reserved.connection; + } + + async close(): Promise { + this.driver = undefined; + await this.#reserved?.release(); + } +} + +export class SqliteConnectable + implements SqlConnectable +{ + readonly connection: SqliteConnection; + readonly options: SqliteConnectionOptions; + get connected(): boolean { + return this.connection.connected; + } + + constructor( + connection: SqliteConnectable['connection'], + options: SqliteConnectable['options'] = {} + ) { + this.connection = connection; + this.options = options; + } + [Symbol.asyncDispose](): Promise { + return this.connection.close(); + } +} diff --git a/packages/stdext-sql-adapter/src/core.ts b/packages/stdext-sql-adapter/src/core.ts new file mode 100644 index 0000000..a96f402 --- /dev/null +++ b/packages/stdext-sql-adapter/src/core.ts @@ -0,0 +1,538 @@ +import type { + ArrayRow, + Row, + SqlClient, + SqlClientPool, + SqlConnectionOptions, + SqlPoolClient, + SqlPreparable, + SqlPreparedStatement, + SqlQueriable, + SqlQueryOptions, + SqlTransaction, + SqlTransactionable, + SqlTransactionOptions +} from '@stdext/sql'; + +import { + ReservedConnection, + SqliteDriverConnectionPool, + SqliteDriverStatement, + SqliteValue +} from '@sqlite-js/driver'; +import { + SqliteConnectable, + SqliteConnection, + SqliteReservedConnection, + type SqliteConnectionOptions +} from './connection.js'; +import type { DatabaseOpenOptions } from './database.js'; +import { SqliteTransactionError } from './errors.js'; +import { + SqliteCloseEvent, + SqliteConnectEvent, + SqliteEventTarget +} from './events.js'; +import { mergeQueryOptions } from './util.js'; + +export type SqliteParameterType = SqliteValue; +export type BindValue = SqliteValue; + +export interface SqliteQueryOptions extends SqlQueryOptions {} + +export interface SqliteTransactionOptions extends SqlTransactionOptions { + beginTransactionOptions: { + behavior?: 'DEFERRED' | 'IMMEDIATE' | 'EXCLUSIVE'; + }; + commitTransactionOptions: undefined; + rollbackTransactionOptions: { + savepoint?: string; + }; +} + +/** Various options that can be configured when opening Database connection. */ +export interface SqliteClientOptions + extends SqlConnectionOptions, + DatabaseOpenOptions {} + +export class SqlitePreparedStatement + extends SqliteConnectable + implements SqlPreparedStatement +{ + readonly sql: string; + declare readonly options: SqliteConnectionOptions & SqliteQueryOptions; + + #statementObject?: SqliteDriverStatement; + #statementArray?: SqliteDriverStatement; + + #deallocated = false; + + constructor( + connection: SqlitePreparedStatement['connection'], + sql: string, + options: SqlitePreparedStatement['options'] = {} + ) { + super(connection, options); + this.sql = sql; + } + + private _statementObject(): SqliteDriverStatement { + this.#statementObject ??= this.connection.driver!.prepare(this.sql); + return this.#statementObject; + } + + private _statementArray(): SqliteDriverStatement { + this.#statementArray ??= this.connection.driver!.prepare(this.sql, { + rawResults: true + }); + return this.#statementArray; + } + + get deallocated(): boolean { + return this.#deallocated; + } + + async deallocate(): Promise { + this.#statementArray?.finalize(); + this.#statementObject?.finalize(); + this.#deallocated = true; + } + + async execute( + params?: SqliteParameterType[], + _options?: SqliteQueryOptions | undefined + ): Promise { + const statement = this._statementObject(); + statement.bind(params ?? []); + const result = await statement.run(); + return result.changes; + } + + async query = Row>( + params?: SqliteParameterType[], + options?: SqliteQueryOptions | undefined + ): Promise { + const statement = this._statementObject(); + try { + statement.bind(params ?? []); + const { rows } = await statement.step(); + return rows as T[]; + } finally { + statement.reset(); + } + } + + async queryOne = Row>( + params?: SqliteParameterType[], + options?: SqliteQueryOptions | undefined + ): Promise { + const all = await this.query(params, options); + return all[0]; + } + + async *queryMany = Row>( + params?: SqliteParameterType[], + options?: SqliteQueryOptions | undefined + ): AsyncGenerator { + const chunkSize = 100; + const statement = this._statementObject(); + + try { + statement.bind(params ?? []); + + while (true) { + const { rows, done } = await statement.step(chunkSize); + if (rows != null) { + for (let row of rows as T[]) { + yield row; + } + } + if (done) { + break; + } + } + } finally { + statement.reset(); + } + } + + async queryArray = ArrayRow>( + params?: SqliteParameterType[], + options?: SqliteQueryOptions | undefined + ): Promise { + const statement = this._statementArray(); + try { + statement.bind(params ?? []); + const { rows } = await statement.step(); + return rows as T[]; + } finally { + statement.reset(); + } + } + + async queryOneArray = ArrayRow>( + params?: SqliteParameterType[], + options?: SqliteQueryOptions | undefined + ): Promise { + const all = await this.queryArray(params, options); + return all[0]; + } + async *queryManyArray = ArrayRow>( + params?: SqliteParameterType[], + options?: SqliteQueryOptions | undefined + ): AsyncGenerator { + const chunkSize = 100; + const statement = this._statementArray(); + + try { + statement.bind(params ?? []); + + while (true) { + const { rows, done } = await statement.step(chunkSize); + if (rows != null) { + for (let row of rows as T[]) { + yield row; + } + } + if (done) { + break; + } + } + } finally { + statement.reset(); + } + } + + async [Symbol.asyncDispose](): Promise { + await this.deallocate(); + await super[Symbol.asyncDispose](); + } +} + +/** + * Represents a base queriable class for SQLite3. + */ +export class SqliteQueriable extends SqliteConnectable implements SqlQueriable { + declare readonly options: SqliteConnectionOptions & SqliteQueryOptions; + + constructor( + connection: SqliteQueriable['connection'], + options: SqliteQueriable['options'] = {} + ) { + super(connection, options); + } + + prepare(sql: string, options?: SqliteQueryOptions): SqlitePreparedStatement { + return new SqlitePreparedStatement( + this.connection, + sql, + mergeQueryOptions(this.options, options) + ); + } + + execute( + sql: string, + params?: SqliteParameterType[], + options?: SqliteQueryOptions | undefined + ): Promise { + return this.prepare(sql, options).execute(params); + } + + query = Row>( + sql: string, + params?: SqliteParameterType[], + options?: SqliteQueryOptions | undefined + ): Promise { + return this.prepare(sql, options).query(params); + } + + queryOne = Row>( + sql: string, + params?: SqliteParameterType[], + options?: SqliteQueryOptions | undefined + ): Promise { + return this.prepare(sql, options).queryOne(params); + } + + queryMany = Row>( + sql: string, + params?: SqliteParameterType[], + options?: SqliteQueryOptions | undefined + ): AsyncGenerator { + return this.prepare(sql, options).queryMany(params); + } + + queryArray = ArrayRow>( + sql: string, + params?: SqliteParameterType[], + options?: SqliteQueryOptions | undefined + ): Promise { + return this.prepare(sql, options).queryArray(params); + } + + queryOneArray = ArrayRow>( + sql: string, + params?: SqliteParameterType[], + options?: SqliteQueryOptions | undefined + ): Promise { + return this.prepare(sql, options).queryOneArray(params); + } + + queryManyArray = ArrayRow>( + sql: string, + params?: SqliteParameterType[], + options?: SqliteQueryOptions | undefined + ): AsyncGenerator { + return this.connection.queryManyArray(sql, params, options); + } + + sql = Row>( + strings: TemplateStringsArray, + ...parameters: BindValue[] + ): Promise { + const sql = strings.join('?'); + return this.query(sql, parameters); + } + + sqlArray = ArrayRow>( + strings: TemplateStringsArray, + ...parameters: BindValue[] + ): Promise { + const sql = strings.join('?'); + return this.queryArray(sql, parameters); + } +} + +export class SqlitePreparable + extends SqliteQueriable + implements SqlPreparable {} + +export class SqliteTransaction + extends SqliteQueriable + implements SqlTransaction +{ + #inTransaction: boolean = true; + get inTransaction(): boolean { + return this.connected && this.#inTransaction; + } + + get connected(): boolean { + if (!this.#inTransaction) { + throw new SqliteTransactionError( + 'Transaction is not active, create a new one using beginTransaction' + ); + } + + return super.connected; + } + + async commitTransaction( + _options?: SqliteTransactionOptions['commitTransactionOptions'] + ): Promise { + try { + await this.execute('COMMIT'); + } catch (e) { + this.#inTransaction = false; + throw e; + } + } + + async rollbackTransaction( + options?: SqliteTransactionOptions['rollbackTransactionOptions'] + ): Promise { + try { + if (options?.savepoint) { + await this.execute('ROLLBACK TO ?', [options.savepoint]); + } else { + await this.execute('ROLLBACK'); + } + } catch (e) { + this.#inTransaction = false; + throw e; + } + } + + async createSavepoint(name: string = `\t_bs3.\t`): Promise { + await this.execute(`SAVEPOINT ${name}`); + } + + async releaseSavepoint(name: string = `\t_bs3.\t`): Promise { + await this.execute(`RELEASE ${name}`); + } +} + +/** + * Represents a queriable class that can be used to run transactions. + */ +export class SqliteTransactionable + extends SqlitePreparable + implements SqlTransactionable +{ + async beginTransaction( + options?: SqliteTransactionOptions['beginTransactionOptions'] + ): Promise { + let sql = 'BEGIN'; + if (options?.behavior) { + sql += ` ${options.behavior}`; + } + await this.execute(sql); + + return new SqliteTransaction(this.connection, this.options); + } + + async transaction( + fn: (t: SqliteTransaction) => Promise, + options?: SqliteTransactionOptions + ): Promise { + const transaction = await this.beginTransaction( + options?.beginTransactionOptions + ); + + try { + const result = await fn(transaction); + await transaction.commitTransaction(options?.commitTransactionOptions); + return result; + } catch (error) { + await transaction.rollbackTransaction( + options?.rollbackTransactionOptions + ); + throw error; + } + } +} + +/** + * Single-connection client. Not safe to use concurrently. + */ +export class SqliteClient extends SqliteTransactionable implements SqlClient { + readonly eventTarget: SqliteEventTarget; + + constructor( + connectionUrl: string, + pool: SqliteDriverConnectionPool, + options: SqliteClientOptions = {} + ) { + const connect = () => { + return pool.reserveConnection(); + }; + const conn = new SqliteReservedConnection(connectionUrl, connect, options); + super(conn, options); + this.eventTarget = new SqliteEventTarget(); + } + + async connect(): Promise { + await this.connection.connect(); + this.eventTarget.dispatchEvent( + new SqliteConnectEvent({ connection: this.connection } as any) + ); + } + + async close(): Promise { + this.eventTarget.dispatchEvent( + new SqliteCloseEvent({ connection: this.connection } as any) + ); + await this.connection.close(); + } + + async [Symbol.asyncDispose](): Promise { + await this.close(); + } +} + +class SqlitePoolClient + extends SqliteTransactionable + implements + SqlPoolClient< + SqliteConnectionOptions, + SqliteConnection, + SqliteParameterType, + SqliteQueryOptions, + SqlitePreparedStatement, + SqliteTransactionOptions, + SqliteTransaction + > +{ + readonly eventTarget: SqliteEventTarget; + readonly reserved: ReservedConnection; + readonly connectionUrl: string; + + constructor( + connectionUrl: string, + reserved: ReservedConnection, + options: SqliteClientOptions = {} + ) { + const conn = new SqliteConnection( + connectionUrl, + reserved.connection, + options + ); + super(conn, options); + this.reserved = reserved; + this.connectionUrl = connectionUrl; + this.eventTarget = new SqliteEventTarget(); + } + disposed: boolean = false; + async release(): Promise { + this.disposed = true; + await this.reserved.release(); + } + + async connect(): Promise { + await this.connection.connect(); + this.eventTarget.dispatchEvent( + new SqliteConnectEvent({ connection: this.connection } as any) + ); + } + + async close(): Promise { + this.eventTarget.dispatchEvent( + new SqliteCloseEvent({ connection: this.connection } as any) + ); + await this.connection.close(); + } + + async [Symbol.asyncDispose](): Promise { + await this.close(); + } +} + +/** + * Sqlite client + */ +export class SqliteClientPool implements SqlClientPool { + readonly eventTarget: SqliteEventTarget; + + readonly connectionUrl: string; + readonly pool: SqliteDriverConnectionPool; + readonly options: SqliteClientOptions; + readonly connected = true; + + constructor( + connectionUrl: string, + pool: SqliteDriverConnectionPool, + options: SqliteClientOptions = {} + ) { + this.pool = pool; + this.connectionUrl = connectionUrl; + this.options = options; + this.eventTarget = new SqliteEventTarget(); + } + + async acquire(): Promise { + const reserved = await this.pool.reserveConnection(); + return new SqlitePoolClient(this.connectionUrl, reserved, this.options); + } + + async connect(): Promise { + // No-op + } + + async close(): Promise { + // TODO: this.eventTarget.dispatchEvent(new SqliteCloseEvent({})); + await this.pool.close(); + } + + async [Symbol.asyncDispose](): Promise { + await this.close(); + } +} diff --git a/packages/stdext-sql-adapter/src/database.ts b/packages/stdext-sql-adapter/src/database.ts new file mode 100644 index 0000000..3184d10 --- /dev/null +++ b/packages/stdext-sql-adapter/src/database.ts @@ -0,0 +1,53 @@ +/** Various options that can be configured when opening Database connection. */ +export interface DatabaseOpenOptions { + /** Whether to open database only in read-only mode. By default, this is false. */ + readonly?: boolean; + /** Whether to create a new database file at specified path if one does not exist already. By default this is true. */ + create?: boolean; + /** Raw SQLite C API flags. Specifying this ignores all other options. */ + flags?: number; + /** Opens an in-memory database. */ + memory?: boolean; + /** Whether to support BigInt columns. False by default, integers larger than 32 bit will be inaccurate. */ + int64?: boolean; + /** Apply agressive optimizations that are not possible with concurrent clients. */ + unsafeConcurrency?: boolean; + /** Enable or disable extension loading */ + enableLoadExtension?: boolean; +} + +/** Transaction function created using `Database#transaction`. */ +export type Transaction void> = (( + ...args: Parameters +) => ReturnType) & { + /** BEGIN */ + default: Transaction; + /** BEGIN DEFERRED */ + deferred: Transaction; + /** BEGIN IMMEDIATE */ + immediate: Transaction; + /** BEGIN EXCLUSIVE */ + exclusive: Transaction; +}; + +/** + * Options for user-defined functions. + * + * @link https://www.sqlite.org/c3ref/c_deterministic.html + */ +export interface FunctionOptions { + varargs?: boolean; + deterministic?: boolean; + directOnly?: boolean; + innocuous?: boolean; + subtype?: boolean; +} + +/** + * Options for user-defined aggregate functions. + */ +export interface AggregateFunctionOptions extends FunctionOptions { + start: any | (() => any); + step: (aggregate: any, ...args: any[]) => void; + final?: (aggregate: any) => any; +} diff --git a/packages/stdext-sql-adapter/src/errors.ts b/packages/stdext-sql-adapter/src/errors.ts new file mode 100644 index 0000000..097f2cb --- /dev/null +++ b/packages/stdext-sql-adapter/src/errors.ts @@ -0,0 +1,20 @@ +import { isSqlError, SqlError } from '@stdext/sql'; + +export class SqliteError extends SqlError { + constructor(msg: string) { + super(msg); + } +} + +export class SqliteTransactionError extends SqliteError { + constructor(msg: string) { + super(msg); + } +} + +/** + * Check if an error is a SqliteError + */ +export function isSqliteError(err: unknown): err is SqliteError { + return isSqlError(err) && err instanceof SqliteError; +} diff --git a/packages/stdext-sql-adapter/src/events.ts b/packages/stdext-sql-adapter/src/events.ts new file mode 100644 index 0000000..520b18c --- /dev/null +++ b/packages/stdext-sql-adapter/src/events.ts @@ -0,0 +1,27 @@ +import { + type SqlClientEventType, + SqlCloseEvent, + SqlConnectEvent, + type SqlConnectionEventInit, + SqlEventTarget +} from '@stdext/sql'; +import type { + SqliteConnection, + SqliteConnectionOptions +} from './connection.js'; + +export class SqliteEventTarget extends SqlEventTarget< + SqliteConnectionOptions, + SqliteConnection, + SqlClientEventType, + SqliteConnectionEventInit, + SqliteEvents +> {} + +export type SqliteConnectionEventInit = + SqlConnectionEventInit; + +export class SqliteConnectEvent extends SqlConnectEvent {} +export class SqliteCloseEvent extends SqlCloseEvent {} + +export type SqliteEvents = SqliteConnectEvent | SqliteCloseEvent; diff --git a/packages/stdext-sql-adapter/src/index.ts b/packages/stdext-sql-adapter/src/index.ts new file mode 100644 index 0000000..71fb871 --- /dev/null +++ b/packages/stdext-sql-adapter/src/index.ts @@ -0,0 +1,3 @@ +export * from './better-sqlite-client.js'; +export * from './errors.js'; +export * from './core.js'; diff --git a/packages/stdext-sql-adapter/src/util.ts b/packages/stdext-sql-adapter/src/util.ts new file mode 100644 index 0000000..99ab4de --- /dev/null +++ b/packages/stdext-sql-adapter/src/util.ts @@ -0,0 +1,40 @@ +import { SqlError } from '@stdext/sql'; +import type { SqliteQueryOptions } from './core.js'; + +export const encoder = new TextEncoder(); + +export function toCString(str: string): Uint8Array { + return encoder.encode(str + '\0'); +} + +export class SqliteError extends SqlError { + name = 'SqliteError'; + + constructor( + public code: number = 1, + message: string = 'Unknown Error' + ) { + super(`${code}: ${message}`); + } +} + +export function transformToAsyncGenerator< + T extends unknown, + I extends IterableIterator +>(iterableIterator: I): AsyncGenerator { + return iterableIterator as unknown as AsyncGenerator; +} + +export function mergeQueryOptions( + ...options: (SqliteQueryOptions | undefined)[] +): SqliteQueryOptions { + const mergedOptions: SqliteQueryOptions = {}; + + for (const option of options) { + if (option) { + Object.assign(mergedOptions, option); + } + } + + return mergedOptions; +} diff --git a/packages/stdext-sql-adapter/test/src/adapter.test.ts b/packages/stdext-sql-adapter/test/src/adapter.test.ts new file mode 100644 index 0000000..516bf45 --- /dev/null +++ b/packages/stdext-sql-adapter/test/src/adapter.test.ts @@ -0,0 +1,100 @@ +import * as fs from 'node:fs/promises'; +import { fileURLToPath } from 'node:url'; +import { assert, test } from 'vitest'; +import { + BetterSqliteClient, + BetterSqliteClientPool, + SqliteClient, + SqliteClientPool +} from '../../lib/index.js'; + +const assertEquals = assert.deepEqual; + +test('sqlite', async () => { + const DB_URL = new URL('../../test.db', import.meta.url); + const path = fileURLToPath(DB_URL); + + // Remove any existing test.db. + await fs.rm(path).catch(() => {}); + await fs.rm(path + '-shm').catch(() => {}); + await fs.rm(path + '-wal').catch(() => {}); + + // To test the pool: + // let pool: SqliteClientPool = new BetterSqliteClientPool(path); + // let db = await pool.acquire(); + + let db: SqliteClient = new BetterSqliteClient(path); + await db.connect(); + + await db.execute('pragma journal_mode = WAL'); + await db.execute('pragma synchronous = normal'); + assertEquals(await db.execute('pragma temp_store = memory'), 0); + + const [version] = (await db + .prepare('select sqlite_version()') + .queryOneArray<[string]>())!; + + await db.execute(`create table test ( + integer integer, + text text not null, + double double, + blob blob not null, + nullable integer + )`); + + await db.execute( + `insert into test (integer, text, double, blob, nullable) + values (?, ?, ?, ?, ?)`, + [0, 'hello world', 3.14, new Uint8Array([1, 2, 3]), null] + ); + + await db.execute('delete from test where integer = 0'); + + const SQL = `insert into test (integer, text, double, blob, nullable) + values (?, ?, ?, ?, ?)`; + const stmt = db.prepare(SQL); + + await db.transaction(async () => { + const data: any[][] = []; + for (let i = 0; i < 10; i++) { + data.push([i, `hello ${i}`, 3.14, new Uint8Array([3, 2, 1]), null]); + } + + for (const row of data) { + stmt.execute(row); + } + }); + + await stmt.deallocate(); + + const row = ( + await db + .prepare('select * from test where integer = 0') + .queryArray<[number, string, number, Uint8Array, null]>() + )[0]; + + assertEquals(row[0], 0); + assertEquals(row[1], 'hello 0'); + assertEquals(row[2], 3.14); + assertEquals(row[3], new Uint8Array([3, 2, 1])); + assertEquals(row[4], null); + + const rows = await db + .prepare('select * from test where integer != ? and text != ?') + .query<{ + integer: number; + text: string; + double: number; + blob: Uint8Array; + nullable: null; + }>([1, 'hello world']); + + assertEquals(rows.length, 9); + for (const row of rows) { + assertEquals(typeof row.integer, 'number'); + assertEquals(row.text, `hello ${row.integer}`); + assertEquals(row.double, 3.14); + assertEquals(row.blob, new Uint8Array([3, 2, 1])); + assertEquals(row.nullable, null); + } +}); diff --git a/packages/stdext-sql-adapter/test/tsconfig.json b/packages/stdext-sql-adapter/test/tsconfig.json new file mode 100644 index 0000000..42305e6 --- /dev/null +++ b/packages/stdext-sql-adapter/test/tsconfig.json @@ -0,0 +1,9 @@ +{ + "extends": "../../../tsconfig.base.json", + "compilerOptions": { + "lib": ["ES2022", "DOM"], + "noEmit": true + }, + "include": ["src"], + "references": [{ "path": "../" }] +} diff --git a/packages/stdext-sql-adapter/tsconfig.json b/packages/stdext-sql-adapter/tsconfig.json new file mode 100644 index 0000000..ab6b949 --- /dev/null +++ b/packages/stdext-sql-adapter/tsconfig.json @@ -0,0 +1,10 @@ +{ + "extends": "../../tsconfig.base.json", + "compilerOptions": { + "lib": ["ES2023", "DOM"], + "outDir": "lib", + "rootDir": "src" + }, + "include": ["src"], + "references": [{ "path": "../stdext-sql" }, { "path": "../driver" }] +} diff --git a/packages/stdext-sql-adapter/vitest.config.ts b/packages/stdext-sql-adapter/vitest.config.ts new file mode 100644 index 0000000..8932301 --- /dev/null +++ b/packages/stdext-sql-adapter/vitest.config.ts @@ -0,0 +1,9 @@ +import { defineConfig } from 'vitest/config'; + +export default defineConfig({ + esbuild: { target: 'es2022' }, + test: { + environment: 'node', + include: ['test/src/**/*.test.ts'] + } +}); diff --git a/packages/stdext-sql/README.md b/packages/stdext-sql/README.md new file mode 100644 index 0000000..3c51bcc --- /dev/null +++ b/packages/stdext-sql/README.md @@ -0,0 +1,326 @@ +# @stdext/sql + +**A fork of https://github.com/halvardssm/deno_stdext/tree/85605adb29850e6c6e7234edf67da34588e8b7b4/sql** + +The SQL package contains a standard interface for SQL based databases + +Inspired by [rust sqlx](https://docs.rs/sqlx/latest/sqlx/index.html) and +[go sql](https://pkg.go.dev/database/sql). + +The goal for this package is to have a standard interface for SQL-like database +clients that can be used in Deno, Node and other JS runtimes. + +## Usage + +Minimal usage example: + +```ts +await using client = new Client(connectionUrl, connectionOptions); +await client.connect(); +await client.execute('SOME INSERT QUERY'); +const res = await client.query('SELECT * FROM table'); +``` + +Both the `Client` and `ClientPool` need to be connected using `connect()` before +the database can be queried. At the end of the script, this connection also +needs to be cleaned up by calling `close()`. If using the new +[AsyncDispose](https://github.com/tc39/proposal-explicit-resource-management), +there is no need to call `close()` manually as shown in the example above. + +See the [examples](#examples) section for more usage. + +### Client + +The Client provides a database client with the following methods (see +[SqlClient](./client.ts)): + +- `connect` (See [SqlConnection](./connection.ts)): Creates a connection to the + database +- `close` (See [SqlConnection](./connection.ts)): Closes the connection to the + database +- `execute` (See [SqlQueriable](./core.ts)): Executes a SQL statement +- `query` (See [SqlQueriable](./core.ts)): Queries the database and returns an + array of object +- `queryOne` (See [SqlQueriable](./core.ts)): Queries the database and returns + at most one entry as an object +- `queryMany` (See [SqlQueriable](./core.ts)): Queries the database with an + async generator and yields each entry as an object. This is good for when you + want to iterate over a massive amount of rows. +- `queryArray` (See [SqlQueriable](./core.ts)): Queries the database and returns + an array of arrays +- `queryOneArray` (See [SqlQueriable](./core.ts)): Queries the database and + returns at most one entry as an array +- `queryManyArray` (See [SqlQueriable](./core.ts)): Queries the database with an + async generator and yields each entry as an array. This is good for when you + want to iterate over a massive amount of rows. +- `sql` (See [SqlQueriable](./core.ts)): Allows you to create a query using + template literals, and returns the entries as an array of objects. This is a + wrapper around `query` +- `sqlArray` (See [SqlQueriable](./core.ts)): Allows you to create a query using + template literals, and returns the entries as an array of arrays. This is a + wrapper around `queryArray` +- `prepare` (See [SqlPreparable](./core.ts)): Returns a prepared statement class + that contains a subset of the Queriable functions (see + [SqlPreparedQueriable](./core.ts)) +- `beginTransaction` (See [SqlTransactionable](./core.ts)): Returns a + transaction class that contains implements the queriable functions, as well as + transaction related functions (see [SqlTransactionQueriable](./core.ts)) +- `transaction` (See [SqlTransactionable](./core.ts)): A wrapper function for + transactions, handles the logic of beginning, committing and rollback a + transaction. + +#### Events + +The following events can be subscribed to according to the specs (see +[events.ts](./events.ts)): + +- `connect`: Gets dispatched when a connection is established +- `close`: Gets dispatched when a connection is about to be closed +- `error`: Gets dispatched when an error is triggered + +### ClientPool + +The ClientPool provides a database client pool (a pool of clients) with the +following methods (see [SqlClientPool](./pool.ts)): + +- `connect` (See [SqlConnection](./core.ts)): Creates the connection classes and + adds them to a connection pool, and optionally connects them to the database +- `close` (See [SqlConnection](./core.ts)): Closes all connections in the pool +- `acquire` (See [SqlPoolable](./core.ts)): Retrieves a + [SqlPoolClient](./pool.ts) (a subset of [Client](#client)), and connects if + not already connected + +#### Events + +The following events can be subscribed to according to the specs (see +[events.ts](./events.ts)): + +- `connect`: Gets dispatched when a connection is established for a pool client + (with lazy initialization, it will only get triggered once a connection is + popped and activated) +- `close`: Gets dispatched when a connection is about to be closed +- `error`: Gets dispatched when an error is triggered +- `acquire`: Gets dispatched when a connection is acquired from the pool +- `release`: Gets dispatched when a connection is released back to the pool + +### Examples + +Async dispose + +```ts +await using client = new Client(connectionUrl, connectionOptions); +await client.connect(); +await client.execute('SOME INSERT QUERY'); +const res = await client.query('SELECT * FROM table'); +``` + +Using const (requires manual close at the end) + +```ts +const client = new Client(connectionUrl, connectionOptions); +await client.connect(); +await client.execute('SOME INSERT QUERY'); +const res = await client.query('SELECT * FROM table'); +await client.close(); +``` + +Query objects + +```ts +const res = await client.query('SELECT * FROM table'); +console.log(res); +// [{ col1: "some value" }] +``` + +Query one object + +```ts +const res = await client.queryOne('SELECT * FROM table'); +console.log(res); +// { col1: "some value" } +``` + +Query many objects with an iterator + +```ts +const res = Array.fromAsync(client.queryMany('SELECT * FROM table')); +console.log(res); +// [{ col1: "some value" }] + +// OR + +for await (const iterator of client.queryMany('SELECT * FROM table')) { + console.log(res); + // { col1: "some value" } +} +``` + +Query as an array + +```ts +const res = await client.queryArray('SELECT * FROM table'); +console.log(res); +// [[ "some value" ]] +``` + +Query one as an array + +```ts +const res = await client.queryOneArray('SELECT * FROM table'); +console.log(res); +// [[ "some value" ]] +``` + +Query many as array with an iterator + +```ts +const res = Array.fromAsync(client.queryManyArray('SELECT * FROM table')); +console.log(res); +// [[ "some value" ]] + +// OR + +for await (const iterator of client.queryManyArray('SELECT * FROM table')) { + console.log(res); + // [ "some value" ] +} +``` + +Query with template literals as an object + +```ts +const res = await client.sql`SELECT * FROM table where id = ${id}`; +console.log(res); +// [{ col1: "some value" }] +``` + +Query with template literals as an array + +```ts +const res = await client.sqlArray`SELECT * FROM table where id = ${id}`; +console.log(res); +// [[ "some value" ]] +``` + +Transaction + +```ts +const transaction = await client.beginTransaction(); +await transaction.execute('SOME INSERT QUERY'); +await transaction.commitTransaction(); +// `transaction` can no longer be used, and a new transaction needs to be created +``` + +Transaction wrapper + +```ts +const res = await client.transaction(async (t) => { + await t.execute('SOME INSERT QUERY'); + return t.query('SOME SELECT QUERY'); +}); +console.log(res); +// [{ col1: "some value" }] +``` + +Prepared statement + +```ts +const prepared = db.prepare('SOME PREPARED STATEMENT'); +await prepared.query([...params]); +console.log(res); +// [{ col1: "some value" }] +``` + +## Implementation + +> This section is for implementing the interface for database drivers. For +> general usage, read the [usage](#usage) section. +> To be fully compliant with the specs, you will need to implement the following +> classes for your database driver: + +- `Connection` ([SqlConnection](./connection.ts)): This represents the + connection to the database. This should preferably only contain the + functionality of containing a connection, and provide a minimum set of query + methods to be used to query the database +- `PreparedStatement` ([SqlPreparedStatement](./core.ts)): This represents a + prepared statement. All queriable methods must be implemented +- `Transaction` ([SqlTransaction](./core.ts)): This represents a transaction. + All queriable methods must be implemented +- `Client` ([SqlClient](./client.ts)): This represents a database client +- `ClientPool` ([SqlClientPool](./pool.ts)): This represents a pool of clients +- `PoolClient` ([SqlPoolClient](./pool.ts)): This represents a client to be + provided by a pool + +It is also however advisable to create additional helper classes for easier +inheritance. See [test.ts](./test.ts) for a minimum but functional example of +how to implement these interfaces into intermediate classes. + +### Inheritance graph + +Here is an overview of the inheritance and flow of the different interfaces. In +most cases, these are the classes and the inheritance graph that should be +implemented. + +![inheritance flow](./_assets/inheritance_flowchart.jpg) + +### Constructor Signature + +The constructor also must follow a strict signature. + +The constructor for both the Client and the ClientPool follows the same +signature: + +1. `connectionUrl`: string | URL +2. `options`?: ConnectionOptions & QueryOptions + +As `ConnectionOptions` and `QueryOptions` can be extended, the options can be +used to customize the settings, thus having a standard 2 argument signature of +the constructor. + +> The current way to specify a constructor using interfaces in TS, is to use a +> combination of `implements` and `satisfies`. This will be updated if anything +> changes. + +#### Client + +The Client must have a constructor following the signature specified by +`SqlClientConstructor`. + +```ts +export const Client = class extends Transactionable implements SqlClient<...> { // Transactionable is a class implementing `SqlTransactionable` + ... + // The constructor now has to satisfy `SqlClientConstructor` + constructor( + connectionUrl: string | URL, + options: ConnectionOptions & QueryOptions = {}, + ) { + ... + } + ... +} satisfies SqlClientConstructor<...>; + +// We need to also export the instance type of the client +export type Client = InstanceType; +``` + +#### ClientPool + +The ClientPool must have a constructor following the signature specified by +`SqlClientPoolConstructor`. + +```ts +const ClientPool = class extends Transactionable implements SqlClientPool<...> { // Transactionable is a class implementing `SqlTransactionable` + ... + // The constructor now has to satisfy `SqlClientPoolConstructor` + constructor( + connectionUrl: string | URL, + options: ConnectionOptions & QueryOptions = {}, + ) { + ... + } + ... +} satisfies SqlClientPoolConstructor<...>; + +// We need to also export the instance type of the client pool +export type ClientPool = InstanceType; +``` diff --git a/packages/stdext-sql/package.json b/packages/stdext-sql/package.json new file mode 100644 index 0000000..b50655e --- /dev/null +++ b/packages/stdext-sql/package.json @@ -0,0 +1,20 @@ +{ + "name": "@stdext/sql", + "version": "0.0.1", + "description": "", + "type": "module", + "scripts": { + "build": "tsc -b", + "clean": "tsc -b --clean && rm -rf lib" + }, + "exports": { + ".": "./lib/mod.js" + }, + "keywords": [], + "author": "", + "license": "MIT", + "dependencies": {}, + "devDependencies": { + "typescript": "^5.5.4" + } +} diff --git a/packages/stdext-sql/src/asserts.ts b/packages/stdext-sql/src/asserts.ts new file mode 100644 index 0000000..2348fad --- /dev/null +++ b/packages/stdext-sql/src/asserts.ts @@ -0,0 +1,427 @@ +import { + assertExists, + assertInstanceOf, + AssertionError +} from './std-assert.js'; +import { + type SqlClient, + type SqlClientPool, + type SqlConnectable, + type SqlConnection, + SqlError, + type SqlEventable, + type SqlPoolClient, + type SqlPreparedStatement, + type SqlQueriable, + type SqlTransaction, + type SqlTransactionable +} from './mod.js'; + +/** + * Check if an object has a property + */ +function hasProperty(obj: T, property: string | symbol | number): boolean { + let currentProto = obj; + + while (currentProto !== null && currentProto !== undefined) { + if (Object.hasOwn(currentProto, property)) { + return true; + } + const descriptor = Object.getOwnPropertyDescriptor(currentProto, property); + if (descriptor !== undefined) { + return true; + } + currentProto = Object.getPrototypeOf(currentProto); + } + + return false; +} + +/** + * Check if an object has properties + */ +export function hasProperties( + value: unknown, + properties: Array +): value is { [K in T]: unknown } { + assertExists(value); + + const missing: Array = []; + + for (const property of properties) { + if (!hasProperty(value as { [K in T]: unknown }, property)) { + missing.push(property); + } + } + + return missing.length === 0; +} + +/** + * Check if an object has properties and throws if not + */ +export function assertHasProperties( + value: unknown, + properties: Array +): asserts value is { [K in T]: unknown } { + assertExists(value); + + const missing: Array = []; + + for (const property of properties) { + if (!hasProperty(value as { [K in T]: unknown }, property)) { + missing.push(property); + } + } + + if (missing.length) { + throw new AssertionError( + `Object is missing properties: ${missing + .map((e) => e.toString()) + .join(', ')}` + ); + } +} + +/** + * Check if an error is a SqlError + */ +export function isSqlError(err: unknown): err is SqlError { + return err instanceof SqlError; +} + +/** + * Asserts that an error is a SqlError + */ +export function assertIsSqlError(err: unknown): asserts err is SqlError { + assertInstanceOf(err, SqlError); +} + +/** + * Check if an object is an AsyncDisposable + */ +export function isAsyncDisposable(value: unknown): value is AsyncDisposable { + return hasProperties(value, [Symbol.asyncDispose]); +} + +/** + * Asserts that an object is an AsyncDisposable + */ +export function assertIsAsyncDisposable( + value: unknown +): asserts value is AsyncDisposable { + assertHasProperties(value, [Symbol.asyncDispose]); +} + +/** + * Check if an object is an SqlConnection + */ +export function isSqlConnection(value: unknown): value is SqlConnection { + return ( + isAsyncDisposable(value) && + hasProperties(value, [ + 'options', + 'connected', + 'connect', + 'close', + 'execute', + 'queryMany', + 'queryManyArray' + ]) + ); +} + +/** + * Asserts that an object is an SqlConnection + */ +export function assertIsSqlConnection( + value: unknown +): asserts value is SqlConnection { + assertIsAsyncDisposable(value); + assertHasProperties(value, [ + 'options', + 'connected', + 'connect', + 'close', + 'execute', + 'queryMany', + 'queryManyArray' + ]); +} + +/** + * Check if an object is an SqlConnectable + */ +export function isSqlConnectable(value: unknown): value is SqlConnectable { + return ( + isAsyncDisposable(value) && + hasProperties(value, ['connection', 'connected']) && + isSqlConnection(value.connection) + ); +} + +/** + * Asserts that an object is an SqlConnectable + */ +export function assertIsSqlConnectable( + value: unknown +): asserts value is SqlConnectable { + assertIsAsyncDisposable(value); + assertHasProperties(value, ['connection', 'connected']); + assertIsSqlConnection(value.connection); +} + +/** + * Check if an object is an SqlPreparedStatement + */ +export function isSqlPreparedStatement( + value: unknown +): value is SqlPreparedStatement { + return ( + isSqlConnectable(value) && + hasProperties(value, [ + 'sql', + 'options', + 'execute', + 'query', + 'queryOne', + 'queryMany', + 'queryArray', + 'queryOneArray', + 'queryManyArray' + ]) + ); +} + +/** + * Asserts that an object is an SqlPreparedStatement + */ +export function assertIsSqlPreparedStatement( + value: unknown +): asserts value is SqlPreparedStatement { + assertIsSqlConnectable(value); + assertHasProperties(value, [ + 'sql', + 'options', + 'execute', + 'query', + 'queryOne', + 'queryMany', + 'queryArray', + 'queryOneArray', + 'queryManyArray' + ]); +} + +/** + * Check if an object is an SqlQueriable + */ +export function isSqlQueriable(value: unknown): value is SqlQueriable { + return ( + isSqlConnectable(value) && + hasProperties(value, [ + 'options', + 'execute', + 'query', + 'queryOne', + 'queryMany', + 'queryArray', + 'queryOneArray', + 'queryManyArray' + ]) + ); +} + +/** + * Asserts that an object is an SqlQueriable + */ +export function assertIsSqlQueriable( + value: unknown +): asserts value is SqlQueriable { + assertIsSqlConnectable(value); + assertHasProperties(value, [ + 'options', + 'execute', + 'query', + 'queryOne', + 'queryMany', + 'queryArray', + 'queryOneArray', + 'queryManyArray' + ]); +} + +/** + * Check if an object is an SqlTransaction + */ +export function isSqlPreparable(value: unknown): value is SqlQueriable { + return isSqlQueriable(value) && hasProperties(value, ['prepare']); +} + +/** + * Asserts that an object is an SqlTransaction + */ +export function assertIsSqlPreparable( + value: unknown +): asserts value is SqlQueriable { + assertIsSqlQueriable(value); + assertHasProperties(value, ['prepare']); +} + +/** + * Check if an object is an SqlTransaction + */ +export function isSqlTransaction(value: unknown): value is SqlTransaction { + return ( + isSqlPreparable(value) && + hasProperties(value, [ + 'inTransaction', + 'commitTransaction', + 'rollbackTransaction', + 'createSavepoint', + 'releaseSavepoint' + ]) + ); +} + +/** + * Asserts that an object is an SqlTransaction + */ +export function assertIsSqlTransaction( + value: unknown +): asserts value is SqlTransaction { + assertIsSqlPreparable(value); + assertHasProperties(value, [ + 'inTransaction', + 'commitTransaction', + 'rollbackTransaction', + 'createSavepoint', + 'releaseSavepoint' + ]); +} + +/** + * Check if an object is an SqlTransactionable + */ +export function isSqlTransactionable( + value: unknown +): value is SqlTransactionable { + return ( + isSqlPreparable(value) && + hasProperties(value, ['beginTransaction', 'transaction']) + ); +} + +/** + * Asserts that an object is an SqlTransactionable + */ +export function assertIsSqlTransactionable( + value: unknown +): asserts value is SqlTransactionable { + assertIsSqlPreparable(value); + assertHasProperties(value, ['beginTransaction', 'transaction']); +} + +/** + * Check if an object is an SqlEventable + */ +export function isSqlEventable(value: unknown): value is SqlEventable { + return ( + hasProperties(value, ['eventTarget']) && + value.eventTarget instanceof EventTarget + ); +} + +/** + * Asserts that an object is an SqlEventable + */ +export function assertIsSqlEventable( + value: unknown +): asserts value is SqlEventable { + assertHasProperties(value, ['eventTarget']); + assertInstanceOf(value.eventTarget, EventTarget); +} + +/** + * Check if an object is an SqlClient + */ +export function isSqlClient(value: unknown): value is SqlClient { + return ( + isSqlConnection(value) && + isSqlQueriable(value) && + isSqlTransactionable(value) && + isSqlEventable(value) && + hasProperties(value, ['options']) + ); +} + +/** + * Asserts that an object is an SqlClient + */ +export function assertIsSqlClient(value: unknown): asserts value is SqlClient { + assertIsSqlConnection(value); + assertIsSqlQueriable(value); + assertIsSqlTransactionable(value); + assertIsSqlEventable(value); + assertHasProperties(value, ['options']); +} + +/** + * Check if an object is an SqlPoolClient + */ +export function isSqlPoolClient(value: unknown): value is SqlPoolClient { + return ( + isSqlConnectable(value) && + isSqlTransactionable(value) && + hasProperties(value, ['options', 'disposed', 'release']) + ); +} + +/** + * Asserts that an object is an SqlPoolClient + */ +export function assertIsSqlPoolClient( + value: unknown +): asserts value is SqlPoolClient { + assertIsSqlConnectable(value); + assertIsSqlTransactionable(value); + assertHasProperties(value, ['options', 'disposed', 'release']); +} + +/** + * Check if an object is an SqlClientPool + */ +export function isSqlClientPool(value: unknown): value is SqlClientPool { + return ( + isSqlEventable(value) && + isAsyncDisposable(value) && + hasProperties(value, [ + 'connectionUrl', + 'options', + 'connected', + 'connect', + 'close', + 'deferredStack', + 'acquire' + ]) + ); +} + +/** + * Asserts that an object is an SqlClientPool + */ +export function assertIsSqlClientPool( + value: unknown +): asserts value is SqlClientPool { + assertIsSqlEventable(value); + assertIsAsyncDisposable(value); + assertHasProperties(value, [ + 'connectionUrl', + 'options', + 'connected', + 'connect', + 'close', + 'deferredStack', + 'acquire' + ]); +} diff --git a/packages/stdext-sql/src/client.ts b/packages/stdext-sql/src/client.ts new file mode 100644 index 0000000..f0307c0 --- /dev/null +++ b/packages/stdext-sql/src/client.ts @@ -0,0 +1,126 @@ +import type { SqlConnection, SqlConnectionOptions } from './connection.js'; +import type { + SqlPreparedStatement, + SqlQueryOptions, + SqlTransaction, + SqlTransactionable, + SqlTransactionOptions +} from './core.js'; +import type { SqlEventable, SqlEventTarget } from './events.js'; + +/** + * SqlClient + * + * This represents a database client. When you need a single connection + * to the database, you will in most cases use this interface. + */ +export interface SqlClient< + EventTarget extends SqlEventTarget = SqlEventTarget, + ConnectionOptions extends SqlConnectionOptions = SqlConnectionOptions, + ParameterType extends unknown = unknown, + QueryOptions extends SqlQueryOptions = SqlQueryOptions, + Connection extends SqlConnection< + ConnectionOptions, + ParameterType, + QueryOptions + > = SqlConnection, + PreparedStatement extends SqlPreparedStatement< + ConnectionOptions, + ParameterType, + QueryOptions, + Connection + > = SqlPreparedStatement< + ConnectionOptions, + ParameterType, + QueryOptions, + Connection + >, + TransactionOptions extends SqlTransactionOptions = SqlTransactionOptions, + Transaction extends SqlTransaction< + ConnectionOptions, + ParameterType, + QueryOptions, + Connection, + PreparedStatement, + TransactionOptions + > = SqlTransaction< + ConnectionOptions, + ParameterType, + QueryOptions, + Connection, + PreparedStatement, + TransactionOptions + > +> extends Pick< + SqlConnection, + 'close' | 'connect' | 'connected' + >, + SqlTransactionable< + ConnectionOptions, + ParameterType, + QueryOptions, + Connection, + PreparedStatement, + TransactionOptions, + Transaction + >, + SqlEventable, + AsyncDisposable {} + +/** + * SqlClientConstructor + * + * The constructor for the SqlClient interface. + */ +export interface SqlClientConstructor< + EventTarget extends SqlEventTarget = SqlEventTarget, + ConnectionOptions extends SqlConnectionOptions = SqlConnectionOptions, + ParameterType extends unknown = unknown, + QueryOptions extends SqlQueryOptions = SqlQueryOptions, + Connection extends SqlConnection< + ConnectionOptions, + ParameterType, + QueryOptions + > = SqlConnection, + PreparedStatement extends SqlPreparedStatement< + ConnectionOptions, + ParameterType, + QueryOptions, + Connection + > = SqlPreparedStatement< + ConnectionOptions, + ParameterType, + QueryOptions, + Connection + >, + TransactionOptions extends SqlTransactionOptions = SqlTransactionOptions, + Transaction extends SqlTransaction< + ConnectionOptions, + ParameterType, + QueryOptions, + Connection, + PreparedStatement, + TransactionOptions + > = SqlTransaction< + ConnectionOptions, + ParameterType, + QueryOptions, + Connection, + PreparedStatement, + TransactionOptions + > +> { + new ( + connectionUrl: string | URL, + options?: ConnectionOptions & QueryOptions + ): SqlClient< + EventTarget, + ConnectionOptions, + ParameterType, + QueryOptions, + Connection, + PreparedStatement, + TransactionOptions, + Transaction + >; +} diff --git a/packages/stdext-sql/src/connection.ts b/packages/stdext-sql/src/connection.ts new file mode 100644 index 0000000..f518440 --- /dev/null +++ b/packages/stdext-sql/src/connection.ts @@ -0,0 +1,131 @@ +import type { ArrayRow, Row, SqlQueryOptions } from './core.js'; + +/** + * SqlConnectionOptions + * + * The options that will be used when connecting to the database. + */ +export interface SqlConnectionOptions { + /** + * Transforms the value that will be sent to the database + */ + transformInput?: (value: unknown) => unknown; + /** + * Transforms the value received from the database + */ + transformOutput?: (value: unknown) => unknown; +} + +/** + * SqlConnection + * + * This represents a connection to a database. + * When a user wants a single connection to the database, + * they should use a class implementing or using this interface. + * + * The class implementing this interface should be able to connect to the database, + * and have the following constructor arguments (if more options are needed, extend the SqlConnectionOptions): + * - connectionUrl: string|URL + * - connectionOptions?: SqlConnectionOptions; + */ +export interface SqlConnection< + ConnectionOptions extends SqlConnectionOptions = SqlConnectionOptions, + ParameterType extends unknown = unknown, + QueryOptions extends SqlQueryOptions = SqlQueryOptions +> extends AsyncDisposable { + /** + * Connection URL + */ + readonly connectionUrl: string; + + /** + * Connection options + */ + readonly options: ConnectionOptions; + + /** + * Whether the connection is connected to the database + */ + get connected(): boolean; + + /** + * Create a connection to the database + */ + connect(): Promise; + + /** + * Close the connection to the database + */ + close(): Promise; + + /** + * Execute a SQL statement + * + * @param sql the SQL statement + * @param params the parameters to bind to the SQL statement + * @param options the options to pass to the query method, will be merged with the global options + * @returns the number of affected rows if any + */ + execute( + sql: string, + params?: ParameterType[], + options?: QueryOptions + ): Promise; + + /** + * Query the database and return an iterator. + * Usefull when querying large datasets, as this should take advantage of data streams. + * + * @param sql the SQL statement + * @param params the parameters to bind to the SQL statement + * @param options the options to pass to the query method, will be merged with the global options + * @returns the rows returned by the query as object entries + */ + // deno-lint-ignore no-explicit-any + queryMany = Row>( + sql: string, + params?: ParameterType[], + options?: QueryOptions + ): AsyncGenerator; + + /** + * Query the database and return an iterator. + * Usefull when querying large datasets, as this should take advantage of data streams. + * + * @param sql the SQL statement + * @param params the parameters to bind to the SQL statement + * @param options the options to pass to the query method, will be merged with the global options + * @returns the rows returned by the query as array entries + */ + // deno-lint-ignore no-explicit-any + queryManyArray = ArrayRow>( + sql: string, + params?: ParameterType[], + options?: QueryOptions + ): AsyncGenerator; +} + +/** + * SqlConnectable + * + * The base interface for everything that interracts with the connection like querying. + */ +export interface SqlConnectable< + Options extends SqlConnectionOptions = SqlConnectionOptions, + Connection extends SqlConnection = SqlConnection +> extends AsyncDisposable { + /** + * The (global) options. + */ + readonly options: Options; + + /** + * The connection to the database + */ + readonly connection: Connection; + + /** + * Whether the connection is connected or not + */ + get connected(): boolean; +} diff --git a/packages/stdext-sql/src/core.ts b/packages/stdext-sql/src/core.ts new file mode 100644 index 0000000..36776e3 --- /dev/null +++ b/packages/stdext-sql/src/core.ts @@ -0,0 +1,483 @@ +// deno-lint-ignore-file no-explicit-any +import type { + SqlConnectable, + SqlConnection, + SqlConnectionOptions +} from './connection.js'; + +/** + * Row + * + * Row type for SQL queries, represented as an object entry. + */ +export type Row = Record; + +/** + * ArrayRow + * + * Row type for SQL queries, represented as an array entry. + */ +export type ArrayRow = T[]; + +/** + * SqlTransactionOptions + * + * Core transaction options + * Used to type the options for the transaction methods + */ +export type SqlTransactionOptions = { + beginTransactionOptions?: Record; + commitTransactionOptions?: Record; + rollbackTransactionOptions?: Record; +}; + +/** + * SqlQueryOptions + * + * Options to pass to the query methods. + */ +export interface SqlQueryOptions extends SqlConnectionOptions { + /** + * A signal to abort the query. + */ + signal?: AbortSignal; +} + +/** + * SqlPreparedQueriable + * + * Represents a prepared statement to be executed separately from creation. + */ +export interface SqlPreparedStatement< + ConnectionOptions extends SqlConnectionOptions = SqlConnectionOptions, + ParameterType extends unknown = unknown, + QueryOptions extends SqlQueryOptions = SqlQueryOptions, + Connection extends SqlConnection< + ConnectionOptions, + ParameterType, + QueryOptions + > = SqlConnection +> extends SqlConnectable { + readonly options: ConnectionOptions & QueryOptions; + + /** + * The SQL statement + */ + readonly sql: string; + + /** + * Whether the prepared statement has been deallocated or not. + */ + deallocated: boolean; + + /** + * Deallocate the prepared statement + */ + deallocate(): Promise; + + /** + * Executes the prepared statement + * + * @param params the parameters to bind to the SQL statement + * @param options the options to pass to the query method, will be merged with the global options + * @returns the number of affected rows if any + */ + execute( + params?: ParameterType[], + options?: QueryOptions + ): Promise; + /** + * Query the database with the prepared statement + * + * @param params the parameters to bind to the SQL statement + * @param options the options to pass to the query method, will be merged with the global options + * @returns the rows returned by the query as object entries + */ + query = Row>( + params?: ParameterType[], + options?: QueryOptions + ): Promise; + /** + * Query the database with the prepared statement, and return at most one row + * + * @param params the parameters to bind to the SQL statement + * @param options the options to pass to the query method, will be merged with the global options + * @returns the row returned by the query as an object entry, or undefined if no row is returned + */ + queryOne = Row>( + params?: ParameterType[], + options?: QueryOptions + ): Promise; + /** + * Query the database with the prepared statement, and return an iterator. + * Usefull when querying large datasets, as this should take advantage of data streams. + * + * @param params the parameters to bind to the SQL statement + * @param options the options to pass to the query method, will be merged with the global options + * @returns the rows returned by the query as object entries + */ + queryMany = Row>( + params?: ParameterType[], + options?: QueryOptions + ): AsyncGenerator; + /** + * Query the database with the prepared statement + * + * @param params the parameters to bind to the SQL statement + * @param options the options to pass to the query method, will be merged with the global options + * @returns the rows returned by the query as array entries + */ + queryArray = ArrayRow>( + params?: ParameterType[], + options?: QueryOptions + ): Promise; + /** + * Query the database with the prepared statement, and return at most one row + * + * @param sql the SQL statement + * @param params the parameters to bind to the SQL statement + * @param options the options to pass to the query method, will be merged with the global options + * @returns the row returned by the query as an array entry, or undefined if no row is returned + */ + queryOneArray = ArrayRow>( + params?: ParameterType[], + options?: QueryOptions + ): Promise; + + /** + * Query the database with the prepared statement, and return an iterator. + * Usefull when querying large datasets, as this should take advantage of data streams. + * + * @param params the parameters to bind to the SQL statement + * @param options the options to pass to the query method, will be merged with the global options + * @returns the rows returned by the query as array entries + */ + queryManyArray = ArrayRow>( + params?: ParameterType[], + options?: QueryOptions + ): AsyncGenerator; +} + +/** + * SqlQueriable + * + * Represents an object that can execute SQL queries. + */ +export interface SqlQueriable< + ConnectionOptions extends SqlConnectionOptions = SqlConnectionOptions, + ParameterType extends unknown = unknown, + QueryOptions extends SqlQueryOptions = SqlQueryOptions, + Connection extends SqlConnection< + ConnectionOptions, + ParameterType, + QueryOptions + > = SqlConnection +> extends SqlConnectable { + readonly options: ConnectionOptions & QueryOptions; + + /** + * Execute a SQL statement + * + * @param sql the SQL statement + * @param params the parameters to bind to the SQL statement + * @param options the options to pass to the query method, will be merged with the global options + * @returns the number of affected rows if any + */ + execute( + sql: string, + params?: ParameterType[], + options?: QueryOptions + ): Promise; + /** + * Query the database + * + * @param sql the SQL statement + * @param params the parameters to bind to the SQL statement + * @param options the options to pass to the query method, will be merged with the global options + * @returns the rows returned by the query as object entries + */ + query = Row>( + sql: string, + params?: ParameterType[], + options?: QueryOptions + ): Promise; + /** + * Query the database and return at most one row + * + * @param sql the SQL statement + * @param params the parameters to bind to the SQL statement + * @param options the options to pass to the query method, will be merged with the global options + * @returns the row returned by the query as an object entry, or undefined if no row is returned + */ + queryOne = Row>( + sql: string, + params?: ParameterType[], + options?: QueryOptions + ): Promise; + /** + * Query the database and return an iterator. + * Usefull when querying large datasets, as this should take advantage of data streams. + * + * @param sql the SQL statement + * @param params the parameters to bind to the SQL statement + * @param options the options to pass to the query method, will be merged with the global options + * @returns the rows returned by the query as object entries + */ + queryMany = Row>( + sql: string, + params?: ParameterType[], + options?: QueryOptions + ): AsyncGenerator; + /** + * Query the database + * + * @param sql the SQL statement + * @param params the parameters to bind to the SQL statement + * @param options the options to pass to the query method, will be merged with the global options + * @returns the rows returned by the query as array entries + */ + queryArray = ArrayRow>( + sql: string, + params?: ParameterType[], + options?: QueryOptions + ): Promise; + /** + * Query the database and return at most one row + * + * @param sql the SQL statement + * @param params the parameters to bind to the SQL statement + * @param options the options to pass to the query method, will be merged with the global options + * @returns the row returned by the query as an array entry, or undefined if no row is returned + */ + queryOneArray = ArrayRow>( + sql: string, + params?: ParameterType[], + options?: QueryOptions + ): Promise; + + /** + * Query the database and return an iterator. + * Usefull when querying large datasets, as this should take advantage of data streams. + * + * @param sql the SQL statement + * @param params the parameters to bind to the SQL statement + * @param options the options to pass to the query method, will be merged with the global options + * @returns the rows returned by the query as array entries + */ + queryManyArray = ArrayRow>( + sql: string, + params?: ParameterType[], + options?: QueryOptions + ): AsyncGenerator; + + /** + * Query the database using tagged template + * + * @returns the rows returned by the query as object entries + */ + sql = Row>( + strings: TemplateStringsArray, + ...parameters: ParameterType[] + ): Promise; + + /** + * Query the database using tagged template + * + * @returns the rows returned by the query as array entries + */ + sqlArray = ArrayRow>( + strings: TemplateStringsArray, + ...parameters: ParameterType[] + ): Promise; +} + +/** + * SqlPreparable + * + * Represents an object that can create a prepared statement. + */ +export interface SqlPreparable< + ConnectionOptions extends SqlConnectionOptions = SqlConnectionOptions, + ParameterType extends unknown = unknown, + QueryOptions extends SqlQueryOptions = SqlQueryOptions, + Connection extends SqlConnection< + ConnectionOptions, + ParameterType, + QueryOptions + > = SqlConnection, + PreparedStatement extends SqlPreparedStatement< + ConnectionOptions, + ParameterType, + QueryOptions, + Connection + > = SqlPreparedStatement< + ConnectionOptions, + ParameterType, + QueryOptions, + Connection + > +> extends SqlQueriable< + ConnectionOptions, + ParameterType, + QueryOptions, + Connection + > { + /** + * Create a prepared statement that can be executed multiple times. + * This is useful when you want to execute the same SQL statement multiple times with different parameters. + * + * @param sql the SQL statement + * @param options the options to pass to the query method, will be merged with the global options + * @returns a prepared statement + * + * @example + * ```ts + * const stmt = db.prepare("SELECT * FROM table WHERE id = ?"); + * + * for (let i = 0; i < 10; i++) { + * const row of stmt.query([i]) + * console.log(row); + * } + * ``` + */ + prepare(sql: string, options?: QueryOptions): PreparedStatement; +} + +/** + * SqlTransaction + * + * Represents a transaction. + */ +export interface SqlTransaction< + ConnectionOptions extends SqlConnectionOptions = SqlConnectionOptions, + ParameterType extends unknown = unknown, + QueryOptions extends SqlQueryOptions = SqlQueryOptions, + Connection extends SqlConnection< + ConnectionOptions, + ParameterType, + QueryOptions + > = SqlConnection, + PreparedStatement extends SqlPreparedStatement< + ConnectionOptions, + ParameterType, + QueryOptions, + Connection + > = SqlPreparedStatement< + ConnectionOptions, + ParameterType, + QueryOptions, + Connection + >, + TransactionOptions extends SqlTransactionOptions = SqlTransactionOptions +> extends SqlPreparable< + ConnectionOptions, + ParameterType, + QueryOptions, + Connection, + PreparedStatement + > { + readonly options: ConnectionOptions & QueryOptions; + /** + * Whether the connection is in an active transaction or not. + */ + inTransaction: boolean; + + /** + * Commit the transaction + */ + commitTransaction( + options?: TransactionOptions['commitTransactionOptions'] + ): Promise; + /** + * Rollback the transaction + */ + rollbackTransaction( + options?: TransactionOptions['rollbackTransactionOptions'] + ): Promise; + /** + * Create a save point + * + * @param name the name of the save point + */ + createSavepoint(name?: string): Promise; + /** + * Release a save point + * + * @param name the name of the save point + */ + releaseSavepoint(name?: string): Promise; +} + +/** + * SqlTransactionable + * + * Represents an object that can create a transaction and a prepared statement. + * + * This interface is to be implemented by any class that supports creating a prepared statement. + * A prepared statement should in most cases be unique to a connection, + * and should not live after the related connection is closed. + */ +export interface SqlTransactionable< + ConnectionOptions extends SqlConnectionOptions = SqlConnectionOptions, + ParameterType extends unknown = unknown, + QueryOptions extends SqlQueryOptions = SqlQueryOptions, + Connection extends SqlConnection< + ConnectionOptions, + ParameterType, + QueryOptions + > = SqlConnection, + PreparedStatement extends SqlPreparedStatement< + ConnectionOptions, + ParameterType, + QueryOptions, + Connection + > = SqlPreparedStatement< + ConnectionOptions, + ParameterType, + QueryOptions, + Connection + >, + TransactionOptions extends SqlTransactionOptions = SqlTransactionOptions, + Transaction extends SqlTransaction< + ConnectionOptions, + ParameterType, + QueryOptions, + Connection, + PreparedStatement, + TransactionOptions + > = SqlTransaction< + ConnectionOptions, + ParameterType, + QueryOptions, + Connection, + PreparedStatement, + TransactionOptions + > +> extends SqlPreparable< + ConnectionOptions, + ParameterType, + QueryOptions, + Connection, + PreparedStatement + > { + readonly options: ConnectionOptions & QueryOptions; + /** + * Starts a transaction + */ + beginTransaction( + options?: TransactionOptions['beginTransactionOptions'] + ): Promise; + + /** + * Transaction wrapper + * + * Automatically begins a transaction, executes the callback function, and commits the transaction. + * + * If the callback function throws an error, the transaction will be rolled back and the error will be rethrown. + * If the callback function returns successfully, the transaction will be committed. + * + * @param fn callback function to be executed within a transaction + * @returns the result of the callback function + */ + transaction(fn: (t: Transaction) => Promise): Promise; +} diff --git a/packages/stdext-sql/src/errors.ts b/packages/stdext-sql/src/errors.ts new file mode 100644 index 0000000..72a95c6 --- /dev/null +++ b/packages/stdext-sql/src/errors.ts @@ -0,0 +1,11 @@ +/** + * SqlError + * + * Base SQLx Error + */ +export class SqlError extends Error { + constructor(message: string) { + super(message); + this.name = this.constructor.name; + } +} diff --git a/packages/stdext-sql/src/events.ts b/packages/stdext-sql/src/events.ts new file mode 100644 index 0000000..24e2f8c --- /dev/null +++ b/packages/stdext-sql/src/events.ts @@ -0,0 +1,187 @@ +/** + * Events + */ +import type { SqlConnection, SqlConnectionOptions } from './connection.js'; +import type { SqlConnectable } from './connection.js'; + +/** + * Event types + */ + +/** + * SQLx Client event types + */ +export type SqlClientEventType = 'connect' | 'close' | 'error'; + +/** + * SQLx Pool Connection event types + */ +export type SqlPoolConnectionEventType = + | SqlClientEventType + | 'acquire' + | 'release'; + +/** + * EventInits + */ + +/** + * SqlErrorEventInit + */ +export interface SqlErrorEventInit< + Connectable extends SqlConnectable = SqlConnectable +> extends ErrorEventInit { + connectable?: Connectable; +} + +/** + * SqlConnectableEventInit + * + * SQLx Connectable event init + */ +export interface SqlConnectionEventInit< + Connection extends SqlConnection = SqlConnection +> extends Event { + connection: Connection; +} + +/** + * Event classes + */ + +/** + * Base SQLx error event class + */ +export class SqlErrorEvent< + EventInit extends SqlErrorEventInit = SqlErrorEventInit +> extends Event { + constructor(type: 'error', eventInitDict?: EventInit) { + super(type, eventInitDict); + } +} + +/** + * Base SQLx event class + */ +export class SqlEvent< + EventType extends SqlPoolConnectionEventType = SqlPoolConnectionEventType, + EventInit extends SqlConnectionEventInit = SqlConnectionEventInit +> extends Event { + constructor(type: EventType, eventInitDict?: EventInit) { + super(type, eventInitDict); + } +} + +/** + * Gets dispatched when a connection is established + */ +export class SqlConnectEvent< + EventInit extends SqlConnectionEventInit = SqlConnectionEventInit +> extends SqlEvent<'connect', EventInit> { + constructor(eventInitDict: EventInit) { + super('connect', eventInitDict); + } +} + +/** + * Gets dispatched when a connection is about to be closed + */ +export class SqlCloseEvent< + EventInit extends SqlConnectionEventInit = SqlConnectionEventInit +> extends SqlEvent<'close', EventInit> { + constructor(eventInitDict: EventInit) { + super('close', eventInitDict); + } +} + +/** + * Gets dispatched when a connection is acquired from the pool + */ +export class SqlAcquireEvent< + EventInit extends SqlConnectionEventInit = SqlConnectionEventInit +> extends SqlEvent<'acquire', EventInit> { + constructor(eventInitDict: EventInit) { + super('acquire', eventInitDict); + } +} + +/** + * Gets dispatched when a connection is released back to the pool + */ +export class SqlReleaseEvent< + EventInit extends SqlConnectionEventInit = SqlConnectionEventInit +> extends SqlEvent<'release', EventInit> { + constructor(eventInitDict: EventInit) { + super('release', eventInitDict); + } +} + +/** + * Event targets + */ + +/** + * SqlEventTarget + * + * The EventTarget to be used by SQLx + */ +export class SqlEventTarget< + ConnectionOptions extends SqlConnectionOptions = SqlConnectionOptions, + Connection extends + SqlConnection = SqlConnection, + EventType extends SqlPoolConnectionEventType = SqlPoolConnectionEventType, + EventInit extends + SqlConnectionEventInit = SqlConnectionEventInit, + Event extends SqlEvent = SqlEvent, + Listener extends + EventListenerOrEventListenerObject = EventListenerOrEventListenerObject, + ListenerOptions extends AddEventListenerOptions = AddEventListenerOptions, + RemoveListenerOptions extends EventListenerOptions = EventListenerOptions +> extends EventTarget { + /** + * With typed events. + * + * @inheritdoc + */ + addEventListener( + type: EventType, + listener: Listener | null, + options?: boolean | ListenerOptions + ): void { + return super.addEventListener(type, listener, options); + } + + /** + * With typed events. + * + * @inheritdoc + */ + dispatchEvent(event: Event): boolean { + return super.dispatchEvent(event); + } + + /** + * With typed events. + * + * @inheritdoc + */ + removeEventListener( + type: EventType, + callback: Listener | null, + options?: boolean | RemoveListenerOptions + ): void { + return super.removeEventListener(type, callback, options); + } +} + +/** + * SqlEventable + */ +export interface SqlEventable< + EventTarget extends SqlEventTarget = SqlEventTarget +> { + /** + * The EventTarget to reduce inheritance + */ + eventTarget: EventTarget; +} diff --git a/packages/stdext-sql/src/mod.ts b/packages/stdext-sql/src/mod.ts new file mode 100644 index 0000000..69781da --- /dev/null +++ b/packages/stdext-sql/src/mod.ts @@ -0,0 +1,7 @@ +export * from './asserts.js'; +export * from './client.js'; +export * from './connection.js'; +export * from './core.js'; +export * from './errors.js'; +export * from './events.js'; +export * from './pool.js'; diff --git a/packages/stdext-sql/src/pool.ts b/packages/stdext-sql/src/pool.ts new file mode 100644 index 0000000..cdd4cc5 --- /dev/null +++ b/packages/stdext-sql/src/pool.ts @@ -0,0 +1,255 @@ +import type { SqlConnection, SqlConnectionOptions } from './connection.js'; +import type { + SqlPreparedStatement, + SqlQueryOptions, + SqlTransaction, + SqlTransactionable, + SqlTransactionOptions +} from './core.js'; +import type { SqlEventable, SqlEventTarget } from './events.js'; + +/** + * SqlPoolClientOptions + * + * This represents the options for a pool client. + */ +export interface SqlPoolClientOptions { + /** + * The function to call when releasing the connection. + */ + releaseFn?: () => Promise; +} + +/** + * SqlClientPoolOptions + * + * This represents the options for a connection pool. + */ +export interface SqlClientPoolOptions extends SqlConnectionOptions { + /** + * Whether to lazily initialize connections. + * + * This means that connections will only be created + * if there are no idle connections available when + * acquiring a connection, and max pool size has not been reached. + */ + lazyInitialization?: boolean; + /** + * The maximum stack size to be allowed. + */ + maxSize?: number; +} + +/** + * SqlPoolClient + * + * This represents a connection to a database from a pool. + * When a user wants to use a connection from a pool, + * they should use a class implementing this interface. + */ +export interface SqlPoolClient< + ConnectionOptions extends SqlConnectionOptions = SqlConnectionOptions, + Connection extends + SqlConnection = SqlConnection, + ParameterType extends unknown = unknown, + QueryOptions extends SqlQueryOptions = SqlQueryOptions, + Prepared extends SqlPreparedStatement< + ConnectionOptions, + ParameterType, + QueryOptions, + Connection + > = SqlPreparedStatement< + ConnectionOptions, + ParameterType, + QueryOptions, + Connection + >, + TransactionOptions extends SqlTransactionOptions = SqlTransactionOptions, + Transaction extends SqlTransaction< + ConnectionOptions, + ParameterType, + QueryOptions, + Connection, + Prepared, + TransactionOptions + > = SqlTransaction< + ConnectionOptions, + ParameterType, + QueryOptions, + Connection, + Prepared, + TransactionOptions + >, + PoolClientOptions extends SqlPoolClientOptions = SqlPoolClientOptions +> extends SqlTransactionable< + ConnectionOptions, + ParameterType, + QueryOptions, + Connection, + Prepared, + TransactionOptions, + Transaction + > { + /** + * The options used to create the pool client + */ + readonly options: ConnectionOptions & QueryOptions & PoolClientOptions; + /** + * Whether the pool client is disposed and should not be available anymore + */ + disposed: boolean; + /** + * Release the connection to the pool + */ + release(): Promise; +} + +/** + * SqlClientPool + * + * This represents a pool of connections to a database. + * When a user wants to use a pool of connections to the database, + * they should use a class implementing this interface. + */ +export interface SqlClientPool< + ConnectionOptions extends SqlConnectionOptions = SqlConnectionOptions, + ParameterType extends unknown = unknown, + QueryOptions extends SqlQueryOptions = SqlQueryOptions, + Connection extends SqlConnection< + ConnectionOptions, + ParameterType, + QueryOptions + > = SqlConnection, + Prepared extends SqlPreparedStatement< + ConnectionOptions, + ParameterType, + QueryOptions, + Connection + > = SqlPreparedStatement< + ConnectionOptions, + ParameterType, + QueryOptions, + Connection + >, + TransactionOptions extends SqlTransactionOptions = SqlTransactionOptions, + Transaction extends SqlTransaction< + ConnectionOptions, + ParameterType, + QueryOptions, + Connection, + Prepared, + TransactionOptions + > = SqlTransaction< + ConnectionOptions, + ParameterType, + QueryOptions, + Connection, + Prepared, + TransactionOptions + >, + PoolClient extends SqlPoolClient< + ConnectionOptions, + Connection, + ParameterType, + QueryOptions, + Prepared, + TransactionOptions, + Transaction + > = SqlPoolClient< + ConnectionOptions, + Connection, + ParameterType, + QueryOptions, + Prepared, + TransactionOptions, + Transaction + >, + EventTarget extends SqlEventTarget = SqlEventTarget +> extends SqlEventable, + Omit< + SqlConnection, + 'execute' | 'queryMany' | 'queryManyArray' + > { + readonly options: ConnectionOptions & QueryOptions & SqlClientPoolOptions; + + /** + * Acquire a connection from the pool + */ + acquire(): Promise; +} + +/** + * SqlClientPoolConstructor + * + * The constructor for the SqlClientPool interface. + */ +export interface SqlClientPoolConstructor< + ConnectionOptions extends SqlConnectionOptions = SqlConnectionOptions, + ParameterType extends unknown = unknown, + QueryOptions extends SqlQueryOptions = SqlQueryOptions, + Connection extends SqlConnection< + ConnectionOptions, + ParameterType, + QueryOptions + > = SqlConnection, + PreparedStatement extends SqlPreparedStatement< + ConnectionOptions, + ParameterType, + QueryOptions, + Connection + > = SqlPreparedStatement< + ConnectionOptions, + ParameterType, + QueryOptions, + Connection + >, + TransactionOptions extends SqlTransactionOptions = SqlTransactionOptions, + Transaction extends SqlTransaction< + ConnectionOptions, + ParameterType, + QueryOptions, + Connection, + PreparedStatement, + TransactionOptions + > = SqlTransaction< + ConnectionOptions, + ParameterType, + QueryOptions, + Connection, + PreparedStatement, + TransactionOptions + >, + PoolClient extends SqlPoolClient< + ConnectionOptions, + Connection, + ParameterType, + QueryOptions, + PreparedStatement, + TransactionOptions, + Transaction + > = SqlPoolClient< + ConnectionOptions, + Connection, + ParameterType, + QueryOptions, + PreparedStatement, + TransactionOptions, + Transaction + >, + EventTarget extends SqlEventTarget = SqlEventTarget +> { + new ( + connectionUrl: string | URL, + options?: ConnectionOptions & QueryOptions + ): SqlClientPool< + ConnectionOptions, + ParameterType, + QueryOptions, + Connection, + PreparedStatement, + TransactionOptions, + Transaction, + PoolClient, + EventTarget + >; +} diff --git a/packages/stdext-sql/src/std-assert.ts b/packages/stdext-sql/src/std-assert.ts new file mode 100644 index 0000000..a4b95ca --- /dev/null +++ b/packages/stdext-sql/src/std-assert.ts @@ -0,0 +1,117 @@ +// Copyright 2018-2024 the Deno authors. All rights reserved. MIT license. +// This module is browser compatible. + +/** + * Error thrown when an assertion fails. + * + * @example Usage + * ```ts no-eval + * import { AssertionError } from "@std/assert"; + * + * try { + * throw new AssertionError("foo", { cause: "bar" }); + * } catch (error) { + * if (error instanceof AssertionError) { + * error.message === "foo"; // true + * error.cause === "bar"; // true + * } + * } + * ``` + */ +export class AssertionError extends Error { + /** Constructs a new instance. + * + * @param message The error message. + * @param options Additional options. This argument is still unstable. It may change in the future release. + */ + constructor(message: string, options?: ErrorOptions) { + super(message, options); + this.name = 'AssertionError'; + } +} + +/** + * Make an assertion that actual is not null or undefined. + * If not then throw. + * + * @example Usage + * ```ts no-eval + * import { assertExists } from "@std/assert"; + * + * assertExists("something"); // Doesn't throw + * assertExists(undefined); // Throws + * ``` + * + * @typeParam T The type of the actual value. + * @param actual The actual value to check. + * @param msg The optional message to include in the error if the assertion fails. + */ +export function assertExists( + actual: T, + msg?: string +): asserts actual is NonNullable { + if (actual === undefined || actual === null) { + const msgSuffix = msg ? `: ${msg}` : '.'; + msg = `Expected actual: "${actual}" to not be null or undefined${msgSuffix}`; + throw new AssertionError(msg); + } +} + +/** Any constructor */ +// deno-lint-ignore no-explicit-any +export type AnyConstructor = new (...args: any[]) => any; +/** Gets constructor type */ +export type GetConstructorType = T extends new ( // deno-lint-ignore no-explicit-any + ...args: any +) => infer C + ? C + : never; + +/** + * Make an assertion that `obj` is an instance of `type`. + * If not then throw. + * + * @example Usage + * ```ts no-eval + * import { assertInstanceOf } from "@std/assert"; + * + * assertInstanceOf(new Date(), Date); // Doesn't throw + * assertInstanceOf(new Date(), Number); // Throws + * ``` + * + * @typeParam T The expected type of the object. + * @param actual The object to check. + * @param expectedType The expected class constructor. + * @param msg The optional message to display if the assertion fails. + */ +export function assertInstanceOf( + actual: unknown, + expectedType: T, + msg = '' +): asserts actual is GetConstructorType { + if (actual instanceof expectedType) return; + + const msgSuffix = msg ? `: ${msg}` : '.'; + const expectedTypeStr = expectedType.name; + + let actualTypeStr = ''; + if (actual === null) { + actualTypeStr = 'null'; + } else if (actual === undefined) { + actualTypeStr = 'undefined'; + } else if (typeof actual === 'object') { + actualTypeStr = actual.constructor?.name ?? 'Object'; + } else { + actualTypeStr = typeof actual; + } + + if (expectedTypeStr === actualTypeStr) { + msg = `Expected object to be an instance of "${expectedTypeStr}"${msgSuffix}`; + } else if (actualTypeStr === 'function') { + msg = `Expected object to be an instance of "${expectedTypeStr}" but was not an instanced object${msgSuffix}`; + } else { + msg = `Expected object to be an instance of "${expectedTypeStr}" but was "${actualTypeStr}"${msgSuffix}`; + } + + throw new AssertionError(msg); +} diff --git a/packages/stdext-sql/tsconfig.json b/packages/stdext-sql/tsconfig.json new file mode 100644 index 0000000..1b3ce41 --- /dev/null +++ b/packages/stdext-sql/tsconfig.json @@ -0,0 +1,10 @@ +{ + "extends": "../../tsconfig.base.json", + "compilerOptions": { + "lib": ["ES2022", "DOM"], + "outDir": "lib", + "rootDir": "src" + }, + "include": ["src"], + "references": [] +} diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 40e2e3d..19ea753 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -16,7 +16,7 @@ importers: version: 10.0.7 '@types/node': specifier: ^20.14.2 - version: 20.14.15 + version: 20.16.2 expect: specifier: ^29.7.0 version: 29.7.0 @@ -28,16 +28,16 @@ importers: version: 3.3.3 ts-node: specifier: ^10.9.2 - version: 10.9.2(@types/node@20.14.15)(typescript@5.5.4) + version: 10.9.2(@types/node@20.16.2)(typescript@5.5.4) tsx: specifier: ^4.16.2 - version: 4.17.0 + version: 4.19.0 typescript: specifier: ^5.4.5 version: 5.5.4 vitest: specifier: ^1.5.0 - version: 1.6.0(@types/node@20.14.15) + version: 1.6.0(@types/node@20.16.2) benchmarks: dependencies: @@ -52,7 +52,7 @@ importers: version: link:../packages/driver better-sqlite3: specifier: ^11.0.0 - version: 11.1.2 + version: 11.2.1 prando: specifier: ^6.0.1 version: 6.0.1 @@ -65,7 +65,7 @@ importers: devDependencies: '@types/node': specifier: ^20.14.2 - version: 20.14.15 + version: 20.16.2 typescript: specifier: ^5.4.5 version: 5.5.4 @@ -84,13 +84,13 @@ importers: version: link:../driver-tests '@types/node': specifier: ^22.3.0 - version: 22.3.0 + version: 22.5.1 typescript: specifier: ^5.5.4 version: 5.5.4 vitest: specifier: ^2.0.5 - version: 2.0.5(@types/node@22.3.0) + version: 2.0.5(@types/node@22.5.1) packages/better-sqlite3-driver: dependencies: @@ -99,26 +99,26 @@ importers: version: link:../driver better-sqlite3: specifier: ^11.1.2 - version: 11.1.2 + version: 11.2.1 devDependencies: '@sqlite-js/driver-tests': specifier: workspace:^ version: link:../driver-tests '@types/node': specifier: ^22.3.0 - version: 22.3.0 + version: 22.5.1 typescript: specifier: ^5.5.4 version: 5.5.4 vitest: specifier: ^2.0.5 - version: 2.0.5(@types/node@22.3.0) + version: 2.0.5(@types/node@22.5.1) packages/driver: devDependencies: '@types/node': specifier: ^22.3.0 - version: 22.3.0 + version: 22.5.1 typescript: specifier: ^5.5.4 version: 5.5.4 @@ -133,15 +133,43 @@ importers: version: 10.7.3 vitest: specifier: ^2.0.5 - version: 2.0.5(@types/node@22.3.0) + version: 2.0.5(@types/node@22.5.1) devDependencies: '@types/node': specifier: ^22.3.0 - version: 22.3.0 + version: 22.5.1 typescript: specifier: ^5.5.4 version: 5.5.4 + packages/stdext-sql: + devDependencies: + typescript: + specifier: ^5.5.4 + version: 5.5.4 + + packages/stdext-sql-adapter: + dependencies: + '@sqlite-js/better-sqlite3-driver': + specifier: workspace:^ + version: link:../better-sqlite3-driver + '@sqlite-js/driver': + specifier: workspace:^ + version: link:../driver + '@stdext/sql': + specifier: workspace:^ + version: link:../stdext-sql + '@types/node': + specifier: ^22.5.1 + version: 22.5.1 + devDependencies: + typescript: + specifier: ^5.5.4 + version: 5.5.4 + vitest: + specifier: ^2.0.5 + version: 2.0.5(@types/node@22.5.1) + packages: '@ampproject/remapping@2.3.0': @@ -170,8 +198,8 @@ packages: cpu: [ppc64] os: [aix] - '@esbuild/aix-ppc64@0.23.0': - resolution: {integrity: sha512-3sG8Zwa5fMcA9bgqB8AfWPQ+HFke6uD3h1s3RIwUNK8EG7a4buxvuFTs3j1IMs2NXAk9F30C/FF4vxRgQCcmoQ==} + '@esbuild/aix-ppc64@0.23.1': + resolution: {integrity: sha512-6VhYk1diRqrhBAqpJEdjASR/+WVRtfjpqKuNw11cLiaWpAT/Uu+nokB+UJnevzy/P9C/ty6AOe0dwueMrGh/iQ==} engines: {node: '>=18'} cpu: [ppc64] os: [aix] @@ -182,8 +210,8 @@ packages: cpu: [arm64] os: [android] - '@esbuild/android-arm64@0.23.0': - resolution: {integrity: sha512-EuHFUYkAVfU4qBdyivULuu03FhJO4IJN9PGuABGrFy4vUuzk91P2d+npxHcFdpUnfYKy0PuV+n6bKIpHOB3prQ==} + '@esbuild/android-arm64@0.23.1': + resolution: {integrity: sha512-xw50ipykXcLstLeWH7WRdQuysJqejuAGPd30vd1i5zSyKK3WE+ijzHmLKxdiCMtH1pHz78rOg0BKSYOSB/2Khw==} engines: {node: '>=18'} cpu: [arm64] os: [android] @@ -194,8 +222,8 @@ packages: cpu: [arm] os: [android] - '@esbuild/android-arm@0.23.0': - resolution: {integrity: sha512-+KuOHTKKyIKgEEqKbGTK8W7mPp+hKinbMBeEnNzjJGyFcWsfrXjSTNluJHCY1RqhxFurdD8uNXQDei7qDlR6+g==} + '@esbuild/android-arm@0.23.1': + resolution: {integrity: sha512-uz6/tEy2IFm9RYOyvKl88zdzZfwEfKZmnX9Cj1BHjeSGNuGLuMD1kR8y5bteYmwqKm1tj8m4cb/aKEorr6fHWQ==} engines: {node: '>=18'} cpu: [arm] os: [android] @@ -206,8 +234,8 @@ packages: cpu: [x64] os: [android] - '@esbuild/android-x64@0.23.0': - resolution: {integrity: sha512-WRrmKidLoKDl56LsbBMhzTTBxrsVwTKdNbKDalbEZr0tcsBgCLbEtoNthOW6PX942YiYq8HzEnb4yWQMLQuipQ==} + '@esbuild/android-x64@0.23.1': + resolution: {integrity: sha512-nlN9B69St9BwUoB+jkyU090bru8L0NA3yFvAd7k8dNsVH8bi9a8cUAUSEcEEgTp2z3dbEDGJGfP6VUnkQnlReg==} engines: {node: '>=18'} cpu: [x64] os: [android] @@ -218,8 +246,8 @@ packages: cpu: [arm64] os: [darwin] - '@esbuild/darwin-arm64@0.23.0': - resolution: {integrity: sha512-YLntie/IdS31H54Ogdn+v50NuoWF5BDkEUFpiOChVa9UnKpftgwzZRrI4J132ETIi+D8n6xh9IviFV3eXdxfow==} + '@esbuild/darwin-arm64@0.23.1': + resolution: {integrity: sha512-YsS2e3Wtgnw7Wq53XXBLcV6JhRsEq8hkfg91ESVadIrzr9wO6jJDMZnCQbHm1Guc5t/CdDiFSSfWP58FNuvT3Q==} engines: {node: '>=18'} cpu: [arm64] os: [darwin] @@ -230,8 +258,8 @@ packages: cpu: [x64] os: [darwin] - '@esbuild/darwin-x64@0.23.0': - resolution: {integrity: sha512-IMQ6eme4AfznElesHUPDZ+teuGwoRmVuuixu7sv92ZkdQcPbsNHzutd+rAfaBKo8YK3IrBEi9SLLKWJdEvJniQ==} + '@esbuild/darwin-x64@0.23.1': + resolution: {integrity: sha512-aClqdgTDVPSEGgoCS8QDG37Gu8yc9lTHNAQlsztQ6ENetKEO//b8y31MMu2ZaPbn4kVsIABzVLXYLhCGekGDqw==} engines: {node: '>=18'} cpu: [x64] os: [darwin] @@ -242,8 +270,8 @@ packages: cpu: [arm64] os: [freebsd] - '@esbuild/freebsd-arm64@0.23.0': - resolution: {integrity: sha512-0muYWCng5vqaxobq6LB3YNtevDFSAZGlgtLoAc81PjUfiFz36n4KMpwhtAd4he8ToSI3TGyuhyx5xmiWNYZFyw==} + '@esbuild/freebsd-arm64@0.23.1': + resolution: {integrity: sha512-h1k6yS8/pN/NHlMl5+v4XPfikhJulk4G+tKGFIOwURBSFzE8bixw1ebjluLOjfwtLqY0kewfjLSrO6tN2MgIhA==} engines: {node: '>=18'} cpu: [arm64] os: [freebsd] @@ -254,8 +282,8 @@ packages: cpu: [x64] os: [freebsd] - '@esbuild/freebsd-x64@0.23.0': - resolution: {integrity: sha512-XKDVu8IsD0/q3foBzsXGt/KjD/yTKBCIwOHE1XwiXmrRwrX6Hbnd5Eqn/WvDekddK21tfszBSrE/WMaZh+1buQ==} + '@esbuild/freebsd-x64@0.23.1': + resolution: {integrity: sha512-lK1eJeyk1ZX8UklqFd/3A60UuZ/6UVfGT2LuGo3Wp4/z7eRTRYY+0xOu2kpClP+vMTi9wKOfXi2vjUpO1Ro76g==} engines: {node: '>=18'} cpu: [x64] os: [freebsd] @@ -266,8 +294,8 @@ packages: cpu: [arm64] os: [linux] - '@esbuild/linux-arm64@0.23.0': - resolution: {integrity: sha512-j1t5iG8jE7BhonbsEg5d9qOYcVZv/Rv6tghaXM/Ug9xahM0nX/H2gfu6X6z11QRTMT6+aywOMA8TDkhPo8aCGw==} + '@esbuild/linux-arm64@0.23.1': + resolution: {integrity: sha512-/93bf2yxencYDnItMYV/v116zff6UyTjo4EtEQjUBeGiVpMmffDNUyD9UN2zV+V3LRV3/on4xdZ26NKzn6754g==} engines: {node: '>=18'} cpu: [arm64] os: [linux] @@ -278,8 +306,8 @@ packages: cpu: [arm] os: [linux] - '@esbuild/linux-arm@0.23.0': - resolution: {integrity: sha512-SEELSTEtOFu5LPykzA395Mc+54RMg1EUgXP+iw2SJ72+ooMwVsgfuwXo5Fn0wXNgWZsTVHwY2cg4Vi/bOD88qw==} + '@esbuild/linux-arm@0.23.1': + resolution: {integrity: sha512-CXXkzgn+dXAPs3WBwE+Kvnrf4WECwBdfjfeYHpMeVxWE0EceB6vhWGShs6wi0IYEqMSIzdOF1XjQ/Mkm5d7ZdQ==} engines: {node: '>=18'} cpu: [arm] os: [linux] @@ -290,8 +318,8 @@ packages: cpu: [ia32] os: [linux] - '@esbuild/linux-ia32@0.23.0': - resolution: {integrity: sha512-P7O5Tkh2NbgIm2R6x1zGJJsnacDzTFcRWZyTTMgFdVit6E98LTxO+v8LCCLWRvPrjdzXHx9FEOA8oAZPyApWUA==} + '@esbuild/linux-ia32@0.23.1': + resolution: {integrity: sha512-VTN4EuOHwXEkXzX5nTvVY4s7E/Krz7COC8xkftbbKRYAl96vPiUssGkeMELQMOnLOJ8k3BY1+ZY52tttZnHcXQ==} engines: {node: '>=18'} cpu: [ia32] os: [linux] @@ -302,8 +330,8 @@ packages: cpu: [loong64] os: [linux] - '@esbuild/linux-loong64@0.23.0': - resolution: {integrity: sha512-InQwepswq6urikQiIC/kkx412fqUZudBO4SYKu0N+tGhXRWUqAx+Q+341tFV6QdBifpjYgUndV1hhMq3WeJi7A==} + '@esbuild/linux-loong64@0.23.1': + resolution: {integrity: sha512-Vx09LzEoBa5zDnieH8LSMRToj7ir/Jeq0Gu6qJ/1GcBq9GkfoEAoXvLiW1U9J1qE/Y/Oyaq33w5p2ZWrNNHNEw==} engines: {node: '>=18'} cpu: [loong64] os: [linux] @@ -314,8 +342,8 @@ packages: cpu: [mips64el] os: [linux] - '@esbuild/linux-mips64el@0.23.0': - resolution: {integrity: sha512-J9rflLtqdYrxHv2FqXE2i1ELgNjT+JFURt/uDMoPQLcjWQA5wDKgQA4t/dTqGa88ZVECKaD0TctwsUfHbVoi4w==} + '@esbuild/linux-mips64el@0.23.1': + resolution: {integrity: sha512-nrFzzMQ7W4WRLNUOU5dlWAqa6yVeI0P78WKGUo7lg2HShq/yx+UYkeNSE0SSfSure0SqgnsxPvmAUu/vu0E+3Q==} engines: {node: '>=18'} cpu: [mips64el] os: [linux] @@ -326,8 +354,8 @@ packages: cpu: [ppc64] os: [linux] - '@esbuild/linux-ppc64@0.23.0': - resolution: {integrity: sha512-cShCXtEOVc5GxU0fM+dsFD10qZ5UpcQ8AM22bYj0u/yaAykWnqXJDpd77ublcX6vdDsWLuweeuSNZk4yUxZwtw==} + '@esbuild/linux-ppc64@0.23.1': + resolution: {integrity: sha512-dKN8fgVqd0vUIjxuJI6P/9SSSe/mB9rvA98CSH2sJnlZ/OCZWO1DJvxj8jvKTfYUdGfcq2dDxoKaC6bHuTlgcw==} engines: {node: '>=18'} cpu: [ppc64] os: [linux] @@ -338,8 +366,8 @@ packages: cpu: [riscv64] os: [linux] - '@esbuild/linux-riscv64@0.23.0': - resolution: {integrity: sha512-HEtaN7Y5UB4tZPeQmgz/UhzoEyYftbMXrBCUjINGjh3uil+rB/QzzpMshz3cNUxqXN7Vr93zzVtpIDL99t9aRw==} + '@esbuild/linux-riscv64@0.23.1': + resolution: {integrity: sha512-5AV4Pzp80fhHL83JM6LoA6pTQVWgB1HovMBsLQ9OZWLDqVY8MVobBXNSmAJi//Csh6tcY7e7Lny2Hg1tElMjIA==} engines: {node: '>=18'} cpu: [riscv64] os: [linux] @@ -350,8 +378,8 @@ packages: cpu: [s390x] os: [linux] - '@esbuild/linux-s390x@0.23.0': - resolution: {integrity: sha512-WDi3+NVAuyjg/Wxi+o5KPqRbZY0QhI9TjrEEm+8dmpY9Xir8+HE/HNx2JoLckhKbFopW0RdO2D72w8trZOV+Wg==} + '@esbuild/linux-s390x@0.23.1': + resolution: {integrity: sha512-9ygs73tuFCe6f6m/Tb+9LtYxWR4c9yg7zjt2cYkjDbDpV/xVn+68cQxMXCjUpYwEkze2RcU/rMnfIXNRFmSoDw==} engines: {node: '>=18'} cpu: [s390x] os: [linux] @@ -362,8 +390,8 @@ packages: cpu: [x64] os: [linux] - '@esbuild/linux-x64@0.23.0': - resolution: {integrity: sha512-a3pMQhUEJkITgAw6e0bWA+F+vFtCciMjW/LPtoj99MhVt+Mfb6bbL9hu2wmTZgNd994qTAEw+U/r6k3qHWWaOQ==} + '@esbuild/linux-x64@0.23.1': + resolution: {integrity: sha512-EV6+ovTsEXCPAp58g2dD68LxoP/wK5pRvgy0J/HxPGB009omFPv3Yet0HiaqvrIrgPTBuC6wCH1LTOY91EO5hQ==} engines: {node: '>=18'} cpu: [x64] os: [linux] @@ -374,14 +402,14 @@ packages: cpu: [x64] os: [netbsd] - '@esbuild/netbsd-x64@0.23.0': - resolution: {integrity: sha512-cRK+YDem7lFTs2Q5nEv/HHc4LnrfBCbH5+JHu6wm2eP+d8OZNoSMYgPZJq78vqQ9g+9+nMuIsAO7skzphRXHyw==} + '@esbuild/netbsd-x64@0.23.1': + resolution: {integrity: sha512-aevEkCNu7KlPRpYLjwmdcuNz6bDFiE7Z8XC4CPqExjTvrHugh28QzUXVOZtiYghciKUacNktqxdpymplil1beA==} engines: {node: '>=18'} cpu: [x64] os: [netbsd] - '@esbuild/openbsd-arm64@0.23.0': - resolution: {integrity: sha512-suXjq53gERueVWu0OKxzWqk7NxiUWSUlrxoZK7usiF50C6ipColGR5qie2496iKGYNLhDZkPxBI3erbnYkU0rQ==} + '@esbuild/openbsd-arm64@0.23.1': + resolution: {integrity: sha512-3x37szhLexNA4bXhLrCC/LImN/YtWis6WXr1VESlfVtVeoFJBRINPJ3f0a/6LV8zpikqoUg4hyXw0sFBt5Cr+Q==} engines: {node: '>=18'} cpu: [arm64] os: [openbsd] @@ -392,8 +420,8 @@ packages: cpu: [x64] os: [openbsd] - '@esbuild/openbsd-x64@0.23.0': - resolution: {integrity: sha512-6p3nHpby0DM/v15IFKMjAaayFhqnXV52aEmv1whZHX56pdkK+MEaLoQWj+H42ssFarP1PcomVhbsR4pkz09qBg==} + '@esbuild/openbsd-x64@0.23.1': + resolution: {integrity: sha512-aY2gMmKmPhxfU+0EdnN+XNtGbjfQgwZj43k8G3fyrDM/UdZww6xrWxmDkuz2eCZchqVeABjV5BpildOrUbBTqA==} engines: {node: '>=18'} cpu: [x64] os: [openbsd] @@ -404,8 +432,8 @@ packages: cpu: [x64] os: [sunos] - '@esbuild/sunos-x64@0.23.0': - resolution: {integrity: sha512-BFelBGfrBwk6LVrmFzCq1u1dZbG4zy/Kp93w2+y83Q5UGYF1d8sCzeLI9NXjKyujjBBniQa8R8PzLFAUrSM9OA==} + '@esbuild/sunos-x64@0.23.1': + resolution: {integrity: sha512-RBRT2gqEl0IKQABT4XTj78tpk9v7ehp+mazn2HbUeZl1YMdaGAQqhapjGTCe7uw7y0frDi4gS0uHzhvpFuI1sA==} engines: {node: '>=18'} cpu: [x64] os: [sunos] @@ -416,8 +444,8 @@ packages: cpu: [arm64] os: [win32] - '@esbuild/win32-arm64@0.23.0': - resolution: {integrity: sha512-lY6AC8p4Cnb7xYHuIxQ6iYPe6MfO2CC43XXKo9nBXDb35krYt7KGhQnOkRGar5psxYkircpCqfbNDB4uJbS2jQ==} + '@esbuild/win32-arm64@0.23.1': + resolution: {integrity: sha512-4O+gPR5rEBe2FpKOVyiJ7wNDPA8nGzDuJ6gN4okSA1gEOYZ67N8JPk58tkWtdtPeLz7lBnY6I5L3jdsr3S+A6A==} engines: {node: '>=18'} cpu: [arm64] os: [win32] @@ -428,8 +456,8 @@ packages: cpu: [ia32] os: [win32] - '@esbuild/win32-ia32@0.23.0': - resolution: {integrity: sha512-7L1bHlOTcO4ByvI7OXVI5pNN6HSu6pUQq9yodga8izeuB1KcT2UkHaH6118QJwopExPn0rMHIseCTx1CRo/uNA==} + '@esbuild/win32-ia32@0.23.1': + resolution: {integrity: sha512-BcaL0Vn6QwCwre3Y717nVHZbAa4UBEigzFm6VdsVdT/MbZ38xoj1X9HPkZhbmaBGUD1W8vxAfffbDe8bA6AKnQ==} engines: {node: '>=18'} cpu: [ia32] os: [win32] @@ -440,8 +468,8 @@ packages: cpu: [x64] os: [win32] - '@esbuild/win32-x64@0.23.0': - resolution: {integrity: sha512-Arm+WgUFLUATuoxCJcahGuk6Yj9Pzxd6l11Zb/2aAuv5kWWvvfhLFo2fni4uSK5vzlUdCGZ/BdV5tH8klj8p8g==} + '@esbuild/win32-x64@0.23.1': + resolution: {integrity: sha512-BHpFFeslkWrXWyUPnbKm+xYYVYruCinGcftSBaa8zoF9hZO4BcSCFUvHVTtzpIY6YzUnYtuEhZ+C9iEXjxnasg==} engines: {node: '>=18'} cpu: [x64] os: [win32] @@ -490,83 +518,83 @@ packages: engines: {node: '>=10'} deprecated: This functionality has been moved to @npmcli/fs - '@rollup/rollup-android-arm-eabi@4.20.0': - resolution: {integrity: sha512-TSpWzflCc4VGAUJZlPpgAJE1+V60MePDQnBd7PPkpuEmOy8i87aL6tinFGKBFKuEDikYpig72QzdT3QPYIi+oA==} + '@rollup/rollup-android-arm-eabi@4.21.1': + resolution: {integrity: sha512-2thheikVEuU7ZxFXubPDOtspKn1x0yqaYQwvALVtEcvFhMifPADBrgRPyHV0TF3b+9BgvgjgagVyvA/UqPZHmg==} cpu: [arm] os: [android] - '@rollup/rollup-android-arm64@4.20.0': - resolution: {integrity: sha512-u00Ro/nok7oGzVuh/FMYfNoGqxU5CPWz1mxV85S2w9LxHR8OoMQBuSk+3BKVIDYgkpeOET5yXkx90OYFc+ytpQ==} + '@rollup/rollup-android-arm64@4.21.1': + resolution: {integrity: sha512-t1lLYn4V9WgnIFHXy1d2Di/7gyzBWS8G5pQSXdZqfrdCGTwi1VasRMSS81DTYb+avDs/Zz4A6dzERki5oRYz1g==} cpu: [arm64] os: [android] - '@rollup/rollup-darwin-arm64@4.20.0': - resolution: {integrity: sha512-uFVfvzvsdGtlSLuL0ZlvPJvl6ZmrH4CBwLGEFPe7hUmf7htGAN+aXo43R/V6LATyxlKVC/m6UsLb7jbG+LG39Q==} + '@rollup/rollup-darwin-arm64@4.21.1': + resolution: {integrity: sha512-AH/wNWSEEHvs6t4iJ3RANxW5ZCK3fUnmf0gyMxWCesY1AlUj8jY7GC+rQE4wd3gwmZ9XDOpL0kcFnCjtN7FXlA==} cpu: [arm64] os: [darwin] - '@rollup/rollup-darwin-x64@4.20.0': - resolution: {integrity: sha512-xbrMDdlev53vNXexEa6l0LffojxhqDTBeL+VUxuuIXys4x6xyvbKq5XqTXBCEUA8ty8iEJblHvFaWRJTk/icAQ==} + '@rollup/rollup-darwin-x64@4.21.1': + resolution: {integrity: sha512-dO0BIz/+5ZdkLZrVgQrDdW7m2RkrLwYTh2YMFG9IpBtlC1x1NPNSXkfczhZieOlOLEqgXOFH3wYHB7PmBtf+Bg==} cpu: [x64] os: [darwin] - '@rollup/rollup-linux-arm-gnueabihf@4.20.0': - resolution: {integrity: sha512-jMYvxZwGmoHFBTbr12Xc6wOdc2xA5tF5F2q6t7Rcfab68TT0n+r7dgawD4qhPEvasDsVpQi+MgDzj2faOLsZjA==} + '@rollup/rollup-linux-arm-gnueabihf@4.21.1': + resolution: {integrity: sha512-sWWgdQ1fq+XKrlda8PsMCfut8caFwZBmhYeoehJ05FdI0YZXk6ZyUjWLrIgbR/VgiGycrFKMMgp7eJ69HOF2pQ==} cpu: [arm] os: [linux] - '@rollup/rollup-linux-arm-musleabihf@4.20.0': - resolution: {integrity: sha512-1asSTl4HKuIHIB1GcdFHNNZhxAYEdqML/MW4QmPS4G0ivbEcBr1JKlFLKsIRqjSwOBkdItn3/ZDlyvZ/N6KPlw==} + '@rollup/rollup-linux-arm-musleabihf@4.21.1': + resolution: {integrity: sha512-9OIiSuj5EsYQlmwhmFRA0LRO0dRRjdCVZA3hnmZe1rEwRk11Jy3ECGGq3a7RrVEZ0/pCsYWx8jG3IvcrJ6RCew==} cpu: [arm] os: [linux] - '@rollup/rollup-linux-arm64-gnu@4.20.0': - resolution: {integrity: sha512-COBb8Bkx56KldOYJfMf6wKeYJrtJ9vEgBRAOkfw6Ens0tnmzPqvlpjZiLgkhg6cA3DGzCmLmmd319pmHvKWWlQ==} + '@rollup/rollup-linux-arm64-gnu@4.21.1': + resolution: {integrity: sha512-0kuAkRK4MeIUbzQYu63NrJmfoUVicajoRAL1bpwdYIYRcs57iyIV9NLcuyDyDXE2GiZCL4uhKSYAnyWpjZkWow==} cpu: [arm64] os: [linux] - '@rollup/rollup-linux-arm64-musl@4.20.0': - resolution: {integrity: sha512-+it+mBSyMslVQa8wSPvBx53fYuZK/oLTu5RJoXogjk6x7Q7sz1GNRsXWjn6SwyJm8E/oMjNVwPhmNdIjwP135Q==} + '@rollup/rollup-linux-arm64-musl@4.21.1': + resolution: {integrity: sha512-/6dYC9fZtfEY0vozpc5bx1RP4VrtEOhNQGb0HwvYNwXD1BBbwQ5cKIbUVVU7G2d5WRE90NfB922elN8ASXAJEA==} cpu: [arm64] os: [linux] - '@rollup/rollup-linux-powerpc64le-gnu@4.20.0': - resolution: {integrity: sha512-yAMvqhPfGKsAxHN8I4+jE0CpLWD8cv4z7CK7BMmhjDuz606Q2tFKkWRY8bHR9JQXYcoLfopo5TTqzxgPUjUMfw==} + '@rollup/rollup-linux-powerpc64le-gnu@4.21.1': + resolution: {integrity: sha512-ltUWy+sHeAh3YZ91NUsV4Xg3uBXAlscQe8ZOXRCVAKLsivGuJsrkawYPUEyCV3DYa9urgJugMLn8Z3Z/6CeyRQ==} cpu: [ppc64] os: [linux] - '@rollup/rollup-linux-riscv64-gnu@4.20.0': - resolution: {integrity: sha512-qmuxFpfmi/2SUkAw95TtNq/w/I7Gpjurx609OOOV7U4vhvUhBcftcmXwl3rqAek+ADBwSjIC4IVNLiszoj3dPA==} + '@rollup/rollup-linux-riscv64-gnu@4.21.1': + resolution: {integrity: sha512-BggMndzI7Tlv4/abrgLwa/dxNEMn2gC61DCLrTzw8LkpSKel4o+O+gtjbnkevZ18SKkeN3ihRGPuBxjaetWzWg==} cpu: [riscv64] os: [linux] - '@rollup/rollup-linux-s390x-gnu@4.20.0': - resolution: {integrity: sha512-I0BtGXddHSHjV1mqTNkgUZLnS3WtsqebAXv11D5BZE/gfw5KoyXSAXVqyJximQXNvNzUo4GKlCK/dIwXlz+jlg==} + '@rollup/rollup-linux-s390x-gnu@4.21.1': + resolution: {integrity: sha512-z/9rtlGd/OMv+gb1mNSjElasMf9yXusAxnRDrBaYB+eS1shFm6/4/xDH1SAISO5729fFKUkJ88TkGPRUh8WSAA==} cpu: [s390x] os: [linux] - '@rollup/rollup-linux-x64-gnu@4.20.0': - resolution: {integrity: sha512-y+eoL2I3iphUg9tN9GB6ku1FA8kOfmF4oUEWhztDJ4KXJy1agk/9+pejOuZkNFhRwHAOxMsBPLbXPd6mJiCwew==} + '@rollup/rollup-linux-x64-gnu@4.21.1': + resolution: {integrity: sha512-kXQVcWqDcDKw0S2E0TmhlTLlUgAmMVqPrJZR+KpH/1ZaZhLSl23GZpQVmawBQGVhyP5WXIsIQ/zqbDBBYmxm5w==} cpu: [x64] os: [linux] - '@rollup/rollup-linux-x64-musl@4.20.0': - resolution: {integrity: sha512-hM3nhW40kBNYUkZb/r9k2FKK+/MnKglX7UYd4ZUy5DJs8/sMsIbqWK2piZtVGE3kcXVNj3B2IrUYROJMMCikNg==} + '@rollup/rollup-linux-x64-musl@4.21.1': + resolution: {integrity: sha512-CbFv/WMQsSdl+bpX6rVbzR4kAjSSBuDgCqb1l4J68UYsQNalz5wOqLGYj4ZI0thGpyX5kc+LLZ9CL+kpqDovZA==} cpu: [x64] os: [linux] - '@rollup/rollup-win32-arm64-msvc@4.20.0': - resolution: {integrity: sha512-psegMvP+Ik/Bg7QRJbv8w8PAytPA7Uo8fpFjXyCRHWm6Nt42L+JtoqH8eDQ5hRP7/XW2UiIriy1Z46jf0Oa1kA==} + '@rollup/rollup-win32-arm64-msvc@4.21.1': + resolution: {integrity: sha512-3Q3brDgA86gHXWHklrwdREKIrIbxC0ZgU8lwpj0eEKGBQH+31uPqr0P2v11pn0tSIxHvcdOWxa4j+YvLNx1i6g==} cpu: [arm64] os: [win32] - '@rollup/rollup-win32-ia32-msvc@4.20.0': - resolution: {integrity: sha512-GabekH3w4lgAJpVxkk7hUzUf2hICSQO0a/BLFA11/RMxQT92MabKAqyubzDZmMOC/hcJNlc+rrypzNzYl4Dx7A==} + '@rollup/rollup-win32-ia32-msvc@4.21.1': + resolution: {integrity: sha512-tNg+jJcKR3Uwe4L0/wY3Ro0H+u3nrb04+tcq1GSYzBEmKLeOQF2emk1whxlzNqb6MMrQ2JOcQEpuuiPLyRcSIw==} cpu: [ia32] os: [win32] - '@rollup/rollup-win32-x64-msvc@4.20.0': - resolution: {integrity: sha512-aJ1EJSuTdGnM6qbVC4B5DSmozPTqIag9fSzXRNNo+humQLG89XpPgdt16Ia56ORD7s+H8Pmyx44uczDQ0yDzpg==} + '@rollup/rollup-win32-x64-msvc@4.21.1': + resolution: {integrity: sha512-xGiIH95H1zU7naUyTKEyOA/I0aexNMUdO9qRv0bLKN3qu25bBdrxZHqA3PTJ24YNN/GdMzG4xkDcd/GvjuhfLg==} cpu: [x64] os: [win32] @@ -607,11 +635,11 @@ packages: '@types/mocha@10.0.7': resolution: {integrity: sha512-GN8yJ1mNTcFcah/wKEFIJckJx9iJLoMSzWcfRRuxz/Jk+U6KQNnml+etbtxFK8lPjzOw3zp4Ha/kjSst9fsHYw==} - '@types/node@20.14.15': - resolution: {integrity: sha512-Fz1xDMCF/B00/tYSVMlmK7hVeLh7jE5f3B7X1/hmV0MJBwE27KlS7EvD/Yp+z1lm8mVhwV5w+n8jOZG8AfTlKw==} + '@types/node@20.16.2': + resolution: {integrity: sha512-91s/n4qUPV/wg8eE9KHYW1kouTfDk2FPGjXbBMfRWP/2vg1rCXNQL1OCabwGs0XSdukuK+MwCDXE30QpSeMUhQ==} - '@types/node@22.3.0': - resolution: {integrity: sha512-nrWpWVaDZuaVc5X84xJ0vNrLvomM205oQyLsRt7OHNZbSHslcWsvgFR7O7hire2ZonjLrWBbedmotmIlJDVd6g==} + '@types/node@22.5.1': + resolution: {integrity: sha512-KkHsxej0j9IW1KKOOAA/XBA0z08UFSrRQHErzEfA3Vgq57eXIMYboIlHJuYIfd+lwCQjtKqUu3UnmKbtUc9yRw==} '@types/stack-utils@2.0.3': resolution: {integrity: sha512-9aEbYZ3TbYMznPdcdr3SmIrLXwC/AKZXQeCf9Pgao5CKb8CyHuEX5jzWPTkvregvhRJHcpRO6BFoGW9ycaOkYw==} @@ -730,8 +758,8 @@ packages: base64-js@1.5.1: resolution: {integrity: sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==} - better-sqlite3@11.1.2: - resolution: {integrity: sha512-gujtFwavWU4MSPT+h9B+4pkvZdyOUkH54zgLdIrMmmmd4ZqiBIrRNBzNzYVFO417xo882uP5HBu4GjOfaSrIQw==} + better-sqlite3@11.2.1: + resolution: {integrity: sha512-Xbt1d68wQnUuFIEVsbt6V+RG30zwgbtCGQ4QOcXVrOH0FE4eHk64FWZ9NUfRHS4/x1PXqwz/+KOrnXD7f0WieA==} binary-extensions@2.3.0: resolution: {integrity: sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw==} @@ -918,8 +946,8 @@ packages: engines: {node: '>=12'} hasBin: true - esbuild@0.23.0: - resolution: {integrity: sha512-1lvV17H2bMYda/WaFb2jLPeHU3zml2k4/yagNMG8Q/YtfMjCwEUZa2eXXMgZTVSL5q1n4H7sQ0X6CdJDqqeCFA==} + esbuild@0.23.1: + resolution: {integrity: sha512-VVNz/9Sa0bs5SELtn3f7qhJCDPCF5oMEl5cO9/SSinpE9hbPVvxbd572HH5AKiP7WD8INO53GgfDDhRjkylHEg==} engines: {node: '>=18'} hasBin: true @@ -1193,8 +1221,8 @@ packages: merge-stream@2.0.0: resolution: {integrity: sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==} - micromatch@4.0.7: - resolution: {integrity: sha512-LPP/3KorzCwBxfeUuZmaR6bG2kdeHSbe0P2tY3FLRU4vYrjYz5hI4QZwV0njUx3jeuKe67YukQ1LSPZBKDqO/Q==} + micromatch@4.0.8: + resolution: {integrity: sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==} engines: {node: '>=8.6'} mimic-fn@4.0.0: @@ -1281,8 +1309,8 @@ packages: resolution: {integrity: sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==} engines: {node: '>= 0.6'} - node-abi@3.65.0: - resolution: {integrity: sha512-ThjYBfoDNr08AWx6hGaRbfPwxKV9kVzAzOzlLKbk2CuqXE2xnCh+cbAGnwM3t8Lq4v9rUB7VfondlkBckcJrVA==} + node-abi@3.67.0: + resolution: {integrity: sha512-bLn/fU/ALVBE9wj+p4Y21ZJWYFjUXLXPi/IewyLZkx3ApxKDNBWCKdReeKOtD8dWpOdDCeMyLh6ZewzcLsG2Nw==} engines: {node: '>=10'} node-addon-api@7.1.1: @@ -1367,8 +1395,8 @@ packages: resolution: {integrity: sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==} engines: {node: '>=8.6'} - pkg-types@1.1.3: - resolution: {integrity: sha512-+JrgthZG6m3ckicaOB74TwQ+tBWsFl3qVQg7mN8ulwSOElJ7gBhKzj2VkCPnZ4NlF6kEquYU+RIYNVAvzd54UA==} + pkg-types@1.2.0: + resolution: {integrity: sha512-+ifYuSSqOQ8CqP4MbZA5hDpb97n3E8SVWdJe+Wms9kj745lmd3b7EZJiqvmLwAlmRfjrI7Hi5z3kdBJ93lFNPA==} postcss@8.4.41: resolution: {integrity: sha512-TesUflQ0WKZqAvg52PWL6kHgLKP6xB6heTOdoYM0Wt2UHyxNa4K25EZZMgKns3BH1RLVbZCREPpLY0rhnNoHVQ==} @@ -1440,8 +1468,8 @@ packages: deprecated: Rimraf versions prior to v4 are no longer supported hasBin: true - rollup@4.20.0: - resolution: {integrity: sha512-6rbWBChcnSGzIlXeIdNIZTopKYad8ZG8ajhl78lGRLsI2rX8IkaotQhVas2Ma+GPxJav19wrSzvRvuiv0YKzWw==} + rollup@4.21.1: + resolution: {integrity: sha512-ZnYyKvscThhgd3M5+Qt3pmhO4jIRR5RGzaSovB6Q7rGNrK5cUncrtLmcTTJVSdcKXyZjW8X8MB0JMSuH9bcAJg==} engines: {node: '>=18.0.0', npm: '>=8.0.0'} hasBin: true @@ -1585,8 +1613,8 @@ packages: resolution: {integrity: sha512-i11VH5gS6IFeLY3gMBQ00/MmLncVP7JLXOw1vlgkytLmJK7QnEr7NXf0LBdxfmNPAeyetukOk0bOYrJrFGjYJQ==} engines: {node: '>=14.0.0'} - tinypool@1.0.0: - resolution: {integrity: sha512-KIKExllK7jp3uvrNtvRBYBWBOAXSX8ZvoaD8T+7KB/QHIuoJW3Pmr60zucywjAlMb5TeXUkcs/MWeWLu0qvuAQ==} + tinypool@1.0.1: + resolution: {integrity: sha512-URZYihUbRPcGv95En+sz6MfghfIc2OJ1sv/RmhWZLouPY0/8Vo80viwPvg3dlaS9fuq7fQMEfgRRK7BBZThBEA==} engines: {node: ^18.0.0 || >=20.0.0} tinyrainbow@1.2.0: @@ -1619,8 +1647,8 @@ packages: '@swc/wasm': optional: true - tsx@4.17.0: - resolution: {integrity: sha512-eN4mnDA5UMKDt4YZixo9tBioibaMBpoxBkD+rIPAjVmYERSG0/dWEY1CEFuV89CgASlKL499q8AhmkMnnjtOJg==} + tsx@4.19.0: + resolution: {integrity: sha512-bV30kM7bsLZKZIOCHeMNVMJ32/LuJzLVajkQI/qf92J2Qr08ueLQvW00PUZGiuLPP760UINwupgUj8qrSCPUKg==} engines: {node: '>=18.0.0'} hasBin: true @@ -1639,11 +1667,8 @@ packages: ufo@1.5.4: resolution: {integrity: sha512-UsUk3byDzKd04EyoZ7U4DOlxQaD14JUKQl6/P7wiX4FNvUfm3XL246n9W5AmqwW5RSFJ27NAuM0iLscAOYUiGQ==} - undici-types@5.26.5: - resolution: {integrity: sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==} - - undici-types@6.18.2: - resolution: {integrity: sha512-5ruQbENj95yDYJNS3TvcaxPMshV7aizdv/hWYjGIKoANWKjhWNBsr2YEuYZKodQulB1b8l7ILOuDQep3afowQQ==} + undici-types@6.19.8: + resolution: {integrity: sha512-ve2KP6f/JnbPBFyobGHuerC9g1FYGn/F8n1LWTwNxCEzd6IfqTwUQcNXgEtmmQ6DlRrC1hrSrBnCZPokRrDHjw==} unique-filename@1.1.1: resolution: {integrity: sha512-Vmp0jIp2ln35UTXuryvjzkjGdRyf9b2lTXuSYUiPmzRcl3FDtYqAwOnTJkAngD9SWhnoJzDbTKwaOrZ+STtxNQ==} @@ -1667,8 +1692,8 @@ packages: engines: {node: ^18.0.0 || >=20.0.0} hasBin: true - vite@5.4.1: - resolution: {integrity: sha512-1oE6yuNXssjrZdblI9AfBbHCC41nnyoVoEZxQnID6yvQZAFBzxxkqoFLtHUMkYunL8hwOLEjgTuxpkRxvba3kA==} + vite@5.4.2: + resolution: {integrity: sha512-dDrQTRHp5C1fTFzcSaMxjk6vdpKvT+2/mIdE07Gw2ykehT49O0z/VHS3zZ8iV/Gh8BJJKHWOe5RjaNrW5xf/GA==} engines: {node: ^18.0.0 || >=20.0.0} hasBin: true peerDependencies: @@ -1830,142 +1855,142 @@ snapshots: '@esbuild/aix-ppc64@0.21.5': optional: true - '@esbuild/aix-ppc64@0.23.0': + '@esbuild/aix-ppc64@0.23.1': optional: true '@esbuild/android-arm64@0.21.5': optional: true - '@esbuild/android-arm64@0.23.0': + '@esbuild/android-arm64@0.23.1': optional: true '@esbuild/android-arm@0.21.5': optional: true - '@esbuild/android-arm@0.23.0': + '@esbuild/android-arm@0.23.1': optional: true '@esbuild/android-x64@0.21.5': optional: true - '@esbuild/android-x64@0.23.0': + '@esbuild/android-x64@0.23.1': optional: true '@esbuild/darwin-arm64@0.21.5': optional: true - '@esbuild/darwin-arm64@0.23.0': + '@esbuild/darwin-arm64@0.23.1': optional: true '@esbuild/darwin-x64@0.21.5': optional: true - '@esbuild/darwin-x64@0.23.0': + '@esbuild/darwin-x64@0.23.1': optional: true '@esbuild/freebsd-arm64@0.21.5': optional: true - '@esbuild/freebsd-arm64@0.23.0': + '@esbuild/freebsd-arm64@0.23.1': optional: true '@esbuild/freebsd-x64@0.21.5': optional: true - '@esbuild/freebsd-x64@0.23.0': + '@esbuild/freebsd-x64@0.23.1': optional: true '@esbuild/linux-arm64@0.21.5': optional: true - '@esbuild/linux-arm64@0.23.0': + '@esbuild/linux-arm64@0.23.1': optional: true '@esbuild/linux-arm@0.21.5': optional: true - '@esbuild/linux-arm@0.23.0': + '@esbuild/linux-arm@0.23.1': optional: true '@esbuild/linux-ia32@0.21.5': optional: true - '@esbuild/linux-ia32@0.23.0': + '@esbuild/linux-ia32@0.23.1': optional: true '@esbuild/linux-loong64@0.21.5': optional: true - '@esbuild/linux-loong64@0.23.0': + '@esbuild/linux-loong64@0.23.1': optional: true '@esbuild/linux-mips64el@0.21.5': optional: true - '@esbuild/linux-mips64el@0.23.0': + '@esbuild/linux-mips64el@0.23.1': optional: true '@esbuild/linux-ppc64@0.21.5': optional: true - '@esbuild/linux-ppc64@0.23.0': + '@esbuild/linux-ppc64@0.23.1': optional: true '@esbuild/linux-riscv64@0.21.5': optional: true - '@esbuild/linux-riscv64@0.23.0': + '@esbuild/linux-riscv64@0.23.1': optional: true '@esbuild/linux-s390x@0.21.5': optional: true - '@esbuild/linux-s390x@0.23.0': + '@esbuild/linux-s390x@0.23.1': optional: true '@esbuild/linux-x64@0.21.5': optional: true - '@esbuild/linux-x64@0.23.0': + '@esbuild/linux-x64@0.23.1': optional: true '@esbuild/netbsd-x64@0.21.5': optional: true - '@esbuild/netbsd-x64@0.23.0': + '@esbuild/netbsd-x64@0.23.1': optional: true - '@esbuild/openbsd-arm64@0.23.0': + '@esbuild/openbsd-arm64@0.23.1': optional: true '@esbuild/openbsd-x64@0.21.5': optional: true - '@esbuild/openbsd-x64@0.23.0': + '@esbuild/openbsd-x64@0.23.1': optional: true '@esbuild/sunos-x64@0.21.5': optional: true - '@esbuild/sunos-x64@0.23.0': + '@esbuild/sunos-x64@0.23.1': optional: true '@esbuild/win32-arm64@0.21.5': optional: true - '@esbuild/win32-arm64@0.23.0': + '@esbuild/win32-arm64@0.23.1': optional: true '@esbuild/win32-ia32@0.21.5': optional: true - '@esbuild/win32-ia32@0.23.0': + '@esbuild/win32-ia32@0.23.1': optional: true '@esbuild/win32-x64@0.21.5': optional: true - '@esbuild/win32-x64@0.23.0': + '@esbuild/win32-x64@0.23.1': optional: true '@gar/promisify@1.1.3': @@ -1984,7 +2009,7 @@ snapshots: '@jest/schemas': 29.6.3 '@types/istanbul-lib-coverage': 2.0.6 '@types/istanbul-reports': 3.0.4 - '@types/node': 22.3.0 + '@types/node': 22.5.1 '@types/yargs': 17.0.33 chalk: 4.1.2 @@ -2022,52 +2047,52 @@ snapshots: rimraf: 3.0.2 optional: true - '@rollup/rollup-android-arm-eabi@4.20.0': + '@rollup/rollup-android-arm-eabi@4.21.1': optional: true - '@rollup/rollup-android-arm64@4.20.0': + '@rollup/rollup-android-arm64@4.21.1': optional: true - '@rollup/rollup-darwin-arm64@4.20.0': + '@rollup/rollup-darwin-arm64@4.21.1': optional: true - '@rollup/rollup-darwin-x64@4.20.0': + '@rollup/rollup-darwin-x64@4.21.1': optional: true - '@rollup/rollup-linux-arm-gnueabihf@4.20.0': + '@rollup/rollup-linux-arm-gnueabihf@4.21.1': optional: true - '@rollup/rollup-linux-arm-musleabihf@4.20.0': + '@rollup/rollup-linux-arm-musleabihf@4.21.1': optional: true - '@rollup/rollup-linux-arm64-gnu@4.20.0': + '@rollup/rollup-linux-arm64-gnu@4.21.1': optional: true - '@rollup/rollup-linux-arm64-musl@4.20.0': + '@rollup/rollup-linux-arm64-musl@4.21.1': optional: true - '@rollup/rollup-linux-powerpc64le-gnu@4.20.0': + '@rollup/rollup-linux-powerpc64le-gnu@4.21.1': optional: true - '@rollup/rollup-linux-riscv64-gnu@4.20.0': + '@rollup/rollup-linux-riscv64-gnu@4.21.1': optional: true - '@rollup/rollup-linux-s390x-gnu@4.20.0': + '@rollup/rollup-linux-s390x-gnu@4.21.1': optional: true - '@rollup/rollup-linux-x64-gnu@4.20.0': + '@rollup/rollup-linux-x64-gnu@4.21.1': optional: true - '@rollup/rollup-linux-x64-musl@4.20.0': + '@rollup/rollup-linux-x64-musl@4.21.1': optional: true - '@rollup/rollup-win32-arm64-msvc@4.20.0': + '@rollup/rollup-win32-arm64-msvc@4.21.1': optional: true - '@rollup/rollup-win32-ia32-msvc@4.20.0': + '@rollup/rollup-win32-ia32-msvc@4.21.1': optional: true - '@rollup/rollup-win32-x64-msvc@4.20.0': + '@rollup/rollup-win32-x64-msvc@4.21.1': optional: true '@sinclair/typebox@0.27.8': {} @@ -2085,7 +2110,7 @@ snapshots: '@types/better-sqlite3@7.6.11': dependencies: - '@types/node': 20.14.15 + '@types/node': 22.5.1 '@types/estree@1.0.5': {} @@ -2101,13 +2126,13 @@ snapshots: '@types/mocha@10.0.7': {} - '@types/node@20.14.15': + '@types/node@20.16.2': dependencies: - undici-types: 5.26.5 + undici-types: 6.19.8 - '@types/node@22.3.0': + '@types/node@22.5.1': dependencies: - undici-types: 6.18.2 + undici-types: 6.19.8 '@types/stack-utils@2.0.3': {} @@ -2246,7 +2271,7 @@ snapshots: base64-js@1.5.1: {} - better-sqlite3@11.1.2: + better-sqlite3@11.2.1: dependencies: bindings: 1.5.0 prebuild-install: 7.1.2 @@ -2479,32 +2504,32 @@ snapshots: '@esbuild/win32-ia32': 0.21.5 '@esbuild/win32-x64': 0.21.5 - esbuild@0.23.0: + esbuild@0.23.1: optionalDependencies: - '@esbuild/aix-ppc64': 0.23.0 - '@esbuild/android-arm': 0.23.0 - '@esbuild/android-arm64': 0.23.0 - '@esbuild/android-x64': 0.23.0 - '@esbuild/darwin-arm64': 0.23.0 - '@esbuild/darwin-x64': 0.23.0 - '@esbuild/freebsd-arm64': 0.23.0 - '@esbuild/freebsd-x64': 0.23.0 - '@esbuild/linux-arm': 0.23.0 - '@esbuild/linux-arm64': 0.23.0 - '@esbuild/linux-ia32': 0.23.0 - '@esbuild/linux-loong64': 0.23.0 - '@esbuild/linux-mips64el': 0.23.0 - '@esbuild/linux-ppc64': 0.23.0 - '@esbuild/linux-riscv64': 0.23.0 - '@esbuild/linux-s390x': 0.23.0 - '@esbuild/linux-x64': 0.23.0 - '@esbuild/netbsd-x64': 0.23.0 - '@esbuild/openbsd-arm64': 0.23.0 - '@esbuild/openbsd-x64': 0.23.0 - '@esbuild/sunos-x64': 0.23.0 - '@esbuild/win32-arm64': 0.23.0 - '@esbuild/win32-ia32': 0.23.0 - '@esbuild/win32-x64': 0.23.0 + '@esbuild/aix-ppc64': 0.23.1 + '@esbuild/android-arm': 0.23.1 + '@esbuild/android-arm64': 0.23.1 + '@esbuild/android-x64': 0.23.1 + '@esbuild/darwin-arm64': 0.23.1 + '@esbuild/darwin-x64': 0.23.1 + '@esbuild/freebsd-arm64': 0.23.1 + '@esbuild/freebsd-x64': 0.23.1 + '@esbuild/linux-arm': 0.23.1 + '@esbuild/linux-arm64': 0.23.1 + '@esbuild/linux-ia32': 0.23.1 + '@esbuild/linux-loong64': 0.23.1 + '@esbuild/linux-mips64el': 0.23.1 + '@esbuild/linux-ppc64': 0.23.1 + '@esbuild/linux-riscv64': 0.23.1 + '@esbuild/linux-s390x': 0.23.1 + '@esbuild/linux-x64': 0.23.1 + '@esbuild/netbsd-x64': 0.23.1 + '@esbuild/openbsd-arm64': 0.23.1 + '@esbuild/openbsd-x64': 0.23.1 + '@esbuild/sunos-x64': 0.23.1 + '@esbuild/win32-arm64': 0.23.1 + '@esbuild/win32-ia32': 0.23.1 + '@esbuild/win32-x64': 0.23.1 escalade@3.1.2: {} @@ -2727,7 +2752,7 @@ snapshots: '@types/stack-utils': 2.0.3 chalk: 4.1.2 graceful-fs: 4.2.11 - micromatch: 4.0.7 + micromatch: 4.0.8 pretty-format: 29.7.0 slash: 3.0.0 stack-utils: 2.0.6 @@ -2735,7 +2760,7 @@ snapshots: jest-util@29.7.0: dependencies: '@jest/types': 29.6.3 - '@types/node': 22.3.0 + '@types/node': 22.5.1 chalk: 4.1.2 ci-info: 3.9.0 graceful-fs: 4.2.11 @@ -2755,7 +2780,7 @@ snapshots: local-pkg@0.5.0: dependencies: mlly: 1.7.1 - pkg-types: 1.1.3 + pkg-types: 1.2.0 locate-path@6.0.0: dependencies: @@ -2810,7 +2835,7 @@ snapshots: merge-stream@2.0.0: {} - micromatch@4.0.7: + micromatch@4.0.8: dependencies: braces: 3.0.3 picomatch: 2.3.1 @@ -2878,7 +2903,7 @@ snapshots: dependencies: acorn: 8.12.1 pathe: 1.1.2 - pkg-types: 1.1.3 + pkg-types: 1.2.0 ufo: 1.5.4 mocha@10.7.3: @@ -2915,7 +2940,7 @@ snapshots: negotiator@0.6.3: optional: true - node-abi@3.65.0: + node-abi@3.67.0: dependencies: semver: 7.6.3 @@ -3001,7 +3026,7 @@ snapshots: picomatch@2.3.1: {} - pkg-types@1.1.3: + pkg-types@1.2.0: dependencies: confbox: 0.1.7 mlly: 1.7.1 @@ -3023,7 +3048,7 @@ snapshots: minimist: 1.2.8 mkdirp-classic: 0.5.3 napi-build-utils: 1.0.2 - node-abi: 3.65.0 + node-abi: 3.67.0 pump: 3.0.0 rc: 1.2.8 simple-get: 4.0.1 @@ -3087,26 +3112,26 @@ snapshots: glob: 7.2.3 optional: true - rollup@4.20.0: + rollup@4.21.1: dependencies: '@types/estree': 1.0.5 optionalDependencies: - '@rollup/rollup-android-arm-eabi': 4.20.0 - '@rollup/rollup-android-arm64': 4.20.0 - '@rollup/rollup-darwin-arm64': 4.20.0 - '@rollup/rollup-darwin-x64': 4.20.0 - '@rollup/rollup-linux-arm-gnueabihf': 4.20.0 - '@rollup/rollup-linux-arm-musleabihf': 4.20.0 - '@rollup/rollup-linux-arm64-gnu': 4.20.0 - '@rollup/rollup-linux-arm64-musl': 4.20.0 - '@rollup/rollup-linux-powerpc64le-gnu': 4.20.0 - '@rollup/rollup-linux-riscv64-gnu': 4.20.0 - '@rollup/rollup-linux-s390x-gnu': 4.20.0 - '@rollup/rollup-linux-x64-gnu': 4.20.0 - '@rollup/rollup-linux-x64-musl': 4.20.0 - '@rollup/rollup-win32-arm64-msvc': 4.20.0 - '@rollup/rollup-win32-ia32-msvc': 4.20.0 - '@rollup/rollup-win32-x64-msvc': 4.20.0 + '@rollup/rollup-android-arm-eabi': 4.21.1 + '@rollup/rollup-android-arm64': 4.21.1 + '@rollup/rollup-darwin-arm64': 4.21.1 + '@rollup/rollup-darwin-x64': 4.21.1 + '@rollup/rollup-linux-arm-gnueabihf': 4.21.1 + '@rollup/rollup-linux-arm-musleabihf': 4.21.1 + '@rollup/rollup-linux-arm64-gnu': 4.21.1 + '@rollup/rollup-linux-arm64-musl': 4.21.1 + '@rollup/rollup-linux-powerpc64le-gnu': 4.21.1 + '@rollup/rollup-linux-riscv64-gnu': 4.21.1 + '@rollup/rollup-linux-s390x-gnu': 4.21.1 + '@rollup/rollup-linux-x64-gnu': 4.21.1 + '@rollup/rollup-linux-x64-musl': 4.21.1 + '@rollup/rollup-win32-arm64-msvc': 4.21.1 + '@rollup/rollup-win32-ia32-msvc': 4.21.1 + '@rollup/rollup-win32-x64-msvc': 4.21.1 fsevents: 2.3.3 safe-buffer@5.2.1: {} @@ -3260,7 +3285,7 @@ snapshots: tinypool@0.8.4: {} - tinypool@1.0.0: {} + tinypool@1.0.1: {} tinyrainbow@1.2.0: {} @@ -3272,14 +3297,14 @@ snapshots: dependencies: is-number: 7.0.0 - ts-node@10.9.2(@types/node@20.14.15)(typescript@5.5.4): + ts-node@10.9.2(@types/node@20.16.2)(typescript@5.5.4): dependencies: '@cspotcode/source-map-support': 0.8.1 '@tsconfig/node10': 1.0.11 '@tsconfig/node12': 1.0.11 '@tsconfig/node14': 1.0.3 '@tsconfig/node16': 1.0.4 - '@types/node': 20.14.15 + '@types/node': 20.16.2 acorn: 8.12.1 acorn-walk: 8.3.3 arg: 4.1.3 @@ -3290,9 +3315,9 @@ snapshots: v8-compile-cache-lib: 3.0.1 yn: 3.1.1 - tsx@4.17.0: + tsx@4.19.0: dependencies: - esbuild: 0.23.0 + esbuild: 0.23.1 get-tsconfig: 4.7.6 optionalDependencies: fsevents: 2.3.3 @@ -3307,9 +3332,7 @@ snapshots: ufo@1.5.4: {} - undici-types@5.26.5: {} - - undici-types@6.18.2: {} + undici-types@6.19.8: {} unique-filename@1.1.1: dependencies: @@ -3325,13 +3348,13 @@ snapshots: v8-compile-cache-lib@3.0.1: {} - vite-node@1.6.0(@types/node@20.14.15): + vite-node@1.6.0(@types/node@20.16.2): dependencies: cac: 6.7.14 debug: 4.3.6(supports-color@8.1.1) pathe: 1.1.2 picocolors: 1.0.1 - vite: 5.4.1(@types/node@20.14.15) + vite: 5.4.2(@types/node@20.16.2) transitivePeerDependencies: - '@types/node' - less @@ -3343,13 +3366,13 @@ snapshots: - supports-color - terser - vite-node@2.0.5(@types/node@22.3.0): + vite-node@2.0.5(@types/node@22.5.1): dependencies: cac: 6.7.14 debug: 4.3.6(supports-color@8.1.1) pathe: 1.1.2 tinyrainbow: 1.2.0 - vite: 5.4.1(@types/node@22.3.0) + vite: 5.4.2(@types/node@22.5.1) transitivePeerDependencies: - '@types/node' - less @@ -3361,25 +3384,25 @@ snapshots: - supports-color - terser - vite@5.4.1(@types/node@20.14.15): + vite@5.4.2(@types/node@20.16.2): dependencies: esbuild: 0.21.5 postcss: 8.4.41 - rollup: 4.20.0 + rollup: 4.21.1 optionalDependencies: - '@types/node': 20.14.15 + '@types/node': 20.16.2 fsevents: 2.3.3 - vite@5.4.1(@types/node@22.3.0): + vite@5.4.2(@types/node@22.5.1): dependencies: esbuild: 0.21.5 postcss: 8.4.41 - rollup: 4.20.0 + rollup: 4.21.1 optionalDependencies: - '@types/node': 22.3.0 + '@types/node': 22.5.1 fsevents: 2.3.3 - vitest@1.6.0(@types/node@20.14.15): + vitest@1.6.0(@types/node@20.16.2): dependencies: '@vitest/expect': 1.6.0 '@vitest/runner': 1.6.0 @@ -3398,11 +3421,11 @@ snapshots: strip-literal: 2.1.0 tinybench: 2.9.0 tinypool: 0.8.4 - vite: 5.4.1(@types/node@20.14.15) - vite-node: 1.6.0(@types/node@20.14.15) + vite: 5.4.2(@types/node@20.16.2) + vite-node: 1.6.0(@types/node@20.16.2) why-is-node-running: 2.3.0 optionalDependencies: - '@types/node': 20.14.15 + '@types/node': 20.16.2 transitivePeerDependencies: - less - lightningcss @@ -3413,7 +3436,7 @@ snapshots: - supports-color - terser - vitest@2.0.5(@types/node@22.3.0): + vitest@2.0.5(@types/node@22.5.1): dependencies: '@ampproject/remapping': 2.3.0 '@vitest/expect': 2.0.5 @@ -3429,13 +3452,13 @@ snapshots: pathe: 1.1.2 std-env: 3.7.0 tinybench: 2.9.0 - tinypool: 1.0.0 + tinypool: 1.0.1 tinyrainbow: 1.2.0 - vite: 5.4.1(@types/node@22.3.0) - vite-node: 2.0.5(@types/node@22.3.0) + vite: 5.4.2(@types/node@22.5.1) + vite-node: 2.0.5(@types/node@22.5.1) why-is-node-running: 2.3.0 optionalDependencies: - '@types/node': 22.3.0 + '@types/node': 22.5.1 transitivePeerDependencies: - less - lightningcss