@remix-run/data-table-sqlite 0.3.0 → 0.4.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 CHANGED
@@ -1,11 +1,11 @@
1
1
  # data-table-sqlite
2
2
 
3
3
  SQLite adapter for [`remix/data-table`](https://github.com/remix-run/remix/tree/main/packages/data-table).
4
- Use this package when you want `data-table` APIs backed by `better-sqlite3`.
4
+ Use this package when you want `data-table` APIs backed by a synchronous SQLite client.
5
5
 
6
6
  ## Features
7
7
 
8
- - **Native `better-sqlite3` Integration**: Works well for local and embedded deployments
8
+ - **Native Runtime SQLite Support**: Works with Node's `node:sqlite` `DatabaseSync`, Bun's `bun:sqlite` `Database`, and compatible synchronous SQLite clients
9
9
  - **Full `data-table` API Support**: Queries, relations, writes, and transactions
10
10
  - **Adapter-Owned Compiler**: SQL compilation lives in this adapter, with optional shared pure helpers from `data-table`
11
11
  - **Migration DDL Support**: Compiles and executes `DataMigrationOperation` operations for `remix/data-table/migrations`
@@ -19,13 +19,26 @@ Use this package when you want `data-table` APIs backed by `better-sqlite3`.
19
19
  ## Installation
20
20
 
21
21
  ```sh
22
- npm i remix better-sqlite3
22
+ npm i remix
23
23
  ```
24
24
 
25
25
  ## Usage
26
26
 
27
+ ### Node
28
+
29
+ ```ts
30
+ import { DatabaseSync } from 'node:sqlite'
31
+ import { createDatabase } from 'remix/data-table'
32
+ import { createSqliteDatabaseAdapter } from 'remix/data-table-sqlite'
33
+
34
+ let sqlite = new DatabaseSync('app.db')
35
+ let db = createDatabase(createSqliteDatabaseAdapter(sqlite))
36
+ ```
37
+
38
+ ### Bun
39
+
27
40
  ```ts
28
- import Database from 'better-sqlite3'
41
+ import { Database } from 'bun:sqlite'
29
42
  import { createDatabase } from 'remix/data-table'
30
43
  import { createSqliteDatabaseAdapter } from 'remix/data-table-sqlite'
31
44
 
@@ -34,7 +47,7 @@ let db = createDatabase(createSqliteDatabaseAdapter(sqlite))
34
47
  ```
35
48
 
36
49
  This is a good fit for local development, embedded deployments, and single-node services.
37
- Import any driver-specific types you need directly from `better-sqlite3`.
50
+ Import any driver-specific types you need directly from your runtime's SQLite module.
38
51
 
39
52
  ## Adapter Capabilities
40
53
 
@@ -51,11 +64,11 @@ Import any driver-specific types you need directly from `better-sqlite3`.
51
64
  ### In-Memory Database For Tests
52
65
 
53
66
  ```ts
54
- import Database from 'better-sqlite3'
67
+ import { DatabaseSync } from 'node:sqlite'
55
68
  import { createDatabase } from 'remix/data-table'
56
69
  import { createSqliteDatabaseAdapter } from 'remix/data-table-sqlite'
57
70
 
58
- let sqlite = new Database(':memory:')
71
+ let sqlite = new DatabaseSync(':memory:')
59
72
  let db = createDatabase(createSqliteDatabaseAdapter(sqlite))
60
73
  ```
61
74
 
package/dist/index.d.ts CHANGED
@@ -1,2 +1,3 @@
1
1
  export { createSqliteDatabaseAdapter, SqliteDatabaseAdapter } from './lib/adapter.ts';
2
+ export type { SqliteDatabase, SqliteRunResult, SqliteStatement } from './lib/adapter.ts';
2
3
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,2BAA2B,EAAE,qBAAqB,EAAE,MAAM,kBAAkB,CAAA"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,2BAA2B,EAAE,qBAAqB,EAAE,MAAM,kBAAkB,CAAA;AACrF,YAAY,EAAE,cAAc,EAAE,eAAe,EAAE,eAAe,EAAE,MAAM,kBAAkB,CAAA"}
@@ -1,7 +1,34 @@
1
1
  import type { DataManipulationRequest, DataMigrationRequest, DataMigrationResult, DataMigrationOperation, DataManipulationResult, DataManipulationOperation, DatabaseAdapter, SqlStatement, TableRef, TransactionOptions, TransactionToken } from '@remix-run/data-table';
