@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 +20 -7
- package/dist/index.d.ts +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/lib/adapter.d.ts +34 -8
- package/dist/lib/adapter.d.ts.map +1 -1
- package/dist/lib/adapter.js +34 -11
- package/package.json +8 -15
- package/src/index.ts +1 -0
- package/src/lib/adapter.ts +78 -17
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
|
+
|
|
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 '
|
|
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,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
|
|
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
|
|
package/dist/index.d.ts
CHANGED
package/dist/index.d.ts.map
CHANGED
|
@@ -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"}
|
package/dist/lib/adapter.d.ts
CHANGED
|
@@ -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
|
-
*
|
|
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:
|
|
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
|
|
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
|
|
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
|
|
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:
|
|
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;
|
|
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"}
|
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
|
/**
|
|
@@ -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
|
-
|
|
57
|
-
|
|
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(...
|
|
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.
|
|
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
|
|
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
|
|
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
|
|
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
|
+
"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
|
-
"
|
|
34
|
-
"@remix-run/
|
|
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.
|
|
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": "
|
|
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
|
@@ -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
|
-
*
|
|
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:
|
|
69
|
+
#database: SqliteDatabase
|
|
40
70
|
#transactions = new Set<string>()
|
|
41
71
|
#transactionCounter = 0
|
|
42
72
|
|
|
43
|
-
constructor(database:
|
|
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
|
|
86
|
-
let rows = normalizeRows(prepared.all(...
|
|
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(...
|
|
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.
|
|
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
|
|
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
|
|
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
|
|
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:
|
|
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:
|
|
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:
|
|
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
|
}
|