@remix-run/data-table-sqlite 0.2.0 → 0.4.0

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
+
27
29
  ```ts
28
- import Database from 'better-sqlite3'
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
+
40
+ ```ts
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,26 +64,14 @@ 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
 
62
- ### Capability Overrides For Fallback Testing
63
-
64
- ```ts
65
- import { createSqliteDatabaseAdapter } from 'remix/data-table-sqlite'
66
-
67
- let adapter = createSqliteDatabaseAdapter(sqlite, {
68
- capabilities: {
69
- returning: false,
70
- },
71
- })
72
- ```
73
-
74
75
  ## Related Packages
75
76
 
76
77
  - [`data-table`](https://github.com/remix-run/remix/tree/main/packages/data-table) - Core query/relations API
package/dist/index.d.ts CHANGED
@@ -1,3 +1,3 @@
1
- export type { SqliteDatabaseAdapterOptions } from './lib/adapter.ts';
2
1
  export { createSqliteDatabaseAdapter, SqliteDatabaseAdapter } from './lib/adapter.ts';
2
+ export type { SqliteDatabase, SqliteRunResult, SqliteStatement } from './lib/adapter.ts';
3
3
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,YAAY,EAAE,4BAA4B,EAAE,MAAM,kBAAkB,CAAA;AACpE,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,13 +1,34 @@
1
- import type { AdapterCapabilityOverrides, 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';
1
+ import type { DataManipulationRequest, DataMigrationRequest, DataMigrationResult, DataMigrationOperation, DataManipulationResult, DataManipulationOperation, DatabaseAdapter, SqlStatement, TableRef, TransactionOptions, TransactionToken } from '@remix-run/data-table';
3
2
  /**
4
- * Sqlite adapter configuration.
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 `better-sqlite3` database instances.
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}.
5
14
  */
6
- export type SqliteDatabaseAdapterOptions = {
7
- capabilities?: AdapterCapabilityOverrides;
8
- };
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;
28
+ lastInsertRowid: unknown;
29
+ }
9
30
  /**
10
- * `DatabaseAdapter` implementation for Better SQLite3.
31
+ * `DatabaseAdapter` implementation for synchronous SQLite clients.
11
32
  */
12
33
  export declare class SqliteDatabaseAdapter implements DatabaseAdapter {
13
34
  #private;
@@ -25,7 +46,7 @@ export declare class SqliteDatabaseAdapter implements DatabaseAdapter {
25
46
  transactionalDdl: boolean;
26
47
  migrationLock: boolean;
27
48
  };
28
- constructor(database: BetterSqliteDatabase, options?: SqliteDatabaseAdapterOptions);
49
+ constructor(database: SqliteDatabase);
29
50
  /**
30
51
  * Compiles a data or migration operation to sqlite SQL statements.
31
52
  * @param operation Operation to compile.
@@ -101,19 +122,18 @@ export declare class SqliteDatabaseAdapter implements DatabaseAdapter {
101
122
  }
102
123
  /**
103
124
  * Creates a sqlite `DatabaseAdapter`.
104
- * @param database Better SQLite3 database instance.
105
- * @param options Optional adapter capability overrides.
125
+ * @param database Synchronous SQLite database client.
106
126
  * @returns A configured sqlite adapter.
107
127
  * @example
108
128
  * ```ts
109
- * import BetterSqlite3 from 'better-sqlite3'
129
+ * import { DatabaseSync } from 'node:sqlite'
110
130
  * import { createDatabase } from 'remix/data-table'
111
131
  * import { createSqliteDatabaseAdapter } from 'remix/data-table-sqlite'
112
132
  *
113
- * let sqlite = new BetterSqlite3('./data/app.db')
133
+ * let sqlite = new DatabaseSync('./data/app.db')
114
134
  * let adapter = createSqliteDatabaseAdapter(sqlite)
115
135
  * let db = createDatabase(adapter)
116
136
  * ```
117
137
  */
118
- export declare function createSqliteDatabaseAdapter(database: BetterSqliteDatabase, options?: SqliteDatabaseAdapterOptions): SqliteDatabaseAdapter;
138
+ export declare function createSqliteDatabaseAdapter(database: SqliteDatabase): SqliteDatabaseAdapter;
119
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,0BAA0B,EAC1B,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,MAAM,MAAM,4BAA4B,GAAG;IACzC,YAAY,CAAC,EAAE,0BAA0B,CAAA;CAC1C,CAAA;AAED;;GAEG;AACH,qBAAa,qBAAsB,YAAW,eAAe;;IAC3D;;OAEG;IACH,OAAO,SAAW;IAElB;;OAEG;IACH,YAAY;;;;;;MAAA;IAMZ,YAAY,QAAQ,EAAE,oBAAoB,EAAE,OAAO,CAAC,EAAE,4BAA4B,EASjF;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,CACzC,QAAQ,EAAE,oBAAoB,EAC9B,OAAO,CAAC,EAAE,4BAA4B,GACrC,qBAAqB,CAEvB"}
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,CAAA;IACf,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,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;;;;;;;;;;;;;;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
  /**
@@ -16,14 +16,14 @@ export class SqliteDatabaseAdapter {
16
16
  #database;
17
17
  #transactions = new Set();
18
18
  #transactionCounter = 0;
19
- constructor(database, options) {
19
+ constructor(database) {
20
20
  this.#database = database;
21
21
  this.capabilities = {
22
- returning: options?.capabilities?.returning ?? true,
23
- savepoints: options?.capabilities?.savepoints ?? true,
24
- upsert: options?.capabilities?.upsert ?? true,
25
- transactionalDdl: options?.capabilities?.transactionalDdl ?? true,
26
- migrationLock: options?.capabilities?.migrationLock ?? false,
22
+ returning: true,
23
+ savepoints: true,
24
+ upsert: true,
25
+ transactionalDdl: true,
26
+ migrationLock: false,
27
27
  };
28
28
  }
29
29
  /**
@@ -53,7 +53,7 @@ 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) {
56
+ if (shouldReadStatement(request.operation, prepared)) {
57
57
  let rows = normalizeRows(prepared.all(...statement.values));
58
58
  if (request.operation.kind === 'count' || request.operation.kind === 'exists') {
59
59
  rows = normalizeCountRows(rows);
@@ -125,7 +125,7 @@ export class SqliteDatabaseAdapter {
125
125
  */
126
126
  async beginTransaction(options) {
127
127
  if (options?.isolationLevel === 'read uncommitted') {
128
- this.#database.pragma('read_uncommitted = true');
128
+ this.#database.exec('pragma read_uncommitted = true');
129
129
  }
130
130
  this.#database.exec('begin');
131
131
  this.#transactionCounter += 1;
@@ -191,22 +191,21 @@ export class SqliteDatabaseAdapter {
191
191
  }
192
192
  /**
193
193
  * Creates a sqlite `DatabaseAdapter`.
194
- * @param database Better SQLite3 database instance.
195
- * @param options Optional adapter capability overrides.
194
+ * @param database Synchronous SQLite database client.
196
195
  * @returns A configured sqlite adapter.
197
196
  * @example
198
197
  * ```ts
199
- * import BetterSqlite3 from 'better-sqlite3'
198
+ * import { DatabaseSync } from 'node:sqlite'
200
199
  * import { createDatabase } from 'remix/data-table'
201
200
  * import { createSqliteDatabaseAdapter } from 'remix/data-table-sqlite'
202
201
  *
203
- * let sqlite = new BetterSqlite3('./data/app.db')
202
+ * let sqlite = new DatabaseSync('./data/app.db')
204
203
  * let adapter = createSqliteDatabaseAdapter(sqlite)
205
204
  * let db = createDatabase(adapter)
206
205
  * ```
207
206
  */
208
- export function createSqliteDatabaseAdapter(database, options) {
209
- return new SqliteDatabaseAdapter(database, options);
207
+ export function createSqliteDatabaseAdapter(database) {
208
+ return new SqliteDatabaseAdapter(database);
210
209
  }
211
210
  function normalizeRows(rows) {
212
211
  return rows.map((row) => {
@@ -270,6 +269,26 @@ function normalizeInsertIdForRun(kind, operation, result) {
270
269
  }
271
270
  return result.lastInsertRowid;
272
271
  }
272
+ function shouldReadStatement(operation, statement) {
273
+ if (operation.kind === 'select' || operation.kind === 'count' || operation.kind === 'exists') {
274
+ return true;
275
+ }
276
+ if (operation.kind !== 'raw') {
277
+ return operation.returning !== undefined;
278
+ }
279
+ if (typeof statement.reader === 'boolean') {
280
+ return statement.reader;
281
+ }
282
+ if (statement.columns) {
283
+ return statement.columns().length > 0;
284
+ }
285
+ try {
286
+ return statement.columnNames !== undefined && statement.columnNames.length > 0;
287
+ }
288
+ catch {
289
+ return false;
290
+ }
291
+ }
273
292
  function quoteIdentifier(value) {
274
293
  return '"' + value.replace(/"/g, '""') + '"';
275
294
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@remix-run/data-table-sqlite",
3
- "version": "0.2.0",
3
+ "version": "0.4.0",
4
4
  "description": "SQLite adapter for remix/data-table",
5
5
  "author": "Michael Jackson <mjijackson@gmail.com>",
6
6
  "license": "MIT",
@@ -27,23 +27,15 @@
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.1.0",
33
+ "@remix-run/data-table": "0.2.0",
34
+ "@remix-run/test": "0.2.0"
35
35
  },
36
36
  "dependencies": {
37
37
  "@remix-run/data-table": "^0.2.0"
38
38
  },
39
- "peerDependencies": {
40
- "better-sqlite3": "^12.4.1"
41
- },
42
- "peerDependenciesMeta": {
43
- "better-sqlite3": {
44
- "optional": true
45
- }
46
- },
47
39
  "keywords": [
48
40
  "remix",
49
41
  "orm",
@@ -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,2 +1,2 @@
1
- export type { SqliteDatabaseAdapterOptions } from './lib/adapter.ts'
2
1
  export { createSqliteDatabaseAdapter, SqliteDatabaseAdapter } from './lib/adapter.ts'
2
+ export type { SqliteDatabase, SqliteRunResult, SqliteStatement } from './lib/adapter.ts'
@@ -1,5 +1,4 @@
1
1
  import type {
2
- AdapterCapabilityOverrides,
3
2
  DataManipulationRequest,
4
3
  DataMigrationRequest,
5
4
  DataMigrationResult,
@@ -19,19 +18,42 @@ import {
19
18
  quoteLiteral as quoteLiteralHelper,
20
19
  quoteTableRef as quoteTableRefHelper,
21
20
  } from '@remix-run/data-table/sql-helpers'
22
- import type { Database as BetterSqliteDatabase, RunResult } from 'better-sqlite3'
23
21
 
24
22
  import { compileSqliteOperation } from './sql-compiler.ts'
25
23
 
26
24
  /**
27
- * Sqlite adapter configuration.
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 `better-sqlite3` database instances.
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}.
28
37
  */
29
- export type SqliteDatabaseAdapterOptions = {
30
- capabilities?: AdapterCapabilityOverrides
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[]
31
45
  }
32
46
 
33
47
  /**
34
- * `DatabaseAdapter` implementation for Better SQLite3.
48
+ * SQLite write execution metadata.
49
+ */
50
+ export interface SqliteRunResult {
51
+ changes: number
52
+ lastInsertRowid: unknown
53
+ }
54
+
55
+ /**
56
+ * `DatabaseAdapter` implementation for synchronous SQLite clients.
35
57
  */
36
58
  export class SqliteDatabaseAdapter implements DatabaseAdapter {
37
59
  /**
@@ -44,18 +66,18 @@ export class SqliteDatabaseAdapter implements DatabaseAdapter {
44
66
  */
45
67
  capabilities
46
68
 
47
- #database: BetterSqliteDatabase
69
+ #database: SqliteDatabase
48
70
  #transactions = new Set<string>()
49
71
  #transactionCounter = 0
50
72
 
51
- constructor(database: BetterSqliteDatabase, options?: SqliteDatabaseAdapterOptions) {
73
+ constructor(database: SqliteDatabase) {
52
74
  this.#database = database
53
75
  this.capabilities = {
54
- returning: options?.capabilities?.returning ?? true,
55
- savepoints: options?.capabilities?.savepoints ?? true,
56
- upsert: options?.capabilities?.upsert ?? true,
57
- transactionalDdl: options?.capabilities?.transactionalDdl ?? true,
58
- migrationLock: options?.capabilities?.migrationLock ?? false,
76
+ returning: true,
77
+ savepoints: true,
78
+ upsert: true,
79
+ transactionalDdl: true,
80
+ migrationLock: false,
59
81
  }
60
82
  }
61
83
 
@@ -90,7 +112,7 @@ export class SqliteDatabaseAdapter implements DatabaseAdapter {
90
112
  let statement = this.compileSql(request.operation)[0]
91
113
  let prepared = this.#database.prepare(statement.text)
92
114
 
93
- if (prepared.reader) {
115
+ if (shouldReadStatement(request.operation, prepared)) {
94
116
  let rows = normalizeRows(prepared.all(...statement.values))
95
117
 
96
118
  if (request.operation.kind === 'count' || request.operation.kind === 'exists') {
@@ -183,7 +205,7 @@ export class SqliteDatabaseAdapter implements DatabaseAdapter {
183
205
  */
184
206
  async beginTransaction(options?: TransactionOptions): Promise<TransactionToken> {
185
207
  if (options?.isolationLevel === 'read uncommitted') {
186
- this.#database.pragma('read_uncommitted = true')
208
+ this.#database.exec('pragma read_uncommitted = true')
187
209
  }
188
210
 
189
211
  this.#database.exec('begin')
@@ -259,25 +281,21 @@ export class SqliteDatabaseAdapter implements DatabaseAdapter {
259
281
 
260
282
  /**
261
283
  * Creates a sqlite `DatabaseAdapter`.
262
- * @param database Better SQLite3 database instance.
263
- * @param options Optional adapter capability overrides.
284
+ * @param database Synchronous SQLite database client.
264
285
  * @returns A configured sqlite adapter.
265
286
  * @example
266
287
  * ```ts
267
- * import BetterSqlite3 from 'better-sqlite3'
288
+ * import { DatabaseSync } from 'node:sqlite'
268
289
  * import { createDatabase } from 'remix/data-table'
269
290
  * import { createSqliteDatabaseAdapter } from 'remix/data-table-sqlite'
270
291
  *
271
- * let sqlite = new BetterSqlite3('./data/app.db')
292
+ * let sqlite = new DatabaseSync('./data/app.db')
272
293
  * let adapter = createSqliteDatabaseAdapter(sqlite)
273
294
  * let db = createDatabase(adapter)
274
295
  * ```
275
296
  */
276
- export function createSqliteDatabaseAdapter(
277
- database: BetterSqliteDatabase,
278
- options?: SqliteDatabaseAdapterOptions,
279
- ): SqliteDatabaseAdapter {
280
- return new SqliteDatabaseAdapter(database, options)
297
+ export function createSqliteDatabaseAdapter(database: SqliteDatabase): SqliteDatabaseAdapter {
298
+ return new SqliteDatabaseAdapter(database)
281
299
  }
282
300
 
283
301
  function normalizeRows(rows: unknown[]): Record<string, unknown>[] {
@@ -350,7 +368,7 @@ function normalizeInsertIdForReader(
350
368
 
351
369
  function normalizeAffectedRowsForRun(
352
370
  kind: DataManipulationRequest['operation']['kind'],
353
- result: RunResult,
371
+ result: SqliteRunResult,
354
372
  ): number | undefined {
355
373
  if (kind === 'select' || kind === 'count' || kind === 'exists') {
356
374
  return undefined
@@ -362,7 +380,7 @@ function normalizeAffectedRowsForRun(
362
380
  function normalizeInsertIdForRun(
363
381
  kind: DataManipulationRequest['operation']['kind'],
364
382
  operation: DataManipulationRequest['operation'],
365
- result: RunResult,
383
+ result: SqliteRunResult,
366
384
  ): unknown {
367
385
  if (!isInsertOperationKind(kind) || !isInsertOperation(operation)) {
368
386
  return undefined
@@ -375,6 +393,33 @@ function normalizeInsertIdForRun(
375
393
  return result.lastInsertRowid
376
394
  }
377
395
 
396
+ function shouldReadStatement(
397
+ operation: DataManipulationRequest['operation'],
398
+ statement: SqliteStatement,
399
+ ): boolean {
400
+ if (operation.kind === 'select' || operation.kind === 'count' || operation.kind === 'exists') {
401
+ return true
402
+ }
403
+
404
+ if (operation.kind !== 'raw') {
405
+ return operation.returning !== undefined
406
+ }
407
+
408
+ if (typeof statement.reader === 'boolean') {
409
+ return statement.reader
410
+ }
411
+
412
+ if (statement.columns) {
413
+ return statement.columns().length > 0
414
+ }
415
+
416
+ try {
417
+ return statement.columnNames !== undefined && statement.columnNames.length > 0
418
+ } catch {
419
+ return false
420
+ }
421
+ }
422
+
378
423
  function quoteIdentifier(value: string): string {
379
424
  return '"' + value.replace(/"/g, '""') + '"'
380
425
  }