2
- import type { Database as BetterSqliteDatabase } from 'better-sqlite3';
3
2
  /**
4
- * `DatabaseAdapter` implementation for Better SQLite3.
3
+ * Synchronous SQLite database client accepted by the sqlite adapter.
4
+ *
5
+ * This matches the shared surface of Node's `node:sqlite` `DatabaseSync`, Bun's `bun:sqlite`
6
+ * `Database`, and compatible synchronous SQLite clients.
7
+ */
8
+ export interface SqliteDatabase {
9
+ prepare(sql: string): SqliteStatement;
10
+ exec(sql: string): unknown;
11
+ }
12
+ /**
13
+ * Prepared statement shape used by {@link SqliteDatabase}.
14
+ */
15
+ export interface SqliteStatement {
16
+ all(...values: unknown[]): unknown[];
17
+ get(...values: unknown[]): unknown;
18
+ run(...values: unknown[]): SqliteRunResult;
19
+ reader?: boolean;
20
+ columns?: () => unknown[];
21
+ columnNames?: string[];
22
+ }
23
+ /**
24
+ * SQLite write execution metadata.
25
+ */
26
+ export interface SqliteRunResult {
27
+ changes: number | bigint;
28
+ lastInsertRowid: unknown;
29
+ }
30
+ /**
31
+ * `DatabaseAdapter` implementation for synchronous SQLite clients.
5
32
  */
