Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -621,6 +621,7 @@ sendTo('sql.0', 'getEnabledDPs', {}, function (result) {

## Changelog
### **WORK IN PROGRESS**
* (@GermanBluefox) `npm run build:ts` no longer needs the optional `mysql2` and `sqlite3` drivers to be installed
* (@GermanBluefox) Fixed buffered values being lost when the adapter stops, and unload no longer hangs when there is nothing else to write (#577)
* (@GermanBluefox) `getCounter` no longer reads the NULL boundary marker that `writeNulls` stores on start and stop as a zero counter value (#577)
* (@GermanBluefox) Fixed "Test connection" reporting a failure for a working configuration: the request was built as a text template that could produce invalid JSON, and it left out `dbname`, `doNotCreateDatabase` and the docker settings (#355)
Expand Down
3 changes: 2 additions & 1 deletion build/lib/mysql-client.js

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion build/lib/mysql-client.js.map

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

37 changes: 37 additions & 0 deletions build/lib/optional-drivers.js

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions build/lib/optional-drivers.js.map

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 2 additions & 1 deletion build/lib/sqlite3-client.js

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion build/lib/sqlite3-client.js.map

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion src/lib/connection-options.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import type { ConnectionOptions as MySQLOptions } from 'mysql2';
import type { MySQLOptions } from './optional-drivers';
import type { SqlAdapterConfigTyped } from '../types';

export type { MySQLOptions };
Expand Down
8 changes: 4 additions & 4 deletions src/lib/mysql-client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,15 +2,15 @@ import { ConnectionFactory, type SQLConnection } from './connection-factory';
import SQLClient from './sql-client';
import { SQLClientPool, type PoolConfig } from './sql-client-pool';

import type { Connection, ConnectionOptions as MySQLOptions } from 'mysql2';
import { importDriver, type MySQLConnection, type MySQLModule, type MySQLOptions } from './optional-drivers';

export type { MySQLOptions };

class MySQL2ConnectionFactory extends ConnectionFactory {
private createConnection: any;
openConnection(options: MySQLOptions, callback: (err: Error | null, connection?: Connection) => void): void {
openConnection(options: MySQLOptions, callback: (err: Error | null, connection?: MySQLConnection) => void): void {
if (!this.createConnection) {
void import('mysql2').then(
void importDriver<MySQLModule>('mysql2').then(
mysql2 => {
this.createConnection = mysql2.default.createConnection;
this.openConnection(options, callback);
Expand All @@ -26,7 +26,7 @@ class MySQL2ConnectionFactory extends ConnectionFactory {
connection.connect((err: Error | null): void => callback(err, connection));
}

closeConnection(connection: Connection | null | undefined, callback: (error?: Error | null) => void): void {
closeConnection(connection: MySQLConnection | null | undefined, callback: (error?: Error | null) => void): void {
if (connection) {
connection.end(callback);
} else {
Expand Down
72 changes: 72 additions & 0 deletions src/lib/optional-drivers.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
/**
* Types and the loader for the optional database drivers.
*
* `mysql2`, `pg`, `mssql` and `sqlite3` are **optionalDependencies**: npm skips a driver whose
* installation fails and says so only in a warning, so a checkout can legitimately be missing one.
* At runtime that is handled - the drivers are `import()`ed lazily and a missing one degrades to a
* log message. The *build* used to be stricter than the runtime:
*
* - `import type { Database } from 'sqlite3'` makes TypeScript resolve the package while type
* checking, and
* - so does `import('sqlite3')`, even though the specifier is only ever evaluated at runtime.
*
* Both fail with TS2307 when the driver is absent, so `npm run build:ts` broke on a CI runner that
* had skipped the optional dependency. `pg` and `mssql` survive that because `@types/pg` and
* `@types/mssql` are regular devDependencies and are therefore always installed; `mysql2` and
* `sqlite3` ship their types inside the driver package and have no `@types` fallback.
*
* Hence the declarations below: they describe only the sliver of each driver this adapter actually
* touches, which keeps the build independent of whether the driver is installed. When a new driver
* call is needed, add it here rather than reaching back into the driver's own types.
*/

/** The `mysql2` connection options this adapter sets. */
export interface MySQLOptions {
host?: string;
port?: number;
user?: string;
password?: string;
/** Takes precedence over host and port - mysql2 then ignores both. */
socketPath?: string;
ssl?: { rejectUnauthorized: boolean };
}

/** The `mysql2` connection surface this adapter uses. */
export interface MySQLConnection {
end(callback?: (err?: Error | null) => void): void;
}

/** The `sqlite3` database surface this adapter uses. */
export interface SQLite3Database {
close(callback?: (err: Error | null) => void): void;
all<T>(sql: string, params: Array<unknown>, callback: (err: Error | null, rows?: Array<T>) => void): void;
}

/** The two `new sqlite3.Database(...)` overloads this adapter calls. */
export interface SQLite3DatabaseConstructor {
new (fileName: string, callback: (err: Error | null) => void): SQLite3Database;
new (fileName: string, mode: number, callback: (err: Error | null) => void): SQLite3Database;
}

/** Shape of `await import('mysql2')`. */
export interface MySQLModule {
default: { createConnection: (options: MySQLOptions) => MySQLConnection };
}

/** Shape of `await import('sqlite3')`. */
export interface SQLite3Module {
default: { Database: SQLite3DatabaseConstructor };
}

/**
* Import an optional driver at runtime without making the build depend on it.
*
* TypeScript resolves `import('<literal>')` while type checking, so a literal specifier would
* reintroduce the TS2307 this module exists to avoid. Passing the name through a `string`-typed
* parameter keeps the import opaque to the type checker; Node resolves it exactly as before.
*
* @param name the driver's package name
*/
export function importDriver<T>(name: string): Promise<T> {
return import(name) as Promise<T>;
}
17 changes: 11 additions & 6 deletions src/lib/sqlite3-client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,18 +2,23 @@ import { ConnectionFactory } from './connection-factory';
import SQLClient from './sql-client';
import { SQLClientPool, type PoolConfig } from './sql-client-pool';

import type { Database } from 'sqlite3';
import {
importDriver,
type SQLite3Database,
type SQLite3DatabaseConstructor,
type SQLite3Module,
} from './optional-drivers';

type SQLite3Options = { fileName: string; mode?: number };

export type { SQLite3Options };

export class SQLite3ConnectionFactory extends ConnectionFactory {
private Database: typeof Database | undefined;
private Database: SQLite3DatabaseConstructor | undefined;

openConnection(options: SQLite3Options, callback: (err: Error | null, connection?: Database) => void): void {
openConnection(options: SQLite3Options, callback: (err: Error | null, connection?: SQLite3Database) => void): void {
if (!this.Database) {
void import('sqlite3').then(
void importDriver<SQLite3Module>('sqlite3').then(
sqlite3 => {
this.Database = sqlite3.default.Database;
this.openConnection(options, callback);
Expand Down Expand Up @@ -44,15 +49,15 @@ export class SQLite3ConnectionFactory extends ConnectionFactory {
});
}

closeConnection(db: Database, callback?: (err?: Error | null) => void): void {
closeConnection(db: SQLite3Database, callback?: (err?: Error | null) => void): void {
if (db) {
db.close(callback);
} else {
callback?.();
}
}

execute<T>(db: Database, sql: string, callback: (err: Error | null, result?: Array<T>) => void): void {
execute<T>(db: SQLite3Database, sql: string, callback: (err: Error | null, result?: Array<T>) => void): void {
db.all(sql, [], callback);
}
}
Expand Down
Loading