@prisma-next/driver-sqlite 0.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md ADDED
@@ -0,0 +1,121 @@
1
+ # @prisma-next/driver-sqlite
2
+
3
+ SQLite driver for Prisma Next.
4
+
5
+ ## Package Classification
6
+
7
+ - **Domain**: targets
8
+ - **Layer**: drivers
9
+ - **Plane**: multi-plane (migration, runtime)
10
+
11
+ ## Overview
12
+
13
+ The SQLite driver provides transport and connection management for SQLite databases using Node.js built-in `node:sqlite` module (`DatabaseSync`). It implements the `SqlDriver` interface for executing SQL statements, explaining queries, and managing connections.
14
+
15
+ In Prisma Next, "driver" refers to the Prisma Next interface (not the underlying `node:sqlite` API). Drivers own connection management and transport, but contain no dialect-specific logic. All dialect behavior lives in adapters. Instantiation is separate from connection; `create()` returns an unbound driver, `connect(binding)` binds at the boundary ([ADR 159](../../../../docs/architecture%20docs/adrs/ADR%20159%20-%20Driver%20Terminology%20and%20Lifecycle.md)).
16
+
17
+ This package spans multiple planes:
18
+ - **Migration plane** (`src/exports/control.ts`): Control plane entry point for driver descriptors
19
+ - **Runtime plane** (`src/exports/runtime.ts`): Runtime entry point for driver implementation
20
+
21
+ ## Purpose
22
+
23
+ Provide SQLite transport and connection management. Execute SQL statements and manage connections without dialect-specific logic.
24
+
25
+ ## Responsibilities
26
+
27
+ - **Connection Management**: Acquire and release database connections via `node:sqlite` `DatabaseSync`
28
+ - **Statement Execution**: Execute SQL statements with positional `?` parameters
29
+ - **Query Explanation**: Execute `EXPLAIN QUERY PLAN` queries for query analysis
30
+ - **Persistent Connection**: Top-level `execute()`/`query()`/`explain()` reuse a persistent connection opened at `connect()` time
31
+ - **Scoped Connections**: `acquireConnection()` opens fresh `DatabaseSync` handles for isolated scopes (transactions)
32
+ - **Transaction Support**: `BEGIN`/`COMMIT`/`ROLLBACK` via `SqliteTransactionImpl`
33
+ - **PRAGMA Configuration**: Enables `PRAGMA foreign_keys = ON` and `PRAGMA busy_timeout = 5000` on every opened connection
34
+ - **Error Normalization**: Maps SQLite extended error codes to SQL state codes (23505 unique, 23503 FK, 23502 NOT NULL) and distinguishes transient (BUSY/LOCKED) from permanent errors
35
+
36
+ **Non-goals:**
37
+ - Dialect-specific SQL lowering (adapters)
38
+ - Query compilation (sql-query)
39
+ - Runtime execution orchestration (sql-runtime)
40
+
41
+ ## Architecture
42
+
43
+ ```mermaid
44
+ flowchart TD
45
+ subgraph "Runtime"
46
+ RT[Runtime]
47
+ ADAPTER[Adapter]
48
+ end
49
+
50
+ subgraph "SQLite Driver"
51
+ DRIVER[Driver]
52
+ CONN[Persistent Connection]
53
+ SCOPED[Scoped Connection]
54
+ end
55
+
56
+ subgraph "SQLite"
57
+ DB[(SQLite File / :memory:)]
58
+ end
59
+
60
+ RT --> ADAPTER
61
+ ADAPTER --> DRIVER
62
+ DRIVER --> CONN
63
+ DRIVER --> SCOPED
64
+ CONN --> DB
65
+ SCOPED --> DB
66
+ DB --> DRIVER
67
+ DRIVER --> RT
68
+ ```
69
+
70
+ ## Components
71
+
72
+ ### Driver (`sqlite-driver.ts`)
73
+ - `SqliteBoundDriver`: main driver implementation wrapping `DatabaseSync`
74
+ - `SqliteConnectionImpl`: connection wrapper with `execute()`, `query()`, `explain()`, `beginTransaction()`
75
+ - `SqliteTransactionImpl`: transaction wrapper adding `commit()` and `rollback()`
76
+ - `createBoundDriverFromBinding()`: factory that creates and immediately connects a driver
77
+
78
+ ### Error Normalization (`normalize-error.ts`)
79
+ - Maps SQLite extended `errcode` to structured `SqlQueryError` / `SqlConnectionError`
80
+ - Handles constraint types individually (UNIQUE, FK, NOT NULL, CHECK)
81
+
82
+ ## Dependencies
83
+
84
+ - **`@prisma-next/sql-relational-core`**: SQL contract types (`SqlDriver`, `SqlConnection`, `SqlTransaction`)
85
+ - **`@prisma-next/framework-components`**: Descriptor types (`RuntimeDriverDescriptor`, `ControlDriverDescriptor`)
86
+ - **`@prisma-next/errors`**: Structured error factories
87
+
88
+ ## Related Subsystems
89
+
90
+ - **[Adapters & Targets](../../../../docs/architecture%20docs/subsystems/5.%20Adapters%20&%20Targets.md)**: Driver specification
91
+
92
+ ## Related ADRs
93
+
94
+ - [ADR 159 -- Driver Terminology and Lifecycle](../../../../docs/architecture%20docs/adrs/ADR%20159%20-%20Driver%20Terminology%20and%20Lifecycle.md)
95
+ - [ADR 005 -- Thin Core Fat Targets](../../../../docs/architecture%20docs/adrs/ADR%20005%20-%20Thin%20Core%20Fat%20Targets.md)
96
+ - [ADR 016 -- Adapter SPI for Lowering](../../../../docs/architecture%20docs/adrs/ADR%20016%20-%20Adapter%20SPI%20for%20Lowering.md)
97
+
98
+ ## Usage
99
+
100
+ Use the descriptor + connect lifecycle:
101
+
102
+ ```typescript
103
+ import sqliteDriver from '@prisma-next/driver-sqlite/runtime';
104
+
105
+ const driver = sqliteDriver.create();
106
+ await driver.connect({ kind: 'path', path: ':memory:' });
107
+ // driver is now bound; use acquireConnection, query, execute, etc.
108
+ ```
109
+
110
+ Binding:
111
+ - `{ kind: 'path', path: ':memory:' }`: In-memory database
112
+ - `{ kind: 'path', path: './data.db' }`: File-based database
113
+
114
+ ## Exports
115
+
116
+ - `./runtime`: Runtime entry point for driver implementation
117
+ - Default: `sqliteRuntimeDriverDescriptor` -- use `create()` for unbound driver, then `connect(binding)`
118
+ - Types: `SqliteBinding`, `SqliteRuntimeDriver`
119
+ - `./control`: Control plane entry point for driver descriptors
120
+ - Default: `ControlDriverDescriptor` for use in CLI config
121
+ - `SqliteControlDriver` class for direct control-plane usage
package/package.json ADDED
@@ -0,0 +1,48 @@
1
+ {
2
+ "name": "@prisma-next/driver-sqlite",
3
+ "version": "0.0.1",
4
+ "type": "module",
5
+ "sideEffects": false,
6
+ "scripts": {
7
+ "build": "tsdown",
8
+ "test": "vitest run",
9
+ "test:coverage": "vitest run --coverage",
10
+ "typecheck": "tsc --project tsconfig.json --noEmit",
11
+ "lint": "biome check . --error-on-warnings",
12
+ "lint:fix": "biome check --write .",
13
+ "lint:fix:unsafe": "biome check --write --unsafe .",
14
+ "clean": "rm -rf dist dist-tsc dist-tsc-prod coverage .tmp-output"
15
+ },
16
+ "dependencies": {
17
+ "@prisma-next/contract": "workspace:*",
18
+ "@prisma-next/errors": "workspace:*",
19
+ "@prisma-next/framework-components": "workspace:*",
20
+ "@prisma-next/sql-contract": "workspace:*",
21
+ "@prisma-next/sql-errors": "workspace:*",
22
+ "@prisma-next/sql-operations": "workspace:*",
23
+ "@prisma-next/sql-relational-core": "workspace:*",
24
+ "@prisma-next/utils": "workspace:*"
25
+ },
26
+ "devDependencies": {
27
+ "@prisma-next/test-utils": "workspace:*",
28
+ "@prisma-next/tsconfig": "workspace:*",
29
+ "@prisma-next/tsdown": "workspace:*",
30
+ "tsdown": "catalog:",
31
+ "typescript": "catalog:",
32
+ "vitest": "catalog:"
33
+ },
34
+ "files": [
35
+ "dist",
36
+ "src"
37
+ ],
38
+ "exports": {
39
+ "./control": "./dist/control.mjs",
40
+ "./runtime": "./dist/runtime.mjs",
41
+ "./package.json": "./package.json"
42
+ },
43
+ "repository": {
44
+ "type": "git",
45
+ "url": "https://github.com/prisma/prisma-next.git",
46
+ "directory": "packages/3-targets/7-drivers/sqlite"
47
+ }
48
+ }
@@ -0,0 +1,54 @@
1
+ import type { SQLInputValue } from 'node:sqlite';
2
+ import { DatabaseSync } from 'node:sqlite';
3
+ import { errorRuntime } from '@prisma-next/errors/execution';
4
+ import type {
5
+ ControlDriverDescriptor,
6
+ ControlDriverInstance,
7
+ } from '@prisma-next/framework-components/control';
8
+ import { normalizeSqliteError } from '../normalize-error';
9
+ import { sqliteDriverDescriptorMeta } from './descriptor-meta';
10
+
11
+ export class SqliteControlDriver implements ControlDriverInstance<'sql', 'sqlite'> {
12
+ readonly familyId = 'sql' as const;
13
+ readonly targetId = 'sqlite' as const;
14
+
15
+ constructor(private readonly db: DatabaseSync) {}
16
+
17
+ async query<Row = Record<string, unknown>>(
18
+ sql: string,
19
+ params?: readonly unknown[],
20
+ ): Promise<{ readonly rows: Row[] }> {
21
+ try {
22
+ const stmt = this.db.prepare(sql);
23
+ const rows = stmt.all(...((params ?? []) as SQLInputValue[])) as Row[];
24
+ return { rows };
25
+ } catch (error) {
26
+ throw normalizeSqliteError(error);
27
+ }
28
+ }
29
+
30
+ async close(): Promise<void> {
31
+ this.db.close();
32
+ }
33
+ }
34
+
35
+ const sqliteDriverDescriptor: ControlDriverDescriptor<'sql', 'sqlite', SqliteControlDriver> = {
36
+ ...sqliteDriverDescriptorMeta,
37
+ async create(pathOrMemory: string): Promise<SqliteControlDriver> {
38
+ try {
39
+ const db = new DatabaseSync(pathOrMemory);
40
+ db.exec('PRAGMA foreign_keys = ON');
41
+ return new SqliteControlDriver(db);
42
+ } catch (error) {
43
+ throw errorRuntime('Database connection failed', {
44
+ why: error instanceof Error ? error.message : String(error),
45
+ fix: 'Verify the database file path exists and is accessible',
46
+ meta: {
47
+ path: pathOrMemory,
48
+ },
49
+ });
50
+ }
51
+ },
52
+ };
53
+
54
+ export default sqliteDriverDescriptor;
@@ -0,0 +1,8 @@
1
+ export const sqliteDriverDescriptorMeta = {
2
+ kind: 'driver',
3
+ familyId: 'sql',
4
+ targetId: 'sqlite',
5
+ id: 'sqlite',
6
+ version: '0.0.1',
7
+ capabilities: {},
8
+ } as const;
@@ -0,0 +1,19 @@
1
+ import type { RuntimeDriverDescriptor } from '@prisma-next/framework-components/execution';
2
+ import { SqliteDriver, type SqliteRuntimeDriver } from '../sqlite-driver';
3
+ import { sqliteDriverDescriptorMeta } from './descriptor-meta';
4
+
5
+ export type { SqliteRuntimeDriver } from '../sqlite-driver';
6
+
7
+ const sqliteRuntimeDriverDescriptor: RuntimeDriverDescriptor<
8
+ 'sql',
9
+ 'sqlite',
10
+ void,
11
+ SqliteRuntimeDriver
12
+ > = {
13
+ ...sqliteDriverDescriptorMeta,
14
+ create(): SqliteRuntimeDriver {
15
+ return new SqliteDriver();
16
+ },
17
+ };
18
+
19
+ export default sqliteRuntimeDriverDescriptor;
@@ -0,0 +1 @@
1
+ export { default, SqliteControlDriver } from '../core/control-driver';
@@ -0,0 +1,3 @@
1
+ export type { SqliteRuntimeDriver } from '../core/runtime-driver';
2
+ export { default } from '../core/runtime-driver';
3
+ export type { SqliteBinding } from '../sqlite-driver';
@@ -0,0 +1,105 @@
1
+ import { SqlConnectionError, SqlQueryError } from '@prisma-next/sql-errors';
2
+
3
+ interface SqliteError extends Error {
4
+ readonly code?: string;
5
+ readonly errcode?: number;
6
+ readonly errstr?: string;
7
+ }
8
+
9
+ // SQLite extended error code ranges (base code * 256 + extended)
10
+ // Base code 19 = SQLITE_CONSTRAINT
11
+ const SQLITE_CONSTRAINT_BASE = 19;
12
+ const SQLITE_CONSTRAINT_UNIQUE = 2067; // 19 + 8*256
13
+ const SQLITE_CONSTRAINT_PRIMARYKEY = 1555; // 19 + 6*256
14
+ const SQLITE_CONSTRAINT_FOREIGNKEY = 787; // 19 + 3*256
15
+ const SQLITE_CONSTRAINT_NOTNULL = 1299; // 19 + 5*256
16
+ const SQLITE_CONSTRAINT_CHECK = 275; // 19 + 1*256
17
+
18
+ // Base code 5 = SQLITE_BUSY
19
+ const SQLITE_BUSY = 5;
20
+ // Base code 6 = SQLITE_LOCKED
21
+ const SQLITE_LOCKED = 6;
22
+
23
+ function isConstraintError(errcode: number): boolean {
24
+ return (errcode & 0xff) === SQLITE_CONSTRAINT_BASE;
25
+ }
26
+
27
+ function isBusyOrLocked(errcode: number): boolean {
28
+ const base = errcode & 0xff;
29
+ return base === SQLITE_BUSY || base === SQLITE_LOCKED;
30
+ }
31
+
32
+ export function isSqliteError(error: unknown): error is SqliteError {
33
+ if (!(error instanceof Error)) {
34
+ return false;
35
+ }
36
+ return (error as SqliteError).code === 'ERR_SQLITE_ERROR';
37
+ }
38
+
39
+ function constraintNameFromMessage(message: string): string | undefined {
40
+ // SQLite constraint messages follow patterns like:
41
+ // "UNIQUE constraint failed: table.column"
42
+ // "FOREIGN KEY constraint failed"
43
+ // "NOT NULL constraint failed: table.column"
44
+ const match = /constraint failed: (.+)/.exec(message);
45
+ return match?.[1];
46
+ }
47
+
48
+ function mapErrCodeToSqlState(errcode: number): string {
49
+ switch (errcode) {
50
+ case SQLITE_CONSTRAINT_UNIQUE:
51
+ case SQLITE_CONSTRAINT_PRIMARYKEY:
52
+ return '23505'; // unique_violation
53
+ case SQLITE_CONSTRAINT_FOREIGNKEY:
54
+ return '23503'; // foreign_key_violation
55
+ case SQLITE_CONSTRAINT_NOTNULL:
56
+ return '23502'; // not_null_violation
57
+ case SQLITE_CONSTRAINT_CHECK:
58
+ return '23514'; // check_violation
59
+ default:
60
+ if (isConstraintError(errcode)) {
61
+ return '23000'; // integrity_constraint_violation
62
+ }
63
+ return 'HY000'; // general error
64
+ }
65
+ }
66
+
67
+ export function normalizeSqliteError(error: unknown): SqlQueryError | SqlConnectionError | Error {
68
+ if (!(error instanceof Error)) {
69
+ return new Error(String(error));
70
+ }
71
+
72
+ if (isSqliteError(error)) {
73
+ const sqliteErr = error as SqliteError;
74
+ const errcode = sqliteErr.errcode ?? 0;
75
+
76
+ if (isBusyOrLocked(errcode)) {
77
+ return new SqlConnectionError(error.message, {
78
+ cause: error,
79
+ transient: true,
80
+ });
81
+ }
82
+
83
+ const sqlState = mapErrCodeToSqlState(errcode);
84
+ const constraint = constraintNameFromMessage(error.message);
85
+
86
+ return new SqlQueryError(error.message, {
87
+ cause: error,
88
+ sqlState,
89
+ ...(constraint !== undefined ? { constraint } : {}),
90
+ });
91
+ }
92
+
93
+ // Connection-related Node.js errors
94
+ if (
95
+ error.message.includes('database is locked') ||
96
+ error.message.includes('unable to open database')
97
+ ) {
98
+ return new SqlConnectionError(error.message, {
99
+ cause: error,
100
+ transient: false,
101
+ });
102
+ }
103
+
104
+ return error;
105
+ }
@@ -0,0 +1,276 @@
1
+ import type { SQLInputValue } from 'node:sqlite';
2
+ import { DatabaseSync } from 'node:sqlite';
3
+ import type { RuntimeDriverInstance } from '@prisma-next/framework-components/execution';
4
+ import type {
5
+ SqlConnection,
6
+ SqlDriver,
7
+ SqlDriverState,
8
+ SqlExecuteRequest,
9
+ SqlExplainResult,
10
+ SqlQueryResult,
11
+ SqlTransaction,
12
+ } from '@prisma-next/sql-relational-core/ast';
13
+ import { normalizeSqliteError } from './normalize-error';
14
+
15
+ export type SqliteBinding = { readonly kind: 'path'; readonly path: string };
16
+
17
+ export type SqliteRuntimeDriver = RuntimeDriverInstance<'sql', 'sqlite'> & SqlDriver<SqliteBinding>;
18
+
19
+ interface DriverRuntimeError extends Error {
20
+ readonly code:
21
+ | 'DRIVER.NOT_CONNECTED'
22
+ | 'DRIVER.ALREADY_CONNECTED'
23
+ | 'DRIVER.EXPLAIN_NOT_SUPPORTED';
24
+ readonly category: 'RUNTIME';
25
+ readonly severity: 'error';
26
+ readonly details?: Record<string, unknown>;
27
+ }
28
+
29
+ function driverError(
30
+ code: DriverRuntimeError['code'],
31
+ message: string,
32
+ details?: Record<string, unknown>,
33
+ ): DriverRuntimeError {
34
+ const error = new Error(message) as DriverRuntimeError;
35
+ Object.defineProperty(error, 'name', {
36
+ value: 'RuntimeError',
37
+ configurable: true,
38
+ });
39
+ return Object.assign(error, {
40
+ code,
41
+ category: 'RUNTIME' as const,
42
+ severity: 'error' as const,
43
+ message,
44
+ details,
45
+ });
46
+ }
47
+
48
+ const NOT_CONNECTED_MESSAGE =
49
+ 'SQLite driver not connected. Call connect(binding) before acquireConnection or execute.';
50
+ const ALREADY_CONNECTED_MESSAGE =
51
+ 'SQLite driver already connected. Call close() before reconnecting with a new binding.';
52
+
53
+ function toSqliteParams(params: readonly unknown[] | undefined): SQLInputValue[] {
54
+ return (params ?? []) as SQLInputValue[];
55
+ }
56
+
57
+ function openConnection(path: string): DatabaseSync {
58
+ try {
59
+ const db = new DatabaseSync(path);
60
+ db.exec('PRAGMA foreign_keys = ON');
61
+ db.exec('PRAGMA busy_timeout = 5000');
62
+ return db;
63
+ } catch (error) {
64
+ throw normalizeSqliteError(error);
65
+ }
66
+ }
67
+
68
+ class SqliteConnectionImpl implements SqlConnection {
69
+ readonly #db: DatabaseSync;
70
+
71
+ constructor(db: DatabaseSync) {
72
+ this.#db = db;
73
+ }
74
+
75
+ async *execute<Row = Record<string, unknown>>(request: SqlExecuteRequest): AsyncIterable<Row> {
76
+ try {
77
+ const stmt = this.#db.prepare(request.sql);
78
+ for (const row of stmt.iterate(...toSqliteParams(request.params))) {
79
+ yield row as Row;
80
+ }
81
+ } catch (error) {
82
+ throw normalizeSqliteError(error);
83
+ }
84
+ }
85
+
86
+ async explain(request: SqlExecuteRequest): Promise<SqlExplainResult> {
87
+ try {
88
+ const stmt = this.#db.prepare(`EXPLAIN QUERY PLAN ${request.sql}`);
89
+ const rows = stmt.all(...toSqliteParams(request.params)) as ReadonlyArray<
90
+ Record<string, unknown>
91
+ >;
92
+ return { rows };
93
+ } catch (error) {
94
+ throw normalizeSqliteError(error);
95
+ }
96
+ }
97
+
98
+ async query<Row = Record<string, unknown>>(
99
+ sql: string,
100
+ params?: readonly unknown[],
101
+ ): Promise<SqlQueryResult<Row>> {
102
+ try {
103
+ const stmt = this.#db.prepare(sql);
104
+ const rows = stmt.all(...toSqliteParams(params)) as Row[];
105
+ return { rows };
106
+ } catch (error) {
107
+ throw normalizeSqliteError(error);
108
+ }
109
+ }
110
+
111
+ async beginTransaction(): Promise<SqlTransaction> {
112
+ try {
113
+ this.#db.exec('BEGIN');
114
+ return new SqliteTransactionImpl(this.#db);
115
+ } catch (error) {
116
+ throw normalizeSqliteError(error);
117
+ }
118
+ }
119
+
120
+ async release(): Promise<void> {
121
+ try {
122
+ this.#db.close();
123
+ } catch (error) {
124
+ throw normalizeSqliteError(error);
125
+ }
126
+ }
127
+ }
128
+
129
+ class SqliteTransactionImpl implements SqlTransaction {
130
+ readonly #db: DatabaseSync;
131
+
132
+ constructor(db: DatabaseSync) {
133
+ this.#db = db;
134
+ }
135
+
136
+ async *execute<Row = Record<string, unknown>>(request: SqlExecuteRequest): AsyncIterable<Row> {
137
+ try {
138
+ const stmt = this.#db.prepare(request.sql);
139
+ for (const row of stmt.iterate(...toSqliteParams(request.params))) {
140
+ yield row as Row;
141
+ }
142
+ } catch (error) {
143
+ throw normalizeSqliteError(error);
144
+ }
145
+ }
146
+
147
+ async explain(request: SqlExecuteRequest): Promise<SqlExplainResult> {
148
+ try {
149
+ const stmt = this.#db.prepare(`EXPLAIN QUERY PLAN ${request.sql}`);
150
+ const rows = stmt.all(...toSqliteParams(request.params)) as ReadonlyArray<
151
+ Record<string, unknown>
152
+ >;
153
+ return { rows };
154
+ } catch (error) {
155
+ throw normalizeSqliteError(error);
156
+ }
157
+ }
158
+
159
+ async query<Row = Record<string, unknown>>(
160
+ sql: string,
161
+ params?: readonly unknown[],
162
+ ): Promise<SqlQueryResult<Row>> {
163
+ try {
164
+ const stmt = this.#db.prepare(sql);
165
+ const rows = stmt.all(...toSqliteParams(params)) as Row[];
166
+ return { rows };
167
+ } catch (error) {
168
+ throw normalizeSqliteError(error);
169
+ }
170
+ }
171
+
172
+ async commit(): Promise<void> {
173
+ try {
174
+ this.#db.exec('COMMIT');
175
+ } catch (error) {
176
+ throw normalizeSqliteError(error);
177
+ }
178
+ }
179
+
180
+ async rollback(): Promise<void> {
181
+ try {
182
+ this.#db.exec('ROLLBACK');
183
+ } catch (error) {
184
+ throw normalizeSqliteError(error);
185
+ }
186
+ }
187
+ }
188
+
189
+ interface ConnectedState {
190
+ readonly kind: 'connected';
191
+ readonly path: string;
192
+ readonly conn: SqliteConnectionImpl;
193
+ }
194
+
195
+ type DriverState = { readonly kind: 'unbound' } | ConnectedState | { readonly kind: 'closed' };
196
+
197
+ export class SqliteDriver implements SqliteRuntimeDriver {
198
+ readonly familyId = 'sql' as const;
199
+ readonly targetId = 'sqlite' as const;
200
+
201
+ #state: DriverState;
202
+
203
+ constructor(initialState?: ConnectedState) {
204
+ this.#state = initialState ?? { kind: 'unbound' };
205
+ }
206
+
207
+ #requireConnected(): ConnectedState {
208
+ if (this.#state.kind !== 'connected') {
209
+ throw driverError('DRIVER.NOT_CONNECTED', NOT_CONNECTED_MESSAGE);
210
+ }
211
+ return this.#state;
212
+ }
213
+
214
+ get state(): SqlDriverState {
215
+ return this.#state.kind;
216
+ }
217
+
218
+ async connect(binding: SqliteBinding): Promise<void> {
219
+ if (this.#state.kind === 'connected') {
220
+ throw driverError('DRIVER.ALREADY_CONNECTED', ALREADY_CONNECTED_MESSAGE, {
221
+ bindingKind: binding.kind,
222
+ });
223
+ }
224
+ this.#state = {
225
+ kind: 'connected',
226
+ path: binding.path,
227
+ conn: new SqliteConnectionImpl(openConnection(binding.path)),
228
+ };
229
+ }
230
+
231
+ async acquireConnection(): Promise<SqliteConnectionImpl> {
232
+ const { path } = this.#requireConnected();
233
+ return new SqliteConnectionImpl(openConnection(path));
234
+ }
235
+
236
+ async close(): Promise<void> {
237
+ if (this.#state.kind !== 'connected') return;
238
+ const { conn } = this.#state;
239
+ this.#state = { kind: 'closed' };
240
+ await conn.release();
241
+ }
242
+
243
+ execute<Row = Record<string, unknown>>(request: SqlExecuteRequest): AsyncIterable<Row> {
244
+ if (this.#state.kind !== 'connected') {
245
+ return {
246
+ [Symbol.asyncIterator]() {
247
+ return {
248
+ async next() {
249
+ throw driverError('DRIVER.NOT_CONNECTED', NOT_CONNECTED_MESSAGE);
250
+ },
251
+ };
252
+ },
253
+ };
254
+ }
255
+ return this.#state.conn.execute<Row>(request);
256
+ }
257
+
258
+ async explain(request: SqlExecuteRequest): Promise<SqlExplainResult> {
259
+ return this.#requireConnected().conn.explain(request);
260
+ }
261
+
262
+ async query<Row = Record<string, unknown>>(
263
+ sql: string,
264
+ params?: readonly unknown[],
265
+ ): Promise<SqlQueryResult<Row>> {
266
+ return this.#requireConnected().conn.query<Row>(sql, params);
267
+ }
268
+ }
269
+
270
+ export function createBoundDriverFromBinding(binding: SqliteBinding): SqliteDriver {
271
+ return new SqliteDriver({
272
+ kind: 'connected',
273
+ path: binding.path,
274
+ conn: new SqliteConnectionImpl(openConnection(binding.path)),
275
+ });
276
+ }