6
33
  export declare class SqliteDatabaseAdapter implements DatabaseAdapter {
7
34
  #private;
@@ -19,7 +46,7 @@ export declare class SqliteDatabaseAdapter implements DatabaseAdapter {
19
46
  transactionalDdl: boolean;
20
47
  migrationLock: boolean;
21
48
  };
22
- constructor(database: BetterSqliteDatabase);
49
+ constructor(database: SqliteDatabase);
23
50
  /**
24
51
  * Compiles a data or migration operation to sqlite SQL statements.
25
52
  * @param operation Operation to compile.
@@ -95,19 +122,18 @@ export declare class SqliteDatabaseAdapter implements DatabaseAdapter {
95
122
  }
96
123
  /**
97
124
  * Creates a sqlite `DatabaseAdapter`.
98
- * @param database Better SQLite3 database instance.
99
- * @param options Optional adapter capability overrides.
125
+ * @param database Synchronous SQLite database client.
100
126
  * @returns A configured sqlite adapter.
101
127
  * @example
102
128
  * ```ts
103
- * import BetterSqlite3 from 'better-sqlite3'
129
+ * import { DatabaseSync } from 'node:sqlite'
104
130
  * import { createDatabase } from 'remix/data-table'
105
131
  * import { createSqliteDatabaseAdapter } from 'remix/data-table-sqlite'
106
132
  *
107
- * let sqlite = new BetterSqlite3('./data/app.db')
133
+ * let sqlite = new DatabaseSync('./data/app.db')
108
134
  * let adapter = createSqliteDatabaseAdapter(sqlite)
109
135
  * let db = createDatabase(adapter)
110
136
  * ```
111
137
  */
112
- export declare function createSqliteDatabaseAdapter(database: BetterSqliteDatabase): SqliteDatabaseAdapter;
138
+ export declare function createSqliteDatabaseAdapter(database: SqliteDatabase): SqliteDatabaseAdapter;
113
139
  //# sourceMappingURL=adapter.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"adapter.d.ts","sourceRoot":"","sources":["../../src/lib/adapter.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACV,uBAAuB,EACvB,oBAAoB,EACpB,mBAAmB,EACnB,sBAAsB,EACtB,sBAAsB,EACtB,yBAAyB,EACzB,eAAe,EAEf,YAAY,EACZ,QAAQ,EACR,kBAAkB,EAClB,gBAAgB,EACjB,MAAM,uBAAuB,CAAA;AAO9B,OAAO,KAAK,EAAE,QAAQ,IAAI,oBAAoB,EAAa,MAAM,gBAAgB,CAAA;AAIjF;;GAEG;AACH,qBAAa,qBAAsB,YAAW,eAAe;;IAC3D;;OAEG;IACH,OAAO,SAAW;IAElB;;OAEG;IACH,YAAY;;;;;;MAAA;IAMZ,YAAY,QAAQ,EAAE,oBAAoB,EASzC;IAED;;;;OAIG;IACH,UAAU,CAAC,SAAS,EAAE,yBAAyB,GAAG,sBAAsB,GAAG,YAAY,EAAE,CAOxF;IAED;;;;OAIG;IACG,OAAO,CAAC,OAAO,EAAE,uBAAuB,GAAG,OAAO,CAAC,sBAAsB,CAAC,CAgC/E;IAED;;;;OAIG;IACG,OAAO,CAAC,OAAO,EAAE,oBAAoB,GAAG,OAAO,CAAC,mBAAmB,CAAC,CAWzE;IAED;;;;;OAKG;IACG,QAAQ,CAAC,KAAK,EAAE,QAAQ,EAAE,WAAW,CAAC,EAAE,gBAAgB,GAAG,OAAO,CAAC,OAAO,CAAC,CAahF;IAED;;;;;;OAMG;IACG,SAAS,CACb,KAAK,EAAE,QAAQ,EACf,MAAM,EAAE,MAAM,EACd,WAAW,CAAC,EAAE,gBAAgB,GAC7B,OAAO,CAAC,OAAO,CAAC,CAYlB;IAED;;;;OAIG;IACG,gBAAgB,CAAC,OAAO,CAAC,EAAE,kBAAkB,GAAG,OAAO,CAAC,gBAAgB,CAAC,CAY9E;IAED;;;;OAIG;IACG,iBAAiB,CAAC,KAAK,EAAE,gBAAgB,GAAG,OAAO,CAAC,IAAI,CAAC,CAI9D;IAED;;;;OAIG;IACG,mBAAmB,CAAC,KAAK,EAAE,gBAAgB,GAAG,OAAO,CAAC,IAAI,CAAC,CAIhE;IAED;;;;;OAKG;IACG,eAAe,CAAC,KAAK,EAAE,gBAAgB,EAAE,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAG1E;IAED;;;;;OAKG;IACG,mBAAmB,CAAC,KAAK,EAAE,gBAAgB,EAAE,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAG9E;IAED;;;;;OAKG;IACG,gBAAgB,CAAC,KAAK,EAAE,gBAAgB,EAAE,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAG3E;CAOF;AAED;;;;;;;;;;;;;;;GAeG;AACH,wBAAgB,2BAA2B,CAAC,QAAQ,EAAE,oBAAoB,GAAG,qBAAqB,CAEjG"}
1
+ {"version":3,"file":"adapter.d.ts","sourceRoot":"","sources":["../../src/lib/adapter.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACV,uBAAuB,EACvB,oBAAoB,EACpB,mBAAmB,EACnB,sBAAsB,EACtB,sBAAsB,EACtB,yBAAyB,EACzB,eAAe,EAEf,YAAY,EACZ,QAAQ,EACR,kBAAkB,EAClB,gBAAgB,EACjB,MAAM,uBAAuB,CAAA;AAU9B;;;;;GAKG;AACH,MAAM,WAAW,cAAc;IAC7B,OAAO,CAAC,GAAG,EAAE,MAAM,GAAG,eAAe,CAAA;IACrC,IAAI,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAA;CAC3B;AAED;;GAEG;AACH,MAAM,WAAW,eAAe;IAC9B,GAAG,CAAC,GAAG,MAAM,EAAE,OAAO,EAAE,GAAG,OAAO,EAAE,CAAA;IACpC,GAAG,CAAC,GAAG,MAAM,EAAE,OAAO,EAAE,GAAG,OAAO,CAAA;IAClC,GAAG,CAAC,GAAG,MAAM,EAAE,OAAO,EAAE,GAAG,eAAe,CAAA;IAC1C,MAAM,CAAC,EAAE,OAAO,CAAA;IAChB,OAAO,CAAC,EAAE,MAAM,OAAO,EAAE,CAAA;IACzB,WAAW,CAAC,EAAE,MAAM,EAAE,CAAA;CACvB;AAED;;GAEG;AACH,MAAM,WAAW,eAAe;IAC9B,OAAO,EAAE,MAAM,GAAG,MAAM,CAAA;IACxB,eAAe,EAAE,OAAO,CAAA;CACzB;AAED;;GAEG;AACH,qBAAa,qBAAsB,YAAW,eAAe;;IAC3D;;OAEG;IACH,OAAO,SAAW;IAElB;;OAEG;IACH,YAAY;;;;;;MAAA;IAMZ,YAAY,QAAQ,EAAE,cAAc,EASnC;IAED;;;;OAIG;IACH,UAAU,CAAC,SAAS,EAAE,yBAAyB,GAAG,sBAAsB,GAAG,YAAY,EAAE,CAOxF;IAED;;;;OAIG;IACG,OAAO,CAAC,OAAO,EAAE,uBAAuB,GAAG,OAAO,CAAC,sBAAsB,CAAC,CAiC/E;IAED;;;;OAIG;IACG,OAAO,CAAC,OAAO,EAAE,oBAAoB,GAAG,OAAO,CAAC,mBAAmB,CAAC,CAWzE;IAED;;;;;OAKG;IACG,QAAQ,CAAC,KAAK,EAAE,QAAQ,EAAE,WAAW,CAAC,EAAE,gBAAgB,GAAG,OAAO,CAAC,OAAO,CAAC,CAahF;IAED;;;;;;OAMG;IACG,SAAS,CACb,KAAK,EAAE,QAAQ,EACf,MAAM,EAAE,MAAM,EACd,WAAW,CAAC,EAAE,gBAAgB,GAC7B,OAAO,CAAC,OAAO,CAAC,CAYlB;IAED;;;;OAIG;IACG,gBAAgB,CAAC,OAAO,CAAC,EAAE,kBAAkB,GAAG,OAAO,CAAC,gBAAgB,CAAC,CAY9E;IAED;;;;OAIG;IACG,iBAAiB,CAAC,KAAK,EAAE,gBAAgB,GAAG,OAAO,CAAC,IAAI,CAAC,CAI9D;IAED;;;;OAIG;IACG,mBAAmB,CAAC,KAAK,EAAE,gBAAgB,GAAG,OAAO,CAAC,IAAI,CAAC,CAIhE;IAED;;;;;OAKG;IACG,eAAe,CAAC,KAAK,EAAE,gBAAgB,EAAE,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAG1E;IAED;;;;;OAKG;IACG,mBAAmB,CAAC,KAAK,EAAE,gBAAgB,EAAE,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAG9E;IAED;;;;;OAKG;IACG,gBAAgB,CAAC,KAAK,EAAE,gBAAgB,EAAE,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAG3E;CAOF;AAED;;;;;;;;;;;;;;GAcG;AACH,wBAAgB,2BAA2B,CAAC,QAAQ,EAAE,cAAc,GAAG,qBAAqB,CAE3F"}
@@ -2,7 +2,7 @@ import { getTablePrimaryKey } from '@remix-run/data-table';
2
2
  import { isDataManipulationOperation as isDataManipulationOperationHelper, quoteLiteral as quoteLiteralHelper, quoteTableRef as quoteTableRefHelper, } from '@remix-run/data-table/sql-helpers';
3
3
  import { compileSqliteOperation } from "./sql-compiler.js";
4
4
  /**
5
- * `DatabaseAdapter` implementation for Better SQLite3.
5
+ * `DatabaseAdapter` implementation for synchronous SQLite clients.
6
6
  */
7
7
  export class SqliteDatabaseAdapter {
8
8
  /**
@@ -53,8 +53,9 @@ export class SqliteDatabaseAdapter {
53
53
  }
54
54
  let statement = this.compileSql(request.operation)[0];
55
55
  let prepared = this.#database.prepare(statement.text);
56
- if (prepared.reader) {
57
- let rows = normalizeRows(prepared.all(...statement.values));
56
+ let values = normalizeStatementValues(statement.values);
57
+ if (shouldReadStatement(request.operation, prepared)) {
58
+ let rows = normalizeRows(prepared.all(...values));
58
59
  if (request.operation.kind === 'count' || request.operation.kind === 'exists') {
59
60
  rows = normalizeCountRows(rows);
60
61
  }
@@ -64,7 +65,7 @@ export class SqliteDatabaseAdapter {
64
65
  insertId: normalizeInsertIdForReader(request.operation.kind, request.operation, rows),
65
66
  };
66
67
  }
67
- let result = prepared.run(...statement.values);
68
+ let result = prepared.run(...values);
68
69
  return {
69
70
  affectedRows: normalizeAffectedRowsForRun(request.operation.kind, result),
70
71
  insertId: normalizeInsertIdForRun(request.operation.kind, request.operation, result),
@@ -79,7 +80,7 @@ export class SqliteDatabaseAdapter {
79
80
  let statements = this.compileSql(request.operation);
80
81
  for (let statement of statements) {
81
82
  let prepared = this.#database.prepare(statement.text);
82
- prepared.run(...statement.values);
83
+ prepared.run(...normalizeStatementValues(statement.values));
83
84
  }
84
85
  return {
85
86
  affectedOperations: statements.length,
@@ -125,7 +126,7 @@ export class SqliteDatabaseAdapter {
125
126
  */
126
127
  async beginTransaction(options) {
127
128
  if (options?.isolationLevel === 'read uncommitted') {
128
- this.#database.pragma('read_uncommitted = true');
129
+ this.#database.exec('pragma read_uncommitted = true');
129
130
  }
130
131
  this.#database.exec('begin');
131
132
  this.#transactionCounter += 1;
@@ -191,16 +192,15 @@ export class SqliteDatabaseAdapter {
191
192
  }
192
193
  /**
193
194
  * Creates a sqlite `DatabaseAdapter`.
194
- * @param database Better SQLite3 database instance.
195
- * @param options Optional adapter capability overrides.
195
+ * @param database Synchronous SQLite database client.
196
196
  * @returns A configured sqlite adapter.
197
197
  * @example
198
198
  * ```ts
199
- * import BetterSqlite3 from 'better-sqlite3'
199
+ * import { DatabaseSync } from 'node:sqlite'
200
200
  * import { createDatabase } from 'remix/data-table'
201
201
  * import { createSqliteDatabaseAdapter } from 'remix/data-table-sqlite'
202
202
  *
203
- * let sqlite = new BetterSqlite3('./data/app.db')
203
+ * let sqlite = new DatabaseSync('./data/app.db')
204
204
  * let adapter = createSqliteDatabaseAdapter(sqlite)
205
205
  * let db = createDatabase(adapter)
206
206
  * ```
@@ -216,6 +216,9 @@ function normalizeRows(rows) {
216
216
  return { ...row };
217
217
  });
218
218
  }
219
+ function normalizeStatementValues(values) {
220
+ return values.map((value) => (value === undefined ? null : value));
221
+ }
219
222
  function normalizeCountRows(rows) {
220
223
  return rows.map((row) => {
221
224
  let count = row.count;
@@ -259,7 +262,7 @@ function normalizeAffectedRowsForRun(kind, result) {
259
262
  if (kind === 'select' || kind === 'count' || kind === 'exists') {
260
263
  return undefined;
261
264
  }
262
- return result.changes;
265
+ return Number(result.changes);
263
266
  }
264
267
  function normalizeInsertIdForRun(kind, operation, result) {
265
268
  if (!isInsertOperationKind(kind) || !isInsertOperation(operation)) {
@@ -270,6 +273,26 @@ function normalizeInsertIdForRun(kind, operation, result) {
270
273
  }
271
274
  return result.lastInsertRowid;
272
275
  }
276
+ function shouldReadStatement(operation, statement) {
277
+ if (operation.kind === 'select' || operation.kind === 'count' || operation.kind === 'exists') {
278
+ return true;
279
+ }
280
+ if (operation.kind !== 'raw') {
281
+ return operation.returning !== undefined;
282
+ }
283
+ if (typeof statement.reader === 'boolean') {
284
+ return statement.reader;
285
+ }
286
+ if (statement.columns) {
287
+ return statement.columns().length > 0;
288
+ }
289
+ try {
290
+ return statement.columnNames !== undefined && statement.columnNames.length > 0;
291
+ }
292
+ catch {
293
+ return false;
294
+ }
295
+ }
273
296
  function quoteIdentifier(value) {
274
297
  return '"' + value.replace(/"/g, '""') + '"';
275
298
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@remix-run/data-table-sqlite",
3
- "version": "0.3.0",
3
+ "version": "0.4.1",
4
4
  "description": "SQLite adapter for remix/data-table",
5
5
  "author": "Michael Jackson <mjijackson@gmail.com>",
6
6
  "license": "MIT",
@@ -27,22 +27,14 @@
27
27
  "./package.json": "./package.json"
28
28
  },
29
29
  "devDependencies": {
30
- "@types/better-sqlite3": "^7.6.13",
31
30
  "@types/node": "^24.6.0",
32
31
  "@typescript/native-preview": "7.0.0-dev.20251125.1",
33
- "better-sqlite3": "^12.4.1",
34
- "@remix-run/data-table": "0.2.0"
32
+ "@remix-run/assert": "0.2.0",
33
+ "@remix-run/test": "0.3.0",
34
+ "@remix-run/data-table": "0.2.1"
35
35
  },
36
36
  "dependencies": {
37
- "@remix-run/data-table": "^0.2.0"
38
- },
39
- "peerDependencies": {
40
- "better-sqlite3": "^12.4.1"
41
- },
42
- "peerDependenciesMeta": {
43
- "better-sqlite3": {
44
- "optional": true
45
- }
37
+ "@remix-run/data-table": "^0.2.1"
46
38
  },
47
39
  "keywords": [
48
40
  "remix",
@@ -54,8 +46,9 @@
54
46
  "scripts": {
55
47
  "build": "tsgo -p tsconfig.build.json",
56
48
  "clean": "git clean -fdX",
57
- "test": "node --test",
58
- "test:coverage": "node --experimental-test-coverage --test-coverage-include='src/**/*.ts' --test-coverage-lines=90 --test-coverage-branches=90 --test-coverage-functions=90 --test ./src/lib/adapter.test.ts ./src/lib/sql-compiler.test.ts",
49
+ "test": "remix-test",
50
+ "test:bun": "bun x --bun remix-test",
51
+ "test:coverage": "remix-test --coverage",
59
52
  "typecheck": "tsgo --noEmit"
60
53
  }
61
54
  }
package/src/index.ts CHANGED
@@ -1 +1,2 @@
1
1
  export { createSqliteDatabaseAdapter, SqliteDatabaseAdapter } from './lib/adapter.ts'
2
+ export type { SqliteDatabase, SqliteRunResult, SqliteStatement } from './lib/adapter.ts'
@@ -18,12 +18,42 @@ import {
18
18
  quoteLiteral as quoteLiteralHelper,
19
19
  quoteTableRef as quoteTableRefHelper,
20
20
  } from '@remix-run/data-table/sql-helpers'
21
- import type { Database as BetterSqliteDatabase, RunResult } from 'better-sqlite3'
22
21
 
23
22
  import { compileSqliteOperation } from './sql-compiler.ts'
24
23
 
25
24
  /**
26
- * `DatabaseAdapter` implementation for Better SQLite3.
25
+ * Synchronous SQLite database client accepted by the sqlite adapter.
26
+ *
27
+ * This matches the shared surface of Node's `node:sqlite` `DatabaseSync`, Bun's `bun:sqlite`
28
+ * `Database`, and compatible synchronous SQLite clients.
29
+ */
30
+ export interface SqliteDatabase {
31
+ prepare(sql: string): SqliteStatement
32
+ exec(sql: string): unknown
33
+ }
34
+
35
+ /**
36
+ * Prepared statement shape used by {@link SqliteDatabase}.
37
+ */
38
+ export interface SqliteStatement {
39
+ all(...values: unknown[]): unknown[]
40
+ get(...values: unknown[]): unknown
41
+ run(...values: unknown[]): SqliteRunResult
42
+ reader?: boolean
43
+ columns?: () => unknown[]
44
+ columnNames?: string[]
45
+ }
46
+
47
+ /**
48
+ * SQLite write execution metadata.
49
+ */
50
+ export interface SqliteRunResult {
51
+ changes: number | bigint
52
+ lastInsertRowid: unknown
53
+ }
54
+
55
+ /**
56
+ * `DatabaseAdapter` implementation for synchronous SQLite clients.
27
57
  */
28
58
  export class SqliteDatabaseAdapter implements DatabaseAdapter {
29
59
  /**
@@ -36,11 +66,11 @@ export class SqliteDatabaseAdapter implements DatabaseAdapter {
36
66
  */
37
67
  capabilities
38
68
 
39
- #database: BetterSqliteDatabase
69
+ #database: SqliteDatabase
40
70
  #transactions = new Set<string>()
41
71
  #transactionCounter = 0
42
72
 
43
- constructor(database: BetterSqliteDatabase) {
73
+ constructor(database: SqliteDatabase) {
44
74
  this.#database = database
45
75
  this.capabilities = {
46
76
  returning: true,
@@ -81,9 +111,10 @@ export class SqliteDatabaseAdapter implements DatabaseAdapter {
81
111
 
82
112
  let statement = this.compileSql(request.operation)[0]
83
113
  let prepared = this.#database.prepare(statement.text)
114
+ let values = normalizeStatementValues(statement.values)
84
115
 
85
- if (prepared.reader) {
86
- let rows = normalizeRows(prepared.all(...statement.values))
116
+ if (shouldReadStatement(request.operation, prepared)) {
117
+ let rows = normalizeRows(prepared.all(...values))
87
118
 
88
119
  if (request.operation.kind === 'count' || request.operation.kind === 'exists') {
89
120
  rows = normalizeCountRows(rows)
@@ -96,7 +127,7 @@ export class SqliteDatabaseAdapter implements DatabaseAdapter {
96
127
  }
97
128
  }
98
129
 
99
- let result = prepared.run(...statement.values)
130
+ let result = prepared.run(...values)
100
131
 
101
132
  return {
102
133
  affectedRows: normalizeAffectedRowsForRun(request.operation.kind, result),
@@ -114,7 +145,7 @@ export class SqliteDatabaseAdapter implements DatabaseAdapter {
114
145
 
115
146
  for (let statement of statements) {
116
147
  let prepared = this.#database.prepare(statement.text)
117
- prepared.run(...statement.values)
148
+ prepared.run(...normalizeStatementValues(statement.values))
118
149
  }
119
150
 
120
151
  return {
@@ -175,7 +206,7 @@ export class SqliteDatabaseAdapter implements DatabaseAdapter {
175
206
  */
176
207
  async beginTransaction(options?: TransactionOptions): Promise<TransactionToken> {
177
208
  if (options?.isolationLevel === 'read uncommitted') {
178
- this.#database.pragma('read_uncommitted = true')
209
+ this.#database.exec('pragma read_uncommitted = true')
179
210
  }
180
211
 
181
212
  this.#database.exec('begin')
@@ -251,21 +282,20 @@ export class SqliteDatabaseAdapter implements DatabaseAdapter {
251
282
 
252
283
  /**
253
284
  * Creates a sqlite `DatabaseAdapter`.
254
- * @param database Better SQLite3 database instance.
255
- * @param options Optional adapter capability overrides.
285
+ * @param database Synchronous SQLite database client.
256
286
  * @returns A configured sqlite adapter.
257
287
  * @example
258
288
  * ```ts
259
- * import BetterSqlite3 from 'better-sqlite3'
289
+ * import { DatabaseSync } from 'node:sqlite'
260
290
  * import { createDatabase } from 'remix/data-table'
261
291
  * import { createSqliteDatabaseAdapter } from 'remix/data-table-sqlite'
262
292
  *
263
- * let sqlite = new BetterSqlite3('./data/app.db')
293
+ * let sqlite = new DatabaseSync('./data/app.db')
264
294
  * let adapter = createSqliteDatabaseAdapter(sqlite)
265
295
  * let db = createDatabase(adapter)
266
296
  * ```
267
297
  */
268
- export function createSqliteDatabaseAdapter(database: BetterSqliteDatabase): SqliteDatabaseAdapter {
298
+ export function createSqliteDatabaseAdapter(database: SqliteDatabase): SqliteDatabaseAdapter {
269
299
  return new SqliteDatabaseAdapter(database)
270
300
  }
271
301
 
@@ -279,6 +309,10 @@ function normalizeRows(rows: unknown[]): Record<string, unknown>[] {
279
309
  })
280
310
  }
281
311
 
312
+ function normalizeStatementValues(values: unknown[]): unknown[] {
313
+ return values.map((value) => (value === undefined ? null : value))
314
+ }
315
+
282
316
  function normalizeCountRows(rows: Record<string, unknown>[]): Record<string, unknown>[] {
283
317
  return rows.map((row) => {
284
318
  let count = row.count
@@ -339,19 +373,19 @@ function normalizeInsertIdForReader(
339
373
 
340
374
  function normalizeAffectedRowsForRun(
341
375
  kind: DataManipulationRequest['operation']['kind'],
342
- result: RunResult,
376
+ result: SqliteRunResult,
343
377
  ): number | undefined {
344
378
  if (kind === 'select' || kind === 'count' || kind === 'exists') {
345
379
  return undefined
346
380
  }
347
381
 
348
- return result.changes
382
+ return Number(result.changes)
349
383
  }
350
384
 
351
385
  function normalizeInsertIdForRun(
352
386
  kind: DataManipulationRequest['operation']['kind'],
353
387
  operation: DataManipulationRequest['operation'],
354
- result: RunResult,
388
+ result: SqliteRunResult,
355
389
  ): unknown {
356
390
  if (!isInsertOperationKind(kind) || !isInsertOperation(operation)) {
357
391
  return undefined
@@ -364,6 +398,33 @@ function normalizeInsertIdForRun(
364
398
  return result.lastInsertRowid
365
399
  }
366
400
 
401
+ function shouldReadStatement(
402
+ operation: DataManipulationRequest['operation'],
403
+ statement: SqliteStatement,
404
+ ): boolean {
405
+ if (operation.kind === 'select' || operation.kind === 'count' || operation.kind === 'exists') {
406
+ return true
407
+ }
408
+
409
+ if (operation.kind !== 'raw') {
410
+ return operation.returning !== undefined
411
+ }
412
+
413
+ if (typeof statement.reader === 'boolean') {
414
+ return statement.reader
415
+ }
416
+
417
+ if (statement.columns) {
418
+ return statement.columns().length > 0
419
+ }
420
+
421
+ try {
422
+ return statement.columnNames !== undefined && statement.columnNames.length > 0
423
+ } catch {
424
+ return false
425
+ }
426
+ }
427
+
367
428
  function quoteIdentifier(value: string): string {
368
429
  return '"' + value.replace(/"/g, '""') + '"'
369
430
  }