@livestore/wa-sqlite 0.4.0-dev.22 → 0.4.0-dev.23
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 +46 -36
- package/dist/README.md +13 -13
- package/dist/fts5/wa-sqlite.mjs +1 -1
- package/dist/fts5/wa-sqlite.node.mjs +1 -1
- package/dist/fts5/wa-sqlite.node.wasm +0 -0
- package/dist/fts5/wa-sqlite.wasm +0 -0
- package/dist/wa-sqlite-async.mjs +1 -1
- package/dist/wa-sqlite-async.wasm +0 -0
- package/dist/wa-sqlite-jspi.mjs +1 -1
- package/dist/wa-sqlite-jspi.wasm +0 -0
- package/dist/wa-sqlite.mjs +1 -1
- package/dist/wa-sqlite.node.mjs +1 -1
- package/dist/wa-sqlite.node.wasm +0 -0
- package/dist/wa-sqlite.wasm +0 -0
- package/package.json +40 -29
- package/src/FacadeVFS.js +252 -261
- package/src/VFS.js +84 -85
- package/src/WebLocksMixin.js +357 -351
- package/src/examples/AccessHandlePoolVFS.js +185 -194
- package/src/examples/IDBBatchAtomicVFS.js +429 -409
- package/src/examples/IDBMirrorVFS.js +402 -409
- package/src/examples/MemoryAsyncVFS.js +32 -37
- package/src/examples/MemoryVFS.js +71 -75
- package/src/examples/OPFSAdaptiveVFS.js +206 -206
- package/src/examples/OPFSAnyContextVFS.js +141 -140
- package/src/examples/OPFSCoopSyncVFS.js +297 -299
- package/src/examples/OPFSPermutedVFS.js +529 -540
- package/src/examples/README.md +27 -15
- package/src/examples/tag.js +27 -27
- package/src/sqlite-api.js +910 -941
- package/src/sqlite-constants.js +246 -232
- package/src/types/globals.d.ts +52 -52
- package/src/types/index.d.ts +586 -576
- package/test/AccessHandlePoolVFS.test.js +21 -21
- package/test/IDBBatchAtomicVFS.test.js +69 -69
- package/test/IDBMirrorVFS.test.js +21 -21
- package/test/MemoryAsyncVFS.test.js +21 -21
- package/test/MemoryVFS.test.js +21 -21
- package/test/OPFSAdaptiveVFS.test.js +21 -21
- package/test/OPFSAnyContextVFS.test.js +21 -21
- package/test/OPFSCoopSyncVFS.test.js +21 -21
- package/test/OPFSPermutedVFS.test.js +21 -21
- package/test/TestContext.js +44 -41
- package/test/WebLocksMixin.test.js +369 -360
- package/test/api.test.js +23 -23
- package/test/api_exec.js +72 -61
- package/test/api_misc.js +53 -54
- package/test/api_statements.js +271 -279
- package/test/callbacks.test.js +492 -478
- package/test/data/idbv5.json +1135 -1
- package/test/sql.test.js +30 -30
- package/test/sql_0001.js +49 -33
- package/test/sql_0002.js +55 -34
- package/test/sql_0003.js +85 -49
- package/test/sql_0004.js +76 -47
- package/test/sql_0005.js +60 -44
- package/test/test-worker.js +171 -163
- package/test/vfs_xAccess.js +1 -2
- package/test/vfs_xClose.js +50 -49
- package/test/vfs_xOpen.js +73 -72
- package/test/vfs_xRead.js +31 -31
- package/test/vfs_xWrite.js +30 -29
|
@@ -1,35 +1,34 @@
|
|
|
1
1
|
// Copyright 2024 Roy T. Hashimoto. All Rights Reserved.
|
|
2
|
-
import { FacadeVFS } from '../FacadeVFS.js'
|
|
3
|
-
import * as VFS from '../VFS.js'
|
|
4
|
-
import { WebLocksMixin } from '../WebLocksMixin.js'
|
|
2
|
+
import { FacadeVFS } from '../FacadeVFS.js'
|
|
3
|
+
import * as VFS from '../VFS.js'
|
|
4
|
+
import { WebLocksMixin } from '../WebLocksMixin.js'
|
|
5
5
|
|
|
6
|
-
const LOCK_NOTIFY_INTERVAL = 1000
|
|
6
|
+
const LOCK_NOTIFY_INTERVAL = 1000
|
|
7
7
|
|
|
8
|
-
const hasUnsafeAccessHandle =
|
|
9
|
-
globalThis.FileSystemSyncAccessHandle.prototype.hasOwnProperty('mode');
|
|
8
|
+
const hasUnsafeAccessHandle = globalThis.FileSystemSyncAccessHandle.prototype.hasOwnProperty('mode')
|
|
10
9
|
|
|
11
10
|
/**
|
|
12
|
-
* @param {string} pathname
|
|
13
|
-
* @param {boolean} create
|
|
11
|
+
* @param {string} pathname
|
|
12
|
+
* @param {boolean} create
|
|
14
13
|
* @returns {Promise<[FileSystemDirectoryHandle, string]>}
|
|
15
14
|
*/
|
|
16
15
|
async function getPathComponents(pathname, create) {
|
|
17
|
-
const [_, directories, filename] = pathname.match(/[/]?(.*)[/](.*)$/)
|
|
16
|
+
const [_, directories, filename] = pathname.match(/[/]?(.*)[/](.*)$/)
|
|
18
17
|
|
|
19
|
-
let directoryHandle = await navigator.storage.getDirectory()
|
|
18
|
+
let directoryHandle = await navigator.storage.getDirectory()
|
|
20
19
|
for (const directory of directories.split('/')) {
|
|
21
20
|
if (directory) {
|
|
22
|
-
directoryHandle = await directoryHandle.getDirectoryHandle(directory, { create })
|
|
21
|
+
directoryHandle = await directoryHandle.getDirectoryHandle(directory, { create })
|
|
23
22
|
}
|
|
24
23
|
}
|
|
25
|
-
return [directoryHandle, filename]
|
|
26
|
-
}
|
|
24
|
+
return [directoryHandle, filename]
|
|
25
|
+
}
|
|
27
26
|
|
|
28
27
|
class File {
|
|
29
|
-
/** @type {string} */ pathname
|
|
30
|
-
/** @type {number} */ flags
|
|
31
|
-
/** @type {FileSystemFileHandle} */ fileHandle
|
|
32
|
-
/** @type {FileSystemSyncAccessHandle} */ accessHandle
|
|
28
|
+
/** @type {string} */ pathname
|
|
29
|
+
/** @type {number} */ flags
|
|
30
|
+
/** @type {FileSystemFileHandle} */ fileHandle
|
|
31
|
+
/** @type {FileSystemSyncAccessHandle} */ accessHandle
|
|
33
32
|
|
|
34
33
|
// The rest of the properties are for platforms without readwrite-unsafe
|
|
35
34
|
// access handles. Only one connection can have an open access handle
|
|
@@ -37,349 +36,348 @@ class File {
|
|
|
37
36
|
//
|
|
38
37
|
// Opening and closing the access handle is expensive so we leave the
|
|
39
38
|
// handle open unless another connection signals on BroadcastChannel.
|
|
40
|
-
/** @type {BroadcastChannel} */ handleRequestChannel
|
|
41
|
-
/** @type {function} */ handleLockReleaser = null
|
|
42
|
-
/** @type {boolean} */ isHandleRequested = false
|
|
43
|
-
/** @type {boolean} */ isFileLocked = false
|
|
39
|
+
/** @type {BroadcastChannel} */ handleRequestChannel
|
|
40
|
+
/** @type {function} */ handleLockReleaser = null
|
|
41
|
+
/** @type {boolean} */ isHandleRequested = false
|
|
42
|
+
/** @type {boolean} */ isFileLocked = false
|
|
44
43
|
|
|
45
44
|
// SQLite makes one read on file open that is not protected by a lock.
|
|
46
45
|
// This needs to be handled as a special case.
|
|
47
|
-
/** @type {function} */ openLockReleaser = null
|
|
46
|
+
/** @type {function} */ openLockReleaser = null
|
|
48
47
|
|
|
49
48
|
constructor(pathname, flags) {
|
|
50
|
-
this.pathname = pathname
|
|
51
|
-
this.flags = flags
|
|
49
|
+
this.pathname = pathname
|
|
50
|
+
this.flags = flags
|
|
52
51
|
}
|
|
53
52
|
}
|
|
54
53
|
|
|
55
54
|
export class OPFSAdaptiveVFS extends WebLocksMixin(FacadeVFS) {
|
|
56
|
-
/** @type {Map<number, File>} */ mapIdToFile = new Map()
|
|
57
|
-
lastError = null
|
|
55
|
+
/** @type {Map<number, File>} */ mapIdToFile = new Map()
|
|
56
|
+
lastError = null
|
|
58
57
|
|
|
59
|
-
log = null
|
|
58
|
+
log = null
|
|
60
59
|
|
|
61
60
|
static async create(name, module, options) {
|
|
62
|
-
const vfs = new OPFSAdaptiveVFS(name, module, options)
|
|
63
|
-
await vfs.isReady()
|
|
64
|
-
return vfs
|
|
61
|
+
const vfs = new OPFSAdaptiveVFS(name, module, options)
|
|
62
|
+
await vfs.isReady()
|
|
63
|
+
return vfs
|
|
65
64
|
}
|
|
66
65
|
|
|
67
66
|
constructor(name, module, options = {}) {
|
|
68
|
-
super(name, module, options)
|
|
67
|
+
super(name, module, options)
|
|
69
68
|
}
|
|
70
|
-
|
|
69
|
+
|
|
71
70
|
getFilename(fileId) {
|
|
72
|
-
const pathname = this.mapIdToFile.get(fileId).pathname
|
|
71
|
+
const pathname = this.mapIdToFile.get(fileId).pathname
|
|
73
72
|
return `OPFS:${pathname}`
|
|
74
73
|
}
|
|
75
74
|
|
|
76
75
|
/**
|
|
77
|
-
* @param {string?} zName
|
|
78
|
-
* @param {number} fileId
|
|
79
|
-
* @param {number} flags
|
|
80
|
-
* @param {DataView} pOutFlags
|
|
76
|
+
* @param {string?} zName
|
|
77
|
+
* @param {number} fileId
|
|
78
|
+
* @param {number} flags
|
|
79
|
+
* @param {DataView} pOutFlags
|
|
81
80
|
* @returns {Promise<number>}
|
|
82
81
|
*/
|
|
83
82
|
async jOpen(zName, fileId, flags, pOutFlags) {
|
|
84
83
|
try {
|
|
85
|
-
const url = new URL(zName || Math.random().toString(36).slice(2), 'file://')
|
|
86
|
-
const pathname = url.pathname
|
|
84
|
+
const url = new URL(zName || Math.random().toString(36).slice(2), 'file://')
|
|
85
|
+
const pathname = url.pathname
|
|
87
86
|
|
|
88
|
-
const file = new File(pathname, flags)
|
|
89
|
-
this.mapIdToFile.set(fileId, file)
|
|
87
|
+
const file = new File(pathname, flags)
|
|
88
|
+
this.mapIdToFile.set(fileId, file)
|
|
90
89
|
|
|
91
|
-
const create = !!(flags & VFS.SQLITE_OPEN_CREATE)
|
|
92
|
-
const [directoryHandle, filename] = await getPathComponents(pathname, create)
|
|
93
|
-
file.fileHandle = await directoryHandle.getFileHandle(filename, { create })
|
|
90
|
+
const create = !!(flags & VFS.SQLITE_OPEN_CREATE)
|
|
91
|
+
const [directoryHandle, filename] = await getPathComponents(pathname, create)
|
|
92
|
+
file.fileHandle = await directoryHandle.getFileHandle(filename, { create })
|
|
94
93
|
|
|
95
|
-
if (
|
|
96
|
-
file.handleRequestChannel = new BroadcastChannel(this.getFilename(fileId))
|
|
94
|
+
if (flags & VFS.SQLITE_OPEN_MAIN_DB && !hasUnsafeAccessHandle) {
|
|
95
|
+
file.handleRequestChannel = new BroadcastChannel(this.getFilename(fileId))
|
|
97
96
|
|
|
98
97
|
// Acquire the access handle lock. The first read of a database
|
|
99
98
|
// file is done outside xLock/xUnlock so we get that lock here.
|
|
100
99
|
function notify() {
|
|
101
|
-
file.handleRequestChannel.postMessage(null)
|
|
100
|
+
file.handleRequestChannel.postMessage(null)
|
|
102
101
|
}
|
|
103
|
-
const notifyId = setInterval(notify, LOCK_NOTIFY_INTERVAL)
|
|
104
|
-
setTimeout(notify)
|
|
102
|
+
const notifyId = setInterval(notify, LOCK_NOTIFY_INTERVAL)
|
|
103
|
+
setTimeout(notify)
|
|
105
104
|
|
|
106
105
|
file.openLockReleaser = await new Promise((resolve, reject) => {
|
|
107
|
-
navigator.locks.request(this.getFilename(fileId), lock => {
|
|
108
|
-
clearInterval(notifyId)
|
|
109
|
-
if (!lock) return reject()
|
|
110
|
-
return new Promise(release => {
|
|
111
|
-
resolve(release)
|
|
112
|
-
})
|
|
113
|
-
})
|
|
114
|
-
})
|
|
115
|
-
this.log?.('access handle acquired for open')
|
|
106
|
+
navigator.locks.request(this.getFilename(fileId), (lock) => {
|
|
107
|
+
clearInterval(notifyId)
|
|
108
|
+
if (!lock) return reject()
|
|
109
|
+
return new Promise((release) => {
|
|
110
|
+
resolve(release)
|
|
111
|
+
})
|
|
112
|
+
})
|
|
113
|
+
})
|
|
114
|
+
this.log?.('access handle acquired for open')
|
|
116
115
|
}
|
|
117
116
|
|
|
118
117
|
// @ts-ignore
|
|
119
118
|
file.accessHandle = await file.fileHandle.createSyncAccessHandle({
|
|
120
|
-
mode: 'readwrite-unsafe'
|
|
121
|
-
})
|
|
122
|
-
|
|
123
|
-
pOutFlags.setInt32(0, flags, true)
|
|
124
|
-
return VFS.SQLITE_OK
|
|
119
|
+
mode: 'readwrite-unsafe',
|
|
120
|
+
})
|
|
121
|
+
|
|
122
|
+
pOutFlags.setInt32(0, flags, true)
|
|
123
|
+
return VFS.SQLITE_OK
|
|
125
124
|
} catch (e) {
|
|
126
|
-
this.lastError = e
|
|
127
|
-
return VFS.SQLITE_CANTOPEN
|
|
125
|
+
this.lastError = e
|
|
126
|
+
return VFS.SQLITE_CANTOPEN
|
|
128
127
|
}
|
|
129
128
|
}
|
|
130
129
|
|
|
131
130
|
/**
|
|
132
|
-
* @param {string} zName
|
|
133
|
-
* @param {number} syncDir
|
|
131
|
+
* @param {string} zName
|
|
132
|
+
* @param {number} syncDir
|
|
134
133
|
* @returns {Promise<number>}
|
|
135
134
|
*/
|
|
136
135
|
async jDelete(zName, syncDir) {
|
|
137
136
|
try {
|
|
138
|
-
const url = new URL(zName, 'file://')
|
|
139
|
-
const pathname = url.pathname
|
|
140
|
-
|
|
141
|
-
const [directoryHandle, name] = await getPathComponents(pathname, false)
|
|
142
|
-
const result = directoryHandle.removeEntry(name, { recursive: false })
|
|
137
|
+
const url = new URL(zName, 'file://')
|
|
138
|
+
const pathname = url.pathname
|
|
139
|
+
|
|
140
|
+
const [directoryHandle, name] = await getPathComponents(pathname, false)
|
|
141
|
+
const result = directoryHandle.removeEntry(name, { recursive: false })
|
|
143
142
|
if (syncDir) {
|
|
144
|
-
await result
|
|
143
|
+
await result
|
|
145
144
|
}
|
|
146
|
-
return VFS.SQLITE_OK
|
|
145
|
+
return VFS.SQLITE_OK
|
|
147
146
|
} catch (e) {
|
|
148
|
-
return VFS.SQLITE_IOERR_DELETE
|
|
147
|
+
return VFS.SQLITE_IOERR_DELETE
|
|
149
148
|
}
|
|
150
149
|
}
|
|
151
150
|
|
|
152
151
|
/**
|
|
153
|
-
* @param {string} zName
|
|
154
|
-
* @param {number} flags
|
|
155
|
-
* @param {DataView} pResOut
|
|
152
|
+
* @param {string} zName
|
|
153
|
+
* @param {number} flags
|
|
154
|
+
* @param {DataView} pResOut
|
|
156
155
|
* @returns {Promise<number>}
|
|
157
156
|
*/
|
|
158
157
|
async jAccess(zName, flags, pResOut) {
|
|
159
158
|
try {
|
|
160
|
-
const url = new URL(zName, 'file://')
|
|
161
|
-
const pathname = url.pathname
|
|
159
|
+
const url = new URL(zName, 'file://')
|
|
160
|
+
const pathname = url.pathname
|
|
162
161
|
|
|
163
|
-
const [directoryHandle, dbName] = await getPathComponents(pathname, false)
|
|
164
|
-
const fileHandle = await directoryHandle.getFileHandle(dbName, { create: false })
|
|
165
|
-
pResOut.setInt32(0, 1, true)
|
|
166
|
-
return VFS.SQLITE_OK
|
|
162
|
+
const [directoryHandle, dbName] = await getPathComponents(pathname, false)
|
|
163
|
+
const fileHandle = await directoryHandle.getFileHandle(dbName, { create: false })
|
|
164
|
+
pResOut.setInt32(0, 1, true)
|
|
165
|
+
return VFS.SQLITE_OK
|
|
167
166
|
} catch (e) {
|
|
168
167
|
if (e.name === 'NotFoundError') {
|
|
169
|
-
pResOut.setInt32(0, 0, true)
|
|
170
|
-
return VFS.SQLITE_OK
|
|
168
|
+
pResOut.setInt32(0, 0, true)
|
|
169
|
+
return VFS.SQLITE_OK
|
|
171
170
|
}
|
|
172
|
-
this.lastError = e
|
|
173
|
-
return VFS.SQLITE_IOERR_ACCESS
|
|
171
|
+
this.lastError = e
|
|
172
|
+
return VFS.SQLITE_IOERR_ACCESS
|
|
174
173
|
}
|
|
175
174
|
}
|
|
176
175
|
|
|
177
176
|
/**
|
|
178
|
-
* @param {number} fileId
|
|
177
|
+
* @param {number} fileId
|
|
179
178
|
* @returns {Promise<number>}
|
|
180
179
|
*/
|
|
181
180
|
async jClose(fileId) {
|
|
182
181
|
try {
|
|
183
|
-
const file = this.mapIdToFile.get(fileId)
|
|
184
|
-
this.mapIdToFile.delete(fileId)
|
|
185
|
-
await file?.accessHandle?.close()
|
|
182
|
+
const file = this.mapIdToFile.get(fileId)
|
|
183
|
+
this.mapIdToFile.delete(fileId)
|
|
184
|
+
await file?.accessHandle?.close()
|
|
186
185
|
|
|
187
186
|
if (file?.flags & VFS.SQLITE_OPEN_DELETEONCLOSE) {
|
|
188
|
-
const [directoryHandle, name] = await getPathComponents(file.pathname, false)
|
|
189
|
-
await directoryHandle.removeEntry(name, { recursive: false })
|
|
187
|
+
const [directoryHandle, name] = await getPathComponents(file.pathname, false)
|
|
188
|
+
await directoryHandle.removeEntry(name, { recursive: false })
|
|
190
189
|
}
|
|
191
|
-
return VFS.SQLITE_OK
|
|
190
|
+
return VFS.SQLITE_OK
|
|
192
191
|
} catch (e) {
|
|
193
|
-
return VFS.SQLITE_IOERR_DELETE
|
|
192
|
+
return VFS.SQLITE_IOERR_DELETE
|
|
194
193
|
}
|
|
195
194
|
}
|
|
196
195
|
|
|
197
196
|
/**
|
|
198
|
-
* @param {number} fileId
|
|
199
|
-
* @param {Uint8Array} pData
|
|
197
|
+
* @param {number} fileId
|
|
198
|
+
* @param {Uint8Array} pData
|
|
200
199
|
* @param {number} iOffset
|
|
201
200
|
* @returns {number}
|
|
202
201
|
*/
|
|
203
202
|
jRead(fileId, pData, iOffset) {
|
|
204
203
|
try {
|
|
205
|
-
const file = this.mapIdToFile.get(fileId)
|
|
204
|
+
const file = this.mapIdToFile.get(fileId)
|
|
206
205
|
|
|
207
206
|
// On Chrome (at least), passing pData to accessHandle.read() is
|
|
208
207
|
// an error because pData is a Proxy of a Uint8Array. Calling
|
|
209
208
|
// subarray() produces a real Uint8Array and that works.
|
|
210
|
-
const bytesRead = file.accessHandle.read(pData.subarray(), { at: iOffset })
|
|
209
|
+
const bytesRead = file.accessHandle.read(pData.subarray(), { at: iOffset })
|
|
211
210
|
if (file.openLockReleaser) {
|
|
212
211
|
// We obtained the access handle on file open.
|
|
213
|
-
file.accessHandle.close()
|
|
214
|
-
file.accessHandle = null
|
|
215
|
-
file.openLockReleaser()
|
|
216
|
-
file.openLockReleaser = null
|
|
217
|
-
this.log?.('access handle released for open')
|
|
212
|
+
file.accessHandle.close()
|
|
213
|
+
file.accessHandle = null
|
|
214
|
+
file.openLockReleaser()
|
|
215
|
+
file.openLockReleaser = null
|
|
216
|
+
this.log?.('access handle released for open')
|
|
218
217
|
}
|
|
219
218
|
|
|
220
219
|
if (bytesRead < pData.byteLength) {
|
|
221
|
-
pData.fill(0, bytesRead)
|
|
222
|
-
return VFS.SQLITE_IOERR_SHORT_READ
|
|
220
|
+
pData.fill(0, bytesRead)
|
|
221
|
+
return VFS.SQLITE_IOERR_SHORT_READ
|
|
223
222
|
}
|
|
224
|
-
return VFS.SQLITE_OK
|
|
223
|
+
return VFS.SQLITE_OK
|
|
225
224
|
} catch (e) {
|
|
226
|
-
this.lastError = e
|
|
227
|
-
return VFS.SQLITE_IOERR_READ
|
|
225
|
+
this.lastError = e
|
|
226
|
+
return VFS.SQLITE_IOERR_READ
|
|
228
227
|
}
|
|
229
228
|
}
|
|
230
229
|
|
|
231
230
|
/**
|
|
232
|
-
* @param {number} fileId
|
|
233
|
-
* @param {Uint8Array} pData
|
|
231
|
+
* @param {number} fileId
|
|
232
|
+
* @param {Uint8Array} pData
|
|
234
233
|
* @param {number} iOffset
|
|
235
234
|
* @returns {number}
|
|
236
235
|
*/
|
|
237
236
|
jWrite(fileId, pData, iOffset) {
|
|
238
237
|
try {
|
|
239
|
-
const file = this.mapIdToFile.get(fileId)
|
|
238
|
+
const file = this.mapIdToFile.get(fileId)
|
|
240
239
|
|
|
241
240
|
// On Chrome (at least), passing pData to accessHandle.write() is
|
|
242
241
|
// an error because pData is a Proxy of a Uint8Array. Calling
|
|
243
242
|
// subarray() produces a real Uint8Array and that works.
|
|
244
|
-
file.accessHandle.write(pData.subarray(), { at: iOffset })
|
|
245
|
-
return VFS.SQLITE_OK
|
|
243
|
+
file.accessHandle.write(pData.subarray(), { at: iOffset })
|
|
244
|
+
return VFS.SQLITE_OK
|
|
246
245
|
} catch (e) {
|
|
247
|
-
this.lastError = e
|
|
248
|
-
return VFS.SQLITE_IOERR_WRITE
|
|
246
|
+
this.lastError = e
|
|
247
|
+
return VFS.SQLITE_IOERR_WRITE
|
|
249
248
|
}
|
|
250
249
|
}
|
|
251
250
|
|
|
252
251
|
/**
|
|
253
|
-
* @param {number} fileId
|
|
254
|
-
* @param {number} iSize
|
|
252
|
+
* @param {number} fileId
|
|
253
|
+
* @param {number} iSize
|
|
255
254
|
* @returns {number}
|
|
256
255
|
*/
|
|
257
256
|
jTruncate(fileId, iSize) {
|
|
258
257
|
try {
|
|
259
|
-
const file = this.mapIdToFile.get(fileId)
|
|
260
|
-
file.accessHandle.truncate(iSize)
|
|
261
|
-
return VFS.SQLITE_OK
|
|
258
|
+
const file = this.mapIdToFile.get(fileId)
|
|
259
|
+
file.accessHandle.truncate(iSize)
|
|
260
|
+
return VFS.SQLITE_OK
|
|
262
261
|
} catch (e) {
|
|
263
|
-
this.lastError = e
|
|
264
|
-
return VFS.SQLITE_IOERR_TRUNCATE
|
|
262
|
+
this.lastError = e
|
|
263
|
+
return VFS.SQLITE_IOERR_TRUNCATE
|
|
265
264
|
}
|
|
266
265
|
}
|
|
267
266
|
|
|
268
267
|
/**
|
|
269
|
-
* @param {number} fileId
|
|
270
|
-
* @param {number} flags
|
|
268
|
+
* @param {number} fileId
|
|
269
|
+
* @param {number} flags
|
|
271
270
|
* @returns {number}
|
|
272
271
|
*/
|
|
273
272
|
jSync(fileId, flags) {
|
|
274
273
|
try {
|
|
275
|
-
const file = this.mapIdToFile.get(fileId)
|
|
276
|
-
file.accessHandle.flush()
|
|
277
|
-
return VFS.SQLITE_OK
|
|
274
|
+
const file = this.mapIdToFile.get(fileId)
|
|
275
|
+
file.accessHandle.flush()
|
|
276
|
+
return VFS.SQLITE_OK
|
|
278
277
|
} catch (e) {
|
|
279
|
-
this.lastError = e
|
|
280
|
-
return VFS.SQLITE_IOERR_FSYNC
|
|
278
|
+
this.lastError = e
|
|
279
|
+
return VFS.SQLITE_IOERR_FSYNC
|
|
281
280
|
}
|
|
282
281
|
}
|
|
283
282
|
|
|
284
283
|
/**
|
|
285
|
-
* @param {number} fileId
|
|
286
|
-
* @param {DataView} pSize64
|
|
284
|
+
* @param {number} fileId
|
|
285
|
+
* @param {DataView} pSize64
|
|
287
286
|
* @returns {number}
|
|
288
287
|
*/
|
|
289
288
|
jFileSize(fileId, pSize64) {
|
|
290
289
|
try {
|
|
291
|
-
const file = this.mapIdToFile.get(fileId)
|
|
292
|
-
const size = file.accessHandle.getSize()
|
|
293
|
-
pSize64.setBigInt64(0, BigInt(size), true)
|
|
294
|
-
return VFS.SQLITE_OK
|
|
290
|
+
const file = this.mapIdToFile.get(fileId)
|
|
291
|
+
const size = file.accessHandle.getSize()
|
|
292
|
+
pSize64.setBigInt64(0, BigInt(size), true)
|
|
293
|
+
return VFS.SQLITE_OK
|
|
295
294
|
} catch (e) {
|
|
296
|
-
this.lastError = e
|
|
297
|
-
return VFS.SQLITE_IOERR_FSTAT
|
|
295
|
+
this.lastError = e
|
|
296
|
+
return VFS.SQLITE_IOERR_FSTAT
|
|
298
297
|
}
|
|
299
298
|
}
|
|
300
299
|
|
|
301
300
|
/**
|
|
302
|
-
* @param {number} fileId
|
|
303
|
-
* @param {number} lockType
|
|
301
|
+
* @param {number} fileId
|
|
302
|
+
* @param {number} lockType
|
|
304
303
|
* @returns {Promise<number>}
|
|
305
304
|
*/
|
|
306
305
|
async jLock(fileId, lockType) {
|
|
307
|
-
if (hasUnsafeAccessHandle) return super.jLock(fileId, lockType)
|
|
306
|
+
if (hasUnsafeAccessHandle) return super.jLock(fileId, lockType)
|
|
308
307
|
|
|
309
|
-
const file = this.mapIdToFile.get(fileId)
|
|
308
|
+
const file = this.mapIdToFile.get(fileId)
|
|
310
309
|
if (!file.isFileLocked) {
|
|
311
|
-
file.isFileLocked = true
|
|
310
|
+
file.isFileLocked = true
|
|
312
311
|
if (!file.handleLockReleaser) {
|
|
313
312
|
// Listen for other connections wanting the access handle.
|
|
314
|
-
file.handleRequestChannel.onmessage = event => {
|
|
315
|
-
if(!file.isFileLocked) {
|
|
313
|
+
file.handleRequestChannel.onmessage = (event) => {
|
|
314
|
+
if (!file.isFileLocked) {
|
|
316
315
|
// We have the access handle but the file is not locked.
|
|
317
316
|
// Release the access handle for the requester.
|
|
318
|
-
file.accessHandle.close()
|
|
319
|
-
file.accessHandle = null
|
|
320
|
-
file.handleLockReleaser()
|
|
321
|
-
file.handleLockReleaser = null
|
|
322
|
-
this.log?.('access handle requested and released')
|
|
317
|
+
file.accessHandle.close()
|
|
318
|
+
file.accessHandle = null
|
|
319
|
+
file.handleLockReleaser()
|
|
320
|
+
file.handleLockReleaser = null
|
|
321
|
+
this.log?.('access handle requested and released')
|
|
323
322
|
} else {
|
|
324
323
|
// We're still using the access handle, so mark it to be
|
|
325
324
|
// released when we're done.
|
|
326
|
-
file.isHandleRequested = true
|
|
327
|
-
this.log?.('access handle requested')
|
|
325
|
+
file.isHandleRequested = true
|
|
326
|
+
this.log?.('access handle requested')
|
|
328
327
|
}
|
|
329
|
-
file.handleRequestChannel.onmessage = null
|
|
330
|
-
}
|
|
328
|
+
file.handleRequestChannel.onmessage = null
|
|
329
|
+
}
|
|
331
330
|
|
|
332
331
|
// We don't have the access handle. First acquire the lock.
|
|
333
332
|
file.handleLockReleaser = await new Promise((resolve, reject) => {
|
|
334
333
|
// Tell everyone we want the access handle.
|
|
335
334
|
function notify() {
|
|
336
|
-
file.handleRequestChannel.postMessage(null)
|
|
335
|
+
file.handleRequestChannel.postMessage(null)
|
|
337
336
|
}
|
|
338
|
-
const notifyId = setInterval(notify, LOCK_NOTIFY_INTERVAL)
|
|
339
|
-
setTimeout(notify)
|
|
340
|
-
|
|
341
|
-
navigator.locks.request(this.getFilename(fileId), lock => {
|
|
342
|
-
clearInterval(notifyId)
|
|
343
|
-
if (!lock) return reject()
|
|
344
|
-
return new Promise(release => {
|
|
345
|
-
resolve(release)
|
|
346
|
-
})
|
|
347
|
-
})
|
|
348
|
-
})
|
|
337
|
+
const notifyId = setInterval(notify, LOCK_NOTIFY_INTERVAL)
|
|
338
|
+
setTimeout(notify)
|
|
339
|
+
|
|
340
|
+
navigator.locks.request(this.getFilename(fileId), (lock) => {
|
|
341
|
+
clearInterval(notifyId)
|
|
342
|
+
if (!lock) return reject()
|
|
343
|
+
return new Promise((release) => {
|
|
344
|
+
resolve(release)
|
|
345
|
+
})
|
|
346
|
+
})
|
|
347
|
+
})
|
|
349
348
|
|
|
350
349
|
// The access handle should now be available.
|
|
351
|
-
file.accessHandle = await file.fileHandle.createSyncAccessHandle()
|
|
352
|
-
this.log?.('access handle acquired')
|
|
350
|
+
file.accessHandle = await file.fileHandle.createSyncAccessHandle()
|
|
351
|
+
this.log?.('access handle acquired')
|
|
353
352
|
}
|
|
354
|
-
|
|
355
353
|
}
|
|
356
|
-
return VFS.SQLITE_OK
|
|
354
|
+
return VFS.SQLITE_OK
|
|
357
355
|
}
|
|
358
356
|
|
|
359
357
|
/**
|
|
360
|
-
* @param {number} fileId
|
|
361
|
-
* @param {number} lockType
|
|
358
|
+
* @param {number} fileId
|
|
359
|
+
* @param {number} lockType
|
|
362
360
|
* @returns {Promise<number>}
|
|
363
361
|
*/
|
|
364
362
|
async jUnlock(fileId, lockType) {
|
|
365
|
-
if (hasUnsafeAccessHandle) return super.jUnlock(fileId, lockType)
|
|
363
|
+
if (hasUnsafeAccessHandle) return super.jUnlock(fileId, lockType)
|
|
366
364
|
|
|
367
365
|
if (lockType === VFS.SQLITE_LOCK_NONE) {
|
|
368
|
-
const file = this.mapIdToFile.get(fileId)
|
|
366
|
+
const file = this.mapIdToFile.get(fileId)
|
|
369
367
|
if (file.isHandleRequested) {
|
|
370
368
|
if (file.handleLockReleaser) {
|
|
371
369
|
// Another connection wants the access handle.
|
|
372
|
-
file.accessHandle.close()
|
|
373
|
-
file.accessHandle = null
|
|
374
|
-
file.handleLockReleaser()
|
|
375
|
-
file.handleLockReleaser = null
|
|
376
|
-
this.log?.('access handle released')
|
|
370
|
+
file.accessHandle.close()
|
|
371
|
+
file.accessHandle = null
|
|
372
|
+
file.handleLockReleaser()
|
|
373
|
+
file.handleLockReleaser = null
|
|
374
|
+
this.log?.('access handle released')
|
|
377
375
|
}
|
|
378
|
-
file.isHandleRequested = false
|
|
376
|
+
file.isHandleRequested = false
|
|
379
377
|
}
|
|
380
|
-
file.isFileLocked = false
|
|
378
|
+
file.isFileLocked = false
|
|
381
379
|
}
|
|
382
|
-
return VFS.SQLITE_OK
|
|
380
|
+
return VFS.SQLITE_OK
|
|
383
381
|
}
|
|
384
382
|
|
|
385
383
|
/**
|
|
@@ -390,48 +388,50 @@ export class OPFSAdaptiveVFS extends WebLocksMixin(FacadeVFS) {
|
|
|
390
388
|
*/
|
|
391
389
|
jFileControl(fileId, op, pArg) {
|
|
392
390
|
try {
|
|
393
|
-
const file = this.mapIdToFile.get(fileId)
|
|
391
|
+
const file = this.mapIdToFile.get(fileId)
|
|
394
392
|
switch (op) {
|
|
395
393
|
case VFS.SQLITE_FCNTL_PRAGMA:
|
|
396
|
-
const key = extractString(pArg, 4)
|
|
397
|
-
const value = extractString(pArg, 8)
|
|
398
|
-
this.log?.('xFileControl', file.pathname, 'PRAGMA', key, value)
|
|
394
|
+
const key = extractString(pArg, 4)
|
|
395
|
+
const value = extractString(pArg, 8)
|
|
396
|
+
this.log?.('xFileControl', file.pathname, 'PRAGMA', key, value)
|
|
399
397
|
switch (key.toLowerCase()) {
|
|
400
398
|
case 'journal_mode':
|
|
401
|
-
if (
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
399
|
+
if (
|
|
400
|
+
value &&
|
|
401
|
+
!hasUnsafeAccessHandle &&
|
|
402
|
+
!['off', 'memory', 'delete', 'wal'].includes(value.toLowerCase())
|
|
403
|
+
) {
|
|
404
|
+
throw new Error('journal_mode must be "off", "memory", "delete", or "wal"')
|
|
405
405
|
}
|
|
406
|
-
break
|
|
406
|
+
break
|
|
407
407
|
case 'write_hint':
|
|
408
|
-
return super.jFileControl(fileId, WebLocksMixin.WRITE_HINT_OP_CODE, null)
|
|
408
|
+
return super.jFileControl(fileId, WebLocksMixin.WRITE_HINT_OP_CODE, null)
|
|
409
409
|
}
|
|
410
|
-
break
|
|
410
|
+
break
|
|
411
411
|
}
|
|
412
412
|
} catch (e) {
|
|
413
|
-
this.lastError = e
|
|
414
|
-
return VFS.SQLITE_IOERR
|
|
413
|
+
this.lastError = e
|
|
414
|
+
return VFS.SQLITE_IOERR
|
|
415
415
|
}
|
|
416
|
-
return super.jFileControl(fileId, op, pArg)
|
|
416
|
+
return super.jFileControl(fileId, op, pArg)
|
|
417
417
|
}
|
|
418
418
|
|
|
419
419
|
jGetLastError(zBuf) {
|
|
420
420
|
if (this.lastError) {
|
|
421
|
-
console.error(this.lastError)
|
|
422
|
-
const outputArray = zBuf.subarray(0, zBuf.byteLength - 1)
|
|
423
|
-
const { written } = new TextEncoder().encodeInto(this.lastError.message, outputArray)
|
|
424
|
-
zBuf[written] = 0
|
|
421
|
+
console.error(this.lastError)
|
|
422
|
+
const outputArray = zBuf.subarray(0, zBuf.byteLength - 1)
|
|
423
|
+
const { written } = new TextEncoder().encodeInto(this.lastError.message, outputArray)
|
|
424
|
+
zBuf[written] = 0
|
|
425
425
|
}
|
|
426
426
|
return VFS.SQLITE_OK
|
|
427
427
|
}
|
|
428
428
|
}
|
|
429
429
|
|
|
430
430
|
function extractString(dataView, offset) {
|
|
431
|
-
const p = dataView.getUint32(offset, true)
|
|
431
|
+
const p = dataView.getUint32(offset, true)
|
|
432
432
|
if (p) {
|
|
433
|
-
const chars = new Uint8Array(dataView.buffer, p)
|
|
434
|
-
return new TextDecoder().decode(chars.subarray(0, chars.indexOf(0)))
|
|
433
|
+
const chars = new Uint8Array(dataView.buffer, p)
|
|
434
|
+
return new TextDecoder().decode(chars.subarray(0, chars.indexOf(0)))
|
|
435
435
|
}
|
|
436
|
-
return null
|
|
437
|
-
}
|
|
436
|
+
return null
|
|
437
|
+
}
|