@remix-run/data-table-sqlite 0.0.0 → 0.1.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/LICENSE +21 -0
- package/README.md +75 -2
- package/dist/index.d.ts +3 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +1 -0
- package/dist/lib/adapter.d.ts +40 -0
- package/dist/lib/adapter.d.ts.map +1 -0
- package/dist/lib/adapter.js +171 -0
- package/dist/lib/sql-compiler.d.ts +8 -0
- package/dist/lib/sql-compiler.d.ts.map +1 -0
- package/dist/lib/sql-compiler.js +377 -0
- package/package.json +49 -7
- package/src/index.ts +2 -0
- package/src/lib/adapter.ts +263 -0
- package/src/lib/sql-compiler.ts +526 -0
|
@@ -0,0 +1,263 @@
|
|
|
1
|
+
import type {
|
|
2
|
+
AdapterCapabilityOverrides,
|
|
3
|
+
AdapterExecuteRequest,
|
|
4
|
+
AdapterResult,
|
|
5
|
+
DatabaseAdapter,
|
|
6
|
+
TransactionOptions,
|
|
7
|
+
TransactionToken,
|
|
8
|
+
} from '@remix-run/data-table'
|
|
9
|
+
import { getTablePrimaryKey } from '@remix-run/data-table'
|
|
10
|
+
import type { Database as BetterSqliteDatabase, RunResult } from 'better-sqlite3'
|
|
11
|
+
|
|
12
|
+
import { compileSqliteStatement } from './sql-compiler.ts'
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Better SQLite3 database handle accepted by the sqlite adapter.
|
|
16
|
+
*/
|
|
17
|
+
export type SqliteDatabaseConnection = BetterSqliteDatabase
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Sqlite adapter configuration.
|
|
21
|
+
*/
|
|
22
|
+
export type SqliteDatabaseAdapterOptions = {
|
|
23
|
+
capabilities?: AdapterCapabilityOverrides
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* `DatabaseAdapter` implementation for Better SQLite3.
|
|
28
|
+
*/
|
|
29
|
+
export class SqliteDatabaseAdapter implements DatabaseAdapter {
|
|
30
|
+
dialect = 'sqlite'
|
|
31
|
+
capabilities
|
|
32
|
+
|
|
33
|
+
#database: SqliteDatabaseConnection
|
|
34
|
+
#transactions = new Set<string>()
|
|
35
|
+
#transactionCounter = 0
|
|
36
|
+
|
|
37
|
+
constructor(database: SqliteDatabaseConnection, options?: SqliteDatabaseAdapterOptions) {
|
|
38
|
+
this.#database = database
|
|
39
|
+
this.capabilities = {
|
|
40
|
+
returning: options?.capabilities?.returning ?? true,
|
|
41
|
+
savepoints: options?.capabilities?.savepoints ?? true,
|
|
42
|
+
upsert: options?.capabilities?.upsert ?? true,
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
async execute(request: AdapterExecuteRequest): Promise<AdapterResult> {
|
|
47
|
+
if (request.statement.kind === 'insertMany' && request.statement.values.length === 0) {
|
|
48
|
+
return {
|
|
49
|
+
affectedRows: 0,
|
|
50
|
+
insertId: undefined,
|
|
51
|
+
rows: request.statement.returning ? [] : undefined,
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
let statement = compileSqliteStatement(request.statement)
|
|
56
|
+
let prepared = this.#database.prepare(statement.text)
|
|
57
|
+
|
|
58
|
+
if (prepared.reader) {
|
|
59
|
+
let rows = normalizeRows(prepared.all(...statement.values))
|
|
60
|
+
|
|
61
|
+
if (request.statement.kind === 'count' || request.statement.kind === 'exists') {
|
|
62
|
+
rows = normalizeCountRows(rows)
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
return {
|
|
66
|
+
rows,
|
|
67
|
+
affectedRows: normalizeAffectedRowsForReader(request.statement.kind, rows),
|
|
68
|
+
insertId: normalizeInsertIdForReader(request.statement.kind, request.statement, rows),
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
let result = prepared.run(...statement.values)
|
|
73
|
+
|
|
74
|
+
return {
|
|
75
|
+
affectedRows: normalizeAffectedRowsForRun(request.statement.kind, result),
|
|
76
|
+
insertId: normalizeInsertIdForRun(request.statement.kind, request.statement, result),
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
async beginTransaction(options?: TransactionOptions): Promise<TransactionToken> {
|
|
81
|
+
if (options?.isolationLevel === 'read uncommitted') {
|
|
82
|
+
this.#database.pragma('read_uncommitted = true')
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
this.#database.exec('begin')
|
|
86
|
+
|
|
87
|
+
this.#transactionCounter += 1
|
|
88
|
+
let token = { id: 'tx_' + String(this.#transactionCounter) }
|
|
89
|
+
this.#transactions.add(token.id)
|
|
90
|
+
|
|
91
|
+
return token
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
async commitTransaction(token: TransactionToken): Promise<void> {
|
|
95
|
+
this.#assertTransaction(token)
|
|
96
|
+
this.#database.exec('commit')
|
|
97
|
+
this.#transactions.delete(token.id)
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
async rollbackTransaction(token: TransactionToken): Promise<void> {
|
|
101
|
+
this.#assertTransaction(token)
|
|
102
|
+
this.#database.exec('rollback')
|
|
103
|
+
this.#transactions.delete(token.id)
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
async createSavepoint(token: TransactionToken, name: string): Promise<void> {
|
|
107
|
+
this.#assertTransaction(token)
|
|
108
|
+
this.#database.exec('savepoint ' + quoteIdentifier(name))
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
async rollbackToSavepoint(token: TransactionToken, name: string): Promise<void> {
|
|
112
|
+
this.#assertTransaction(token)
|
|
113
|
+
this.#database.exec('rollback to savepoint ' + quoteIdentifier(name))
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
async releaseSavepoint(token: TransactionToken, name: string): Promise<void> {
|
|
117
|
+
this.#assertTransaction(token)
|
|
118
|
+
this.#database.exec('release savepoint ' + quoteIdentifier(name))
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
#assertTransaction(token: TransactionToken): void {
|
|
122
|
+
if (!this.#transactions.has(token.id)) {
|
|
123
|
+
throw new Error('Unknown transaction token: ' + token.id)
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
/**
|
|
129
|
+
* Creates a sqlite `DatabaseAdapter`.
|
|
130
|
+
* @param database Better SQLite3 database instance.
|
|
131
|
+
* @param options Optional adapter capability overrides.
|
|
132
|
+
* @returns A configured sqlite adapter.
|
|
133
|
+
*/
|
|
134
|
+
export function createSqliteDatabaseAdapter(
|
|
135
|
+
database: SqliteDatabaseConnection,
|
|
136
|
+
options?: SqliteDatabaseAdapterOptions,
|
|
137
|
+
): SqliteDatabaseAdapter {
|
|
138
|
+
return new SqliteDatabaseAdapter(database, options)
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
function normalizeRows(rows: unknown[]): Record<string, unknown>[] {
|
|
142
|
+
return rows.map((row) => {
|
|
143
|
+
if (typeof row !== 'object' || row === null) {
|
|
144
|
+
return {}
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
return { ...(row as Record<string, unknown>) }
|
|
148
|
+
})
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
function normalizeCountRows(rows: Record<string, unknown>[]): Record<string, unknown>[] {
|
|
152
|
+
return rows.map((row) => {
|
|
153
|
+
let count = row.count
|
|
154
|
+
|
|
155
|
+
if (typeof count === 'string') {
|
|
156
|
+
let numeric = Number(count)
|
|
157
|
+
|
|
158
|
+
if (!Number.isNaN(numeric)) {
|
|
159
|
+
return {
|
|
160
|
+
...row,
|
|
161
|
+
count: numeric,
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
if (typeof count === 'bigint') {
|
|
167
|
+
return {
|
|
168
|
+
...row,
|
|
169
|
+
count: Number(count),
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
return row
|
|
174
|
+
})
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
function normalizeAffectedRowsForReader(
|
|
178
|
+
kind: AdapterExecuteRequest['statement']['kind'],
|
|
179
|
+
rows: Record<string, unknown>[],
|
|
180
|
+
): number | undefined {
|
|
181
|
+
if (isWriteStatementKind(kind)) {
|
|
182
|
+
return rows.length
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
return undefined
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
function normalizeInsertIdForReader(
|
|
189
|
+
kind: AdapterExecuteRequest['statement']['kind'],
|
|
190
|
+
statement: AdapterExecuteRequest['statement'],
|
|
191
|
+
rows: Record<string, unknown>[],
|
|
192
|
+
): unknown {
|
|
193
|
+
if (!isInsertStatementKind(kind) || !isInsertStatement(statement)) {
|
|
194
|
+
return undefined
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
let primaryKey = getTablePrimaryKey(statement.table)
|
|
198
|
+
|
|
199
|
+
if (primaryKey.length !== 1) {
|
|
200
|
+
return undefined
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
let key = primaryKey[0]
|
|
204
|
+
let row = rows[rows.length - 1]
|
|
205
|
+
|
|
206
|
+
return row ? row[key] : undefined
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
function normalizeAffectedRowsForRun(
|
|
210
|
+
kind: AdapterExecuteRequest['statement']['kind'],
|
|
211
|
+
result: RunResult,
|
|
212
|
+
): number | undefined {
|
|
213
|
+
if (kind === 'select' || kind === 'count' || kind === 'exists') {
|
|
214
|
+
return undefined
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
return result.changes
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
function normalizeInsertIdForRun(
|
|
221
|
+
kind: AdapterExecuteRequest['statement']['kind'],
|
|
222
|
+
statement: AdapterExecuteRequest['statement'],
|
|
223
|
+
result: RunResult,
|
|
224
|
+
): unknown {
|
|
225
|
+
if (!isInsertStatementKind(kind) || !isInsertStatement(statement)) {
|
|
226
|
+
return undefined
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
if (getTablePrimaryKey(statement.table).length !== 1) {
|
|
230
|
+
return undefined
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
return result.lastInsertRowid
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
function quoteIdentifier(value: string): string {
|
|
237
|
+
return '"' + value.replace(/"/g, '""') + '"'
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
function isWriteStatementKind(kind: AdapterExecuteRequest['statement']['kind']): boolean {
|
|
241
|
+
return (
|
|
242
|
+
kind === 'insert' ||
|
|
243
|
+
kind === 'insertMany' ||
|
|
244
|
+
kind === 'update' ||
|
|
245
|
+
kind === 'delete' ||
|
|
246
|
+
kind === 'upsert'
|
|
247
|
+
)
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
function isInsertStatementKind(kind: AdapterExecuteRequest['statement']['kind']): boolean {
|
|
251
|
+
return kind === 'insert' || kind === 'insertMany' || kind === 'upsert'
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
function isInsertStatement(
|
|
255
|
+
statement: AdapterExecuteRequest['statement'],
|
|
256
|
+
): statement is Extract<
|
|
257
|
+
AdapterExecuteRequest['statement'],
|
|
258
|
+
{ kind: 'insert' | 'insertMany' | 'upsert' }
|
|
259
|
+
> {
|
|
260
|
+
return (
|
|
261
|
+
statement.kind === 'insert' || statement.kind === 'insertMany' || statement.kind === 'upsert'
|
|
262
|
+
)
|
|
263
|
+
}
|