@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 +20 -19
- package/dist/index.d.ts +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/lib/adapter.d.ts +33 -13
- package/dist/lib/adapter.d.ts.map +1 -1
- package/dist/lib/adapter.js +34 -15
- package/package.json +7 -14
- package/src/index.ts +1 -1
- package/src/lib/adapter.ts +71 -26
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
|
|
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
|
|
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
|
|
22
|
+
npm i remix
|
|
23
23
|
```
|
|
24
24
|
|
|
25
25
|
## Usage
|
|
26
26
|
|
|
27
|
+
### Node
|
|
28
|
+
|
|
27
29
|
```ts
|
|
28
|
-
import
|
|
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
|
|
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
|
|
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
|
|
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
|
package/dist/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,
|
|
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"}
|
package/dist/lib/adapter.d.ts
CHANGED
|
@@ -1,13 +1,34 @@
|
|
|
1
|
-
import type {
|
|
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
|
-
*
|
|
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
|
|
7
|
-
|
|
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
|
|
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:
|
|
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
|
|
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
|
|
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
|
|
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:
|
|
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,
|
|
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"}
|
package/dist/lib/adapter.js
CHANGED
|
@@ -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
|
|
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
|
|
19
|
+
constructor(database) {
|
|
20
20
|
this.#database = database;
|
|
21
21
|
this.capabilities = {
|
|
22
|
-
returning:
|
|
23
|
-
savepoints:
|
|
24
|
-
upsert:
|
|
25
|
-
transactionalDdl:
|
|
26
|
-
migrationLock:
|
|
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
|
|
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.
|
|
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
|
|
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
|
|
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
|
|
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
|
|
209
|
-
return new SqliteDatabaseAdapter(database
|
|
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.
|
|
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
|
-
"
|
|
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": "
|
|
58
|
-
"test:
|
|
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
package/src/lib/adapter.ts
CHANGED
|
@@ -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
|
-
*
|
|
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
|
|
30
|
-
|
|
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
|
-
*
|
|
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:
|
|
69
|
+
#database: SqliteDatabase
|
|
48
70
|
#transactions = new Set<string>()
|
|
49
71
|
#transactionCounter = 0
|
|
50
72
|
|
|
51
|
-
constructor(database:
|
|
73
|
+
constructor(database: SqliteDatabase) {
|
|
52
74
|
this.#database = database
|
|
53
75
|
this.capabilities = {
|
|
54
|
-
returning:
|
|
55
|
-
savepoints:
|
|
56
|
-
upsert:
|
|
57
|
-
transactionalDdl:
|
|
58
|
-
migrationLock:
|
|
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
|
|
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.
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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:
|
|
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:
|
|
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
|
}
|