@tanstack/browser-db-sqlite-persistence 0.1.2
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 +54 -0
- package/dist/assets/opfs-worker-CCciqEMo.js +5879 -0
- package/dist/assets/opfs-worker-CCciqEMo.js.map +1 -0
- package/dist/cjs/browser-coordinator.cjs +541 -0
- package/dist/cjs/browser-coordinator.cjs.map +1 -0
- package/dist/cjs/browser-coordinator.d.cts +67 -0
- package/dist/cjs/browser-persistence.cjs +83 -0
- package/dist/cjs/browser-persistence.cjs.map +1 -0
- package/dist/cjs/browser-persistence.d.cts +16 -0
- package/dist/cjs/index.cjs +14 -0
- package/dist/cjs/index.cjs.map +1 -0
- package/dist/cjs/index.d.cts +8 -0
- package/dist/cjs/opfs-database.cjs +175 -0
- package/dist/cjs/opfs-database.cjs.map +1 -0
- package/dist/cjs/opfs-database.d.cts +10 -0
- package/dist/cjs/opfs-worker-protocol.d.cts +32 -0
- package/dist/cjs/opfs-worker.cjs +11 -0
- package/dist/cjs/opfs-worker.cjs.map +1 -0
- package/dist/cjs/opfs-worker.d.cts +1 -0
- package/dist/cjs/wa-sqlite-driver.cjs +106 -0
- package/dist/cjs/wa-sqlite-driver.cjs.map +1 -0
- package/dist/cjs/wa-sqlite-driver.d.cts +25 -0
- package/dist/esm/browser-coordinator.d.ts +67 -0
- package/dist/esm/browser-coordinator.js +541 -0
- package/dist/esm/browser-coordinator.js.map +1 -0
- package/dist/esm/browser-persistence.d.ts +16 -0
- package/dist/esm/browser-persistence.js +83 -0
- package/dist/esm/browser-persistence.js.map +1 -0
- package/dist/esm/index.d.ts +8 -0
- package/dist/esm/index.js +11 -0
- package/dist/esm/index.js.map +1 -0
- package/dist/esm/opfs-database.d.ts +10 -0
- package/dist/esm/opfs-database.js +175 -0
- package/dist/esm/opfs-database.js.map +1 -0
- package/dist/esm/opfs-worker-protocol.d.ts +32 -0
- package/dist/esm/opfs-worker.d.ts +1 -0
- package/dist/esm/opfs-worker.js +12 -0
- package/dist/esm/opfs-worker.js.map +1 -0
- package/dist/esm/wa-sqlite-driver.d.ts +25 -0
- package/dist/esm/wa-sqlite-driver.js +106 -0
- package/dist/esm/wa-sqlite-driver.js.map +1 -0
- package/package.json +63 -0
- package/src/browser-coordinator.ts +842 -0
- package/src/browser-persistence.ts +172 -0
- package/src/index.ts +15 -0
- package/src/opfs-database.ts +258 -0
- package/src/opfs-worker-protocol.ts +47 -0
- package/src/opfs-worker.ts +305 -0
- package/src/vite-env.d.ts +1 -0
- package/src/wa-sqlite-driver.ts +153 -0
- package/src/wa-sqlite-examples.d.ts +10 -0
|
@@ -0,0 +1,305 @@
|
|
|
1
|
+
import SQLiteESMFactory from '@journeyapps/wa-sqlite/dist/wa-sqlite.mjs'
|
|
2
|
+
import * as SQLite from '@journeyapps/wa-sqlite'
|
|
3
|
+
import { OPFSCoopSyncVFS } from '@journeyapps/wa-sqlite/src/examples/OPFSCoopSyncVFS.js'
|
|
4
|
+
import {
|
|
5
|
+
InvalidPersistedCollectionConfigError,
|
|
6
|
+
PersistenceUnavailableError,
|
|
7
|
+
} from '@tanstack/db-sqlite-persistence-core'
|
|
8
|
+
import type {
|
|
9
|
+
BrowserOPFSWorkerErrorCode,
|
|
10
|
+
BrowserOPFSWorkerRequest,
|
|
11
|
+
BrowserOPFSWorkerResponse,
|
|
12
|
+
} from './opfs-worker-protocol'
|
|
13
|
+
|
|
14
|
+
const workerGlobal = globalThis as typeof globalThis & {
|
|
15
|
+
postMessage: (message: BrowserOPFSWorkerResponse) => void
|
|
16
|
+
addEventListener: (
|
|
17
|
+
type: `message`,
|
|
18
|
+
listener: (event: MessageEvent<BrowserOPFSWorkerRequest>) => void,
|
|
19
|
+
) => void
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
const SQLITE_ROW = 100
|
|
23
|
+
const SQLITE_DONE = 101
|
|
24
|
+
|
|
25
|
+
type SQLiteCompatibleBinding =
|
|
26
|
+
| null
|
|
27
|
+
| string
|
|
28
|
+
| number
|
|
29
|
+
| bigint
|
|
30
|
+
| Uint8Array
|
|
31
|
+
| Array<number>
|
|
32
|
+
|
|
33
|
+
type WorkerOPFSGlobal = {
|
|
34
|
+
navigator?: {
|
|
35
|
+
storage?: {
|
|
36
|
+
getDirectory?: () => Promise<unknown>
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
FileSystemFileHandle?: {
|
|
40
|
+
prototype?: {
|
|
41
|
+
createSyncAccessHandle?: () => Promise<unknown>
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
let sqlite3Instance: ReturnType<typeof SQLite.Factory> | null = null
|
|
47
|
+
let sqliteDatabaseHandle: number | null = null
|
|
48
|
+
let opfsVfsInstance: { close?: () => Promise<void> | void } | null = null
|
|
49
|
+
|
|
50
|
+
function hasWorkerOPFSSyncAccessSupport(globalObject: unknown): boolean {
|
|
51
|
+
const candidate = globalObject as WorkerOPFSGlobal
|
|
52
|
+
const getDirectory = candidate.navigator?.storage?.getDirectory
|
|
53
|
+
const createSyncAccessHandle =
|
|
54
|
+
candidate.FileSystemFileHandle?.prototype?.createSyncAccessHandle
|
|
55
|
+
|
|
56
|
+
return (
|
|
57
|
+
typeof getDirectory === `function` &&
|
|
58
|
+
typeof createSyncAccessHandle === `function`
|
|
59
|
+
)
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
function toBindableValue(value: unknown): SQLiteCompatibleBinding {
|
|
63
|
+
if (value === null || value === undefined) {
|
|
64
|
+
return null
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
if (typeof value === `string`) {
|
|
68
|
+
return value
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
if (typeof value === `number`) {
|
|
72
|
+
return Number.isFinite(value) ? value : null
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
if (typeof value === `bigint`) {
|
|
76
|
+
return value
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
if (typeof value === `boolean`) {
|
|
80
|
+
return value ? 1 : 0
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
if (value instanceof Date) {
|
|
84
|
+
return value.toISOString()
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
if (value instanceof Uint8Array) {
|
|
88
|
+
return value
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
if (
|
|
92
|
+
Array.isArray(value) &&
|
|
93
|
+
value.every((entry) => typeof entry === `number` && Number.isFinite(entry))
|
|
94
|
+
) {
|
|
95
|
+
return [...value]
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
throw new InvalidPersistedCollectionConfigError(
|
|
99
|
+
`Unsupported parameter type for wa-sqlite binding`,
|
|
100
|
+
)
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
function toErrorCode(error: unknown): BrowserOPFSWorkerErrorCode {
|
|
104
|
+
if (error instanceof PersistenceUnavailableError) {
|
|
105
|
+
return `PERSISTENCE_UNAVAILABLE`
|
|
106
|
+
}
|
|
107
|
+
if (error instanceof InvalidPersistedCollectionConfigError) {
|
|
108
|
+
return `INVALID_CONFIG`
|
|
109
|
+
}
|
|
110
|
+
return `INTERNAL`
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
async function closeWorkerDatabase(): Promise<void> {
|
|
114
|
+
const sqlite3 = sqlite3Instance
|
|
115
|
+
const dbHandle = sqliteDatabaseHandle
|
|
116
|
+
const vfs = opfsVfsInstance
|
|
117
|
+
|
|
118
|
+
sqlite3Instance = null
|
|
119
|
+
sqliteDatabaseHandle = null
|
|
120
|
+
opfsVfsInstance = null
|
|
121
|
+
|
|
122
|
+
let closeError: unknown
|
|
123
|
+
if (sqlite3 && dbHandle !== null) {
|
|
124
|
+
try {
|
|
125
|
+
await sqlite3.close(dbHandle)
|
|
126
|
+
} catch (error) {
|
|
127
|
+
closeError = error
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
try {
|
|
132
|
+
await Promise.resolve(vfs?.close?.())
|
|
133
|
+
} catch (error) {
|
|
134
|
+
closeError ??= error
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
if (closeError) {
|
|
138
|
+
throw closeError
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
async function initializeWorkerDatabase(request: {
|
|
143
|
+
databaseName: string
|
|
144
|
+
vfsName: string
|
|
145
|
+
}): Promise<void> {
|
|
146
|
+
if (!hasWorkerOPFSSyncAccessSupport(workerGlobal)) {
|
|
147
|
+
throw new PersistenceUnavailableError(
|
|
148
|
+
`Browser OPFS sync access is not available in this worker runtime`,
|
|
149
|
+
)
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
await closeWorkerDatabase()
|
|
153
|
+
|
|
154
|
+
const sqliteModule = await SQLiteESMFactory()
|
|
155
|
+
const sqlite3 = SQLite.Factory(sqliteModule)
|
|
156
|
+
const opfsVfs = await OPFSCoopSyncVFS.create(request.vfsName, sqliteModule)
|
|
157
|
+
sqlite3.vfs_register(opfsVfs as never, true)
|
|
158
|
+
|
|
159
|
+
const openFlags =
|
|
160
|
+
SQLite.SQLITE_OPEN_CREATE |
|
|
161
|
+
SQLite.SQLITE_OPEN_READWRITE |
|
|
162
|
+
SQLite.SQLITE_OPEN_URI
|
|
163
|
+
const databaseFileUri = `file:${request.databaseName}?vfs=${encodeURIComponent(request.vfsName)}`
|
|
164
|
+
const dbHandle = await sqlite3.open_v2(
|
|
165
|
+
databaseFileUri,
|
|
166
|
+
openFlags,
|
|
167
|
+
request.vfsName,
|
|
168
|
+
)
|
|
169
|
+
|
|
170
|
+
sqlite3Instance = sqlite3
|
|
171
|
+
sqliteDatabaseHandle = dbHandle
|
|
172
|
+
opfsVfsInstance = opfsVfs
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
function getInitializedDatabaseState(): {
|
|
176
|
+
sqlite3: ReturnType<typeof SQLite.Factory>
|
|
177
|
+
dbHandle: number
|
|
178
|
+
} {
|
|
179
|
+
if (!sqlite3Instance || sqliteDatabaseHandle === null) {
|
|
180
|
+
throw new InvalidPersistedCollectionConfigError(
|
|
181
|
+
`OPFS worker database has not been initialized`,
|
|
182
|
+
)
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
return {
|
|
186
|
+
sqlite3: sqlite3Instance,
|
|
187
|
+
dbHandle: sqliteDatabaseHandle,
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
async function executeSqlInWorker(
|
|
192
|
+
sql: string,
|
|
193
|
+
params: ReadonlyArray<unknown>,
|
|
194
|
+
): Promise<ReadonlyArray<Record<string, unknown>>> {
|
|
195
|
+
const { sqlite3, dbHandle } = getInitializedDatabaseState()
|
|
196
|
+
const rows = new Array<Record<string, unknown>>()
|
|
197
|
+
let parametersBound = false
|
|
198
|
+
|
|
199
|
+
for await (const statement of sqlite3.statements(dbHandle, sql)) {
|
|
200
|
+
if (params.length > 0) {
|
|
201
|
+
if (parametersBound) {
|
|
202
|
+
throw new InvalidPersistedCollectionConfigError(
|
|
203
|
+
`wa-sqlite worker only supports parameter binding for a single SQL statement`,
|
|
204
|
+
)
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
sqlite3.bind_collection(
|
|
208
|
+
statement,
|
|
209
|
+
params.map((param) => toBindableValue(param)),
|
|
210
|
+
)
|
|
211
|
+
parametersBound = true
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
let columns = [...sqlite3.column_names(statement)]
|
|
215
|
+
for (;;) {
|
|
216
|
+
const stepResult = await sqlite3.step(statement)
|
|
217
|
+
|
|
218
|
+
if (stepResult === SQLITE_ROW) {
|
|
219
|
+
if (columns.length === 0) {
|
|
220
|
+
columns = [...sqlite3.column_names(statement)]
|
|
221
|
+
}
|
|
222
|
+
const values = sqlite3.row(statement)
|
|
223
|
+
const row: Record<string, unknown> = {}
|
|
224
|
+
for (let columnIndex = 0; columnIndex < columns.length; columnIndex++) {
|
|
225
|
+
const columnName = columns[columnIndex]
|
|
226
|
+
if (!columnName) {
|
|
227
|
+
continue
|
|
228
|
+
}
|
|
229
|
+
row[columnName] = values[columnIndex]
|
|
230
|
+
}
|
|
231
|
+
rows.push(row)
|
|
232
|
+
continue
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
if (stepResult === SQLITE_DONE) {
|
|
236
|
+
break
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
throw new InvalidPersistedCollectionConfigError(
|
|
240
|
+
`wa-sqlite step returned unexpected result code: ${String(stepResult)}`,
|
|
241
|
+
)
|
|
242
|
+
}
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
if (params.length > 0 && !parametersBound) {
|
|
246
|
+
throw new InvalidPersistedCollectionConfigError(
|
|
247
|
+
`SQL query parameters were provided but no statement accepted bindings`,
|
|
248
|
+
)
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
return rows
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
async function handleWorkerRequest(
|
|
255
|
+
request: BrowserOPFSWorkerRequest,
|
|
256
|
+
): Promise<BrowserOPFSWorkerResponse> {
|
|
257
|
+
try {
|
|
258
|
+
switch (request.type) {
|
|
259
|
+
case `init`:
|
|
260
|
+
await initializeWorkerDatabase({
|
|
261
|
+
databaseName: request.databaseName,
|
|
262
|
+
vfsName: request.vfsName,
|
|
263
|
+
})
|
|
264
|
+
return {
|
|
265
|
+
type: `response`,
|
|
266
|
+
requestId: request.requestId,
|
|
267
|
+
ok: true,
|
|
268
|
+
}
|
|
269
|
+
case `execute`: {
|
|
270
|
+
const rows = await executeSqlInWorker(request.sql, request.params)
|
|
271
|
+
return {
|
|
272
|
+
type: `response`,
|
|
273
|
+
requestId: request.requestId,
|
|
274
|
+
ok: true,
|
|
275
|
+
rows,
|
|
276
|
+
}
|
|
277
|
+
}
|
|
278
|
+
case `close`:
|
|
279
|
+
await closeWorkerDatabase()
|
|
280
|
+
return {
|
|
281
|
+
type: `response`,
|
|
282
|
+
requestId: request.requestId,
|
|
283
|
+
ok: true,
|
|
284
|
+
}
|
|
285
|
+
default:
|
|
286
|
+
throw new InvalidPersistedCollectionConfigError(
|
|
287
|
+
`Unknown OPFS worker request type`,
|
|
288
|
+
)
|
|
289
|
+
}
|
|
290
|
+
} catch (error) {
|
|
291
|
+
return {
|
|
292
|
+
type: `response`,
|
|
293
|
+
requestId: request.requestId,
|
|
294
|
+
ok: false,
|
|
295
|
+
code: toErrorCode(error),
|
|
296
|
+
error: (error as Error).message,
|
|
297
|
+
}
|
|
298
|
+
}
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
workerGlobal.addEventListener(`message`, (event) => {
|
|
302
|
+
void handleWorkerRequest(event.data).then((response) => {
|
|
303
|
+
workerGlobal.postMessage(response)
|
|
304
|
+
})
|
|
305
|
+
})
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
/// <reference types="vite/client" />
|
|
@@ -0,0 +1,153 @@
|
|
|
1
|
+
import { InvalidPersistedCollectionConfigError } from '@tanstack/db-sqlite-persistence-core'
|
|
2
|
+
import type { SQLiteDriver } from '@tanstack/db-sqlite-persistence-core'
|
|
3
|
+
|
|
4
|
+
export type BrowserWASQLiteDatabase = {
|
|
5
|
+
execute: <TRow = unknown>(
|
|
6
|
+
sql: string,
|
|
7
|
+
params?: ReadonlyArray<unknown>,
|
|
8
|
+
) => Promise<ReadonlyArray<TRow>>
|
|
9
|
+
close?: () => Promise<void> | void
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export type BrowserWASQLiteDriverOptions = {
|
|
13
|
+
database: BrowserWASQLiteDatabase
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
function assertTransactionCallbackHasDriverArg(
|
|
17
|
+
fn: (transactionDriver: SQLiteDriver) => Promise<unknown>,
|
|
18
|
+
): void {
|
|
19
|
+
if (fn.length > 0) {
|
|
20
|
+
return
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
throw new InvalidPersistedCollectionConfigError(
|
|
24
|
+
`SQLiteDriver.transaction callback must accept the transaction driver argument`,
|
|
25
|
+
)
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
function assertDatabaseShape(
|
|
29
|
+
database: BrowserWASQLiteDatabase,
|
|
30
|
+
): asserts database is BrowserWASQLiteDatabase {
|
|
31
|
+
if (typeof database.execute !== `function`) {
|
|
32
|
+
throw new InvalidPersistedCollectionConfigError(
|
|
33
|
+
`Browser wa-sqlite database handle must provide execute(sql, params?)`,
|
|
34
|
+
)
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
export class BrowserWASQLiteDriver implements SQLiteDriver {
|
|
39
|
+
private readonly database: BrowserWASQLiteDatabase
|
|
40
|
+
private queue: Promise<void> = Promise.resolve()
|
|
41
|
+
private nextSavepointId = 1
|
|
42
|
+
private closed = false
|
|
43
|
+
|
|
44
|
+
constructor(options: BrowserWASQLiteDriverOptions) {
|
|
45
|
+
assertDatabaseShape(options.database)
|
|
46
|
+
this.database = options.database
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
async exec(sql: string): Promise<void> {
|
|
50
|
+
await this.enqueue(async () => {
|
|
51
|
+
await this.database.execute(sql)
|
|
52
|
+
})
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
async query<T>(
|
|
56
|
+
sql: string,
|
|
57
|
+
params: ReadonlyArray<unknown> = [],
|
|
58
|
+
): Promise<ReadonlyArray<T>> {
|
|
59
|
+
return this.enqueue(() => this.database.execute<T>(sql, params))
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
async run(sql: string, params: ReadonlyArray<unknown> = []): Promise<void> {
|
|
63
|
+
await this.enqueue(async () => {
|
|
64
|
+
await this.database.execute(sql, params)
|
|
65
|
+
})
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
async transaction<T>(
|
|
69
|
+
fn: (transactionDriver: SQLiteDriver) => Promise<T>,
|
|
70
|
+
): Promise<T> {
|
|
71
|
+
assertTransactionCallbackHasDriverArg(fn)
|
|
72
|
+
|
|
73
|
+
return this.enqueue(async () => {
|
|
74
|
+
await this.database.execute(`BEGIN IMMEDIATE`)
|
|
75
|
+
try {
|
|
76
|
+
const result = await fn(this.createTransactionDriver())
|
|
77
|
+
await this.database.execute(`COMMIT`)
|
|
78
|
+
return result
|
|
79
|
+
} catch (error) {
|
|
80
|
+
try {
|
|
81
|
+
await this.database.execute(`ROLLBACK`)
|
|
82
|
+
} catch {
|
|
83
|
+
// Preserve original transaction error.
|
|
84
|
+
}
|
|
85
|
+
throw error
|
|
86
|
+
}
|
|
87
|
+
})
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
async transactionWithDriver<T>(
|
|
91
|
+
fn: (transactionDriver: SQLiteDriver) => Promise<T>,
|
|
92
|
+
): Promise<T> {
|
|
93
|
+
return this.transaction(fn)
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
async close(): Promise<void> {
|
|
97
|
+
if (this.closed) {
|
|
98
|
+
return
|
|
99
|
+
}
|
|
100
|
+
this.closed = true
|
|
101
|
+
|
|
102
|
+
if (typeof this.database.close === `function`) {
|
|
103
|
+
await Promise.resolve(this.database.close())
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
getDatabase(): BrowserWASQLiteDatabase {
|
|
108
|
+
return this.database
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
private createTransactionDriver(): SQLiteDriver {
|
|
112
|
+
return {
|
|
113
|
+
exec: (sql) => this.database.execute(sql).then(() => undefined),
|
|
114
|
+
query: <T>(sql: string, params: ReadonlyArray<unknown> = []) =>
|
|
115
|
+
this.database.execute<T>(sql, params),
|
|
116
|
+
run: (sql: string, params: ReadonlyArray<unknown> = []) =>
|
|
117
|
+
this.database.execute(sql, params).then(() => undefined),
|
|
118
|
+
transaction: <T>(fn: (transactionDriver: SQLiteDriver) => Promise<T>) =>
|
|
119
|
+
this.runNestedTransaction(fn),
|
|
120
|
+
transactionWithDriver: <T>(
|
|
121
|
+
fn: (transactionDriver: SQLiteDriver) => Promise<T>,
|
|
122
|
+
) => this.runNestedTransaction(fn),
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
private async runNestedTransaction<T>(
|
|
127
|
+
fn: (transactionDriver: SQLiteDriver) => Promise<T>,
|
|
128
|
+
): Promise<T> {
|
|
129
|
+
assertTransactionCallbackHasDriverArg(fn)
|
|
130
|
+
|
|
131
|
+
const savepointName = `tsdb_sp_${this.nextSavepointId}`
|
|
132
|
+
this.nextSavepointId++
|
|
133
|
+
await this.database.execute(`SAVEPOINT ${savepointName}`)
|
|
134
|
+
try {
|
|
135
|
+
const result = await fn(this.createTransactionDriver())
|
|
136
|
+
await this.database.execute(`RELEASE SAVEPOINT ${savepointName}`)
|
|
137
|
+
return result
|
|
138
|
+
} catch (error) {
|
|
139
|
+
await this.database.execute(`ROLLBACK TO SAVEPOINT ${savepointName}`)
|
|
140
|
+
await this.database.execute(`RELEASE SAVEPOINT ${savepointName}`)
|
|
141
|
+
throw error
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
private enqueue<T>(operation: () => Promise<T> | T): Promise<T> {
|
|
146
|
+
const queuedOperation = this.queue.then(operation, operation)
|
|
147
|
+
this.queue = queuedOperation.then(
|
|
148
|
+
() => undefined,
|
|
149
|
+
() => undefined,
|
|
150
|
+
)
|
|
151
|
+
return queuedOperation
|
|
152
|
+
}
|
|
153
|
+
}
|