@livestore/wa-sqlite 0.4.0-dev.21 → 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,28 +1,25 @@
|
|
|
1
1
|
// Copyright 2023 Roy T. Hashimoto. All Rights Reserved.
|
|
2
|
-
import { FacadeVFS } from '../FacadeVFS.js'
|
|
3
|
-
import * as VFS from '../VFS.js'
|
|
2
|
+
import { FacadeVFS } from '../FacadeVFS.js'
|
|
3
|
+
import * as VFS from '../VFS.js'
|
|
4
4
|
|
|
5
|
-
const SECTOR_SIZE = 4096
|
|
5
|
+
const SECTOR_SIZE = 4096
|
|
6
6
|
|
|
7
7
|
// Each OPFS file begins with a fixed-size header with metadata. The
|
|
8
8
|
// contents of the file follow immediately after the header.
|
|
9
|
-
const HEADER_MAX_PATH_SIZE = 512
|
|
10
|
-
const HEADER_FLAGS_SIZE = 4
|
|
11
|
-
const HEADER_DIGEST_SIZE = 8
|
|
12
|
-
const HEADER_CORPUS_SIZE = HEADER_MAX_PATH_SIZE + HEADER_FLAGS_SIZE
|
|
13
|
-
const HEADER_OFFSET_FLAGS = HEADER_MAX_PATH_SIZE
|
|
14
|
-
const HEADER_OFFSET_DIGEST = HEADER_CORPUS_SIZE
|
|
15
|
-
const HEADER_OFFSET_DATA = SECTOR_SIZE
|
|
9
|
+
const HEADER_MAX_PATH_SIZE = 512
|
|
10
|
+
const HEADER_FLAGS_SIZE = 4
|
|
11
|
+
const HEADER_DIGEST_SIZE = 8
|
|
12
|
+
const HEADER_CORPUS_SIZE = HEADER_MAX_PATH_SIZE + HEADER_FLAGS_SIZE
|
|
13
|
+
const HEADER_OFFSET_FLAGS = HEADER_MAX_PATH_SIZE
|
|
14
|
+
const HEADER_OFFSET_DIGEST = HEADER_CORPUS_SIZE
|
|
15
|
+
const HEADER_OFFSET_DATA = SECTOR_SIZE
|
|
16
16
|
|
|
17
17
|
// These file types are expected to persist in the file system outside
|
|
18
18
|
// a session. Other files will be removed on VFS start.
|
|
19
19
|
const PERSISTENT_FILE_TYPES =
|
|
20
|
-
VFS.SQLITE_OPEN_MAIN_DB |
|
|
21
|
-
VFS.SQLITE_OPEN_MAIN_JOURNAL |
|
|
22
|
-
VFS.SQLITE_OPEN_SUPER_JOURNAL |
|
|
23
|
-
VFS.SQLITE_OPEN_WAL;
|
|
20
|
+
VFS.SQLITE_OPEN_MAIN_DB | VFS.SQLITE_OPEN_MAIN_JOURNAL | VFS.SQLITE_OPEN_SUPER_JOURNAL | VFS.SQLITE_OPEN_WAL
|
|
24
21
|
|
|
25
|
-
const DEFAULT_CAPACITY = 6
|
|
22
|
+
const DEFAULT_CAPACITY = 6
|
|
26
23
|
|
|
27
24
|
/**
|
|
28
25
|
* This VFS uses the updated Access Handle API with all synchronous methods
|
|
@@ -31,211 +28,207 @@ const DEFAULT_CAPACITY = 6;
|
|
|
31
28
|
* Asyncify.
|
|
32
29
|
*/
|
|
33
30
|
export class AccessHandlePoolVFS extends FacadeVFS {
|
|
34
|
-
log = null
|
|
31
|
+
log = null //function(...args) { console.log(`[${contextName}]`, ...args) };
|
|
35
32
|
|
|
36
33
|
// All the OPFS files the VFS uses are contained in one flat directory
|
|
37
34
|
// specified in the constructor. No other files should be written here.
|
|
38
|
-
#directoryPath
|
|
39
|
-
#directoryHandle
|
|
35
|
+
#directoryPath
|
|
36
|
+
#directoryHandle
|
|
40
37
|
|
|
41
38
|
// The OPFS files all have randomly-generated names that do not match
|
|
42
39
|
// the SQLite files whose data they contain. This map links those names
|
|
43
40
|
// with their respective OPFS access handles.
|
|
44
|
-
#mapAccessHandleToName = new Map()
|
|
41
|
+
#mapAccessHandleToName = new Map()
|
|
45
42
|
|
|
46
43
|
// When a SQLite file is associated with an OPFS file, that association
|
|
47
44
|
// is kept in #mapPathToAccessHandle. Each access handle is in exactly
|
|
48
45
|
// one of #mapPathToAccessHandle or #availableAccessHandles.
|
|
49
|
-
#mapPathToAccessHandle = new Map()
|
|
50
|
-
#availableAccessHandles = new Set()
|
|
46
|
+
#mapPathToAccessHandle = new Map()
|
|
47
|
+
#availableAccessHandles = new Set()
|
|
51
48
|
|
|
52
|
-
#mapIdToFile = new Map()
|
|
49
|
+
#mapIdToFile = new Map()
|
|
53
50
|
|
|
54
51
|
static async create(name, module) {
|
|
55
|
-
const vfs = new AccessHandlePoolVFS(name, module)
|
|
56
|
-
await vfs.isReady()
|
|
57
|
-
return vfs
|
|
52
|
+
const vfs = new AccessHandlePoolVFS(name, module)
|
|
53
|
+
await vfs.isReady()
|
|
54
|
+
return vfs
|
|
58
55
|
}
|
|
59
|
-
|
|
56
|
+
|
|
60
57
|
constructor(name, module) {
|
|
61
|
-
super(name, module)
|
|
62
|
-
this.#directoryPath = name
|
|
58
|
+
super(name, module)
|
|
59
|
+
this.#directoryPath = name
|
|
63
60
|
}
|
|
64
61
|
|
|
65
62
|
/**
|
|
66
|
-
* @param {string?} zName
|
|
67
|
-
* @param {number} fileId
|
|
68
|
-
* @param {number} flags
|
|
69
|
-
* @param {DataView} pOutFlags
|
|
63
|
+
* @param {string?} zName
|
|
64
|
+
* @param {number} fileId
|
|
65
|
+
* @param {number} flags
|
|
66
|
+
* @param {DataView} pOutFlags
|
|
70
67
|
* @returns {number}
|
|
71
68
|
*/
|
|
72
69
|
jOpen(zName, fileId, flags, pOutFlags) {
|
|
73
70
|
try {
|
|
74
71
|
// First try to open a path that already exists in the file system.
|
|
75
|
-
const path = zName ? this.#getPath(zName) : Math.random().toString(36)
|
|
76
|
-
let accessHandle = this.#mapPathToAccessHandle.get(path)
|
|
77
|
-
if (!accessHandle &&
|
|
72
|
+
const path = zName ? this.#getPath(zName) : Math.random().toString(36)
|
|
73
|
+
let accessHandle = this.#mapPathToAccessHandle.get(path)
|
|
74
|
+
if (!accessHandle && flags & VFS.SQLITE_OPEN_CREATE) {
|
|
78
75
|
// File not found so try to create it.
|
|
79
76
|
if (this.getSize() < this.getCapacity()) {
|
|
80
77
|
// Choose an unassociated OPFS file from the pool.
|
|
81
|
-
|
|
82
|
-
this.#setAssociatedPath(accessHandle, path, flags)
|
|
78
|
+
;[accessHandle] = this.#availableAccessHandles.keys()
|
|
79
|
+
this.#setAssociatedPath(accessHandle, path, flags)
|
|
83
80
|
} else {
|
|
84
81
|
// Out of unassociated files. This can be fixed by calling
|
|
85
82
|
// addCapacity() from the application.
|
|
86
|
-
throw new Error('cannot create file')
|
|
83
|
+
throw new Error('cannot create file')
|
|
87
84
|
}
|
|
88
85
|
}
|
|
89
86
|
if (!accessHandle) {
|
|
90
|
-
throw new Error('file not found')
|
|
87
|
+
throw new Error('file not found')
|
|
91
88
|
}
|
|
92
89
|
// Subsequent methods are only passed the fileId, so make sure we have
|
|
93
90
|
// a way to get the file resources.
|
|
94
|
-
const file = { path, flags, accessHandle }
|
|
95
|
-
this.#mapIdToFile.set(fileId, file)
|
|
91
|
+
const file = { path, flags, accessHandle }
|
|
92
|
+
this.#mapIdToFile.set(fileId, file)
|
|
96
93
|
|
|
97
|
-
pOutFlags.setInt32(0, flags, true)
|
|
98
|
-
return VFS.SQLITE_OK
|
|
94
|
+
pOutFlags.setInt32(0, flags, true)
|
|
95
|
+
return VFS.SQLITE_OK
|
|
99
96
|
} catch (e) {
|
|
100
|
-
console.error(e.message)
|
|
101
|
-
return VFS.SQLITE_CANTOPEN
|
|
97
|
+
console.error(e.message)
|
|
98
|
+
return VFS.SQLITE_CANTOPEN
|
|
102
99
|
}
|
|
103
100
|
}
|
|
104
101
|
|
|
105
102
|
/**
|
|
106
|
-
* @param {number} fileId
|
|
103
|
+
* @param {number} fileId
|
|
107
104
|
* @returns {number}
|
|
108
105
|
*/
|
|
109
106
|
jClose(fileId) {
|
|
110
|
-
const file = this.#mapIdToFile.get(fileId)
|
|
107
|
+
const file = this.#mapIdToFile.get(fileId)
|
|
111
108
|
if (file) {
|
|
112
|
-
file.accessHandle.flush()
|
|
113
|
-
this.#mapIdToFile.delete(fileId)
|
|
109
|
+
file.accessHandle.flush()
|
|
110
|
+
this.#mapIdToFile.delete(fileId)
|
|
114
111
|
if (file.flags & VFS.SQLITE_OPEN_DELETEONCLOSE) {
|
|
115
|
-
this.#deletePath(file.path)
|
|
112
|
+
this.#deletePath(file.path)
|
|
116
113
|
}
|
|
117
114
|
}
|
|
118
|
-
return VFS.SQLITE_OK
|
|
115
|
+
return VFS.SQLITE_OK
|
|
119
116
|
}
|
|
120
117
|
|
|
121
118
|
/**
|
|
122
|
-
* @param {number} fileId
|
|
123
|
-
* @param {Uint8Array} pData
|
|
119
|
+
* @param {number} fileId
|
|
120
|
+
* @param {Uint8Array} pData
|
|
124
121
|
* @param {number} iOffset
|
|
125
122
|
* @returns {number}
|
|
126
123
|
*/
|
|
127
124
|
jRead(fileId, pData, iOffset) {
|
|
128
|
-
const file = this.#mapIdToFile.get(fileId)
|
|
129
|
-
const nBytes = file.accessHandle.read(
|
|
130
|
-
pData.subarray(),
|
|
131
|
-
{ at: HEADER_OFFSET_DATA + iOffset });
|
|
125
|
+
const file = this.#mapIdToFile.get(fileId)
|
|
126
|
+
const nBytes = file.accessHandle.read(pData.subarray(), { at: HEADER_OFFSET_DATA + iOffset })
|
|
132
127
|
if (nBytes < pData.byteLength) {
|
|
133
|
-
pData.fill(0, nBytes, pData.byteLength)
|
|
134
|
-
return VFS.SQLITE_IOERR_SHORT_READ
|
|
128
|
+
pData.fill(0, nBytes, pData.byteLength)
|
|
129
|
+
return VFS.SQLITE_IOERR_SHORT_READ
|
|
135
130
|
}
|
|
136
|
-
return VFS.SQLITE_OK
|
|
131
|
+
return VFS.SQLITE_OK
|
|
137
132
|
}
|
|
138
133
|
|
|
139
134
|
/**
|
|
140
|
-
* @param {number} fileId
|
|
141
|
-
* @param {Uint8Array} pData
|
|
135
|
+
* @param {number} fileId
|
|
136
|
+
* @param {Uint8Array} pData
|
|
142
137
|
* @param {number} iOffset
|
|
143
138
|
* @returns {number}
|
|
144
139
|
*/
|
|
145
140
|
jWrite(fileId, pData, iOffset) {
|
|
146
|
-
const file = this.#mapIdToFile.get(fileId)
|
|
147
|
-
const nBytes = file.accessHandle.write(
|
|
148
|
-
|
|
149
|
-
{ at: HEADER_OFFSET_DATA + iOffset });
|
|
150
|
-
return nBytes === pData.byteLength ? VFS.SQLITE_OK : VFS.SQLITE_IOERR;
|
|
141
|
+
const file = this.#mapIdToFile.get(fileId)
|
|
142
|
+
const nBytes = file.accessHandle.write(pData.subarray(), { at: HEADER_OFFSET_DATA + iOffset })
|
|
143
|
+
return nBytes === pData.byteLength ? VFS.SQLITE_OK : VFS.SQLITE_IOERR
|
|
151
144
|
}
|
|
152
145
|
|
|
153
146
|
/**
|
|
154
|
-
* @param {number} fileId
|
|
155
|
-
* @param {number} iSize
|
|
147
|
+
* @param {number} fileId
|
|
148
|
+
* @param {number} iSize
|
|
156
149
|
* @returns {number}
|
|
157
150
|
*/
|
|
158
151
|
jTruncate(fileId, iSize) {
|
|
159
|
-
const file = this.#mapIdToFile.get(fileId)
|
|
160
|
-
file.accessHandle.truncate(HEADER_OFFSET_DATA + iSize)
|
|
161
|
-
return VFS.SQLITE_OK
|
|
152
|
+
const file = this.#mapIdToFile.get(fileId)
|
|
153
|
+
file.accessHandle.truncate(HEADER_OFFSET_DATA + iSize)
|
|
154
|
+
return VFS.SQLITE_OK
|
|
162
155
|
}
|
|
163
156
|
|
|
164
157
|
/**
|
|
165
|
-
* @param {number} fileId
|
|
166
|
-
* @param {number} flags
|
|
158
|
+
* @param {number} fileId
|
|
159
|
+
* @param {number} flags
|
|
167
160
|
* @returns {number}
|
|
168
161
|
*/
|
|
169
162
|
jSync(fileId, flags) {
|
|
170
|
-
const file = this.#mapIdToFile.get(fileId)
|
|
171
|
-
file.accessHandle.flush()
|
|
172
|
-
return VFS.SQLITE_OK
|
|
163
|
+
const file = this.#mapIdToFile.get(fileId)
|
|
164
|
+
file.accessHandle.flush()
|
|
165
|
+
return VFS.SQLITE_OK
|
|
173
166
|
}
|
|
174
167
|
|
|
175
168
|
/**
|
|
176
|
-
* @param {number} fileId
|
|
177
|
-
* @param {DataView} pSize64
|
|
169
|
+
* @param {number} fileId
|
|
170
|
+
* @param {DataView} pSize64
|
|
178
171
|
* @returns {number}
|
|
179
172
|
*/
|
|
180
173
|
jFileSize(fileId, pSize64) {
|
|
181
|
-
const file = this.#mapIdToFile.get(fileId)
|
|
182
|
-
const size = file.accessHandle.getSize() - HEADER_OFFSET_DATA
|
|
183
|
-
pSize64.setBigInt64(0, BigInt(size), true)
|
|
184
|
-
return VFS.SQLITE_OK
|
|
174
|
+
const file = this.#mapIdToFile.get(fileId)
|
|
175
|
+
const size = file.accessHandle.getSize() - HEADER_OFFSET_DATA
|
|
176
|
+
pSize64.setBigInt64(0, BigInt(size), true)
|
|
177
|
+
return VFS.SQLITE_OK
|
|
185
178
|
}
|
|
186
179
|
|
|
187
180
|
jSectorSize(fileId) {
|
|
188
|
-
return SECTOR_SIZE
|
|
181
|
+
return SECTOR_SIZE
|
|
189
182
|
}
|
|
190
183
|
|
|
191
184
|
jDeviceCharacteristics(fileId) {
|
|
192
|
-
return VFS.SQLITE_IOCAP_UNDELETABLE_WHEN_OPEN
|
|
185
|
+
return VFS.SQLITE_IOCAP_UNDELETABLE_WHEN_OPEN
|
|
193
186
|
}
|
|
194
187
|
|
|
195
188
|
/**
|
|
196
|
-
* @param {string} zName
|
|
197
|
-
* @param {number} flags
|
|
198
|
-
* @param {DataView} pResOut
|
|
189
|
+
* @param {string} zName
|
|
190
|
+
* @param {number} flags
|
|
191
|
+
* @param {DataView} pResOut
|
|
199
192
|
* @returns {number}
|
|
200
193
|
*/
|
|
201
194
|
jAccess(zName, flags, pResOut) {
|
|
202
|
-
const path = this.#getPath(zName)
|
|
203
|
-
pResOut.setInt32(0, this.#mapPathToAccessHandle.has(path) ? 1 : 0, true)
|
|
204
|
-
return VFS.SQLITE_OK
|
|
195
|
+
const path = this.#getPath(zName)
|
|
196
|
+
pResOut.setInt32(0, this.#mapPathToAccessHandle.has(path) ? 1 : 0, true)
|
|
197
|
+
return VFS.SQLITE_OK
|
|
205
198
|
}
|
|
206
199
|
|
|
207
200
|
/**
|
|
208
|
-
* @param {string} zName
|
|
209
|
-
* @param {number} syncDir
|
|
201
|
+
* @param {string} zName
|
|
202
|
+
* @param {number} syncDir
|
|
210
203
|
* @returns {number}
|
|
211
204
|
*/
|
|
212
205
|
jDelete(zName, syncDir) {
|
|
213
|
-
const path = this.#getPath(zName)
|
|
214
|
-
this.#deletePath(path)
|
|
215
|
-
return VFS.SQLITE_OK
|
|
206
|
+
const path = this.#getPath(zName)
|
|
207
|
+
this.#deletePath(path)
|
|
208
|
+
return VFS.SQLITE_OK
|
|
216
209
|
}
|
|
217
210
|
|
|
218
211
|
async close() {
|
|
219
|
-
await this.#releaseAccessHandles()
|
|
212
|
+
await this.#releaseAccessHandles()
|
|
220
213
|
}
|
|
221
214
|
|
|
222
215
|
async isReady() {
|
|
223
216
|
if (!this.#directoryHandle) {
|
|
224
217
|
// All files are stored in a single directory.
|
|
225
|
-
let handle = await navigator.storage.getDirectory()
|
|
218
|
+
let handle = await navigator.storage.getDirectory()
|
|
226
219
|
for (const d of this.#directoryPath.split('/')) {
|
|
227
220
|
if (d) {
|
|
228
|
-
handle = await handle.getDirectoryHandle(d, { create: true })
|
|
221
|
+
handle = await handle.getDirectoryHandle(d, { create: true })
|
|
229
222
|
}
|
|
230
223
|
}
|
|
231
|
-
this.#directoryHandle = handle
|
|
224
|
+
this.#directoryHandle = handle
|
|
232
225
|
|
|
233
|
-
await this.#acquireAccessHandles()
|
|
226
|
+
await this.#acquireAccessHandles()
|
|
234
227
|
if (this.getCapacity() === 0) {
|
|
235
|
-
await this.addCapacity(DEFAULT_CAPACITY)
|
|
228
|
+
await this.addCapacity(DEFAULT_CAPACITY)
|
|
236
229
|
}
|
|
237
230
|
}
|
|
238
|
-
return true
|
|
231
|
+
return true
|
|
239
232
|
}
|
|
240
233
|
|
|
241
234
|
/**
|
|
@@ -243,7 +236,7 @@ export class AccessHandlePoolVFS extends FacadeVFS {
|
|
|
243
236
|
* @returns {number}
|
|
244
237
|
*/
|
|
245
238
|
getSize() {
|
|
246
|
-
return this.#mapPathToAccessHandle.size
|
|
239
|
+
return this.#mapPathToAccessHandle.size
|
|
247
240
|
}
|
|
248
241
|
|
|
249
242
|
/**
|
|
@@ -251,77 +244,79 @@ export class AccessHandlePoolVFS extends FacadeVFS {
|
|
|
251
244
|
* @returns {number}
|
|
252
245
|
*/
|
|
253
246
|
getCapacity() {
|
|
254
|
-
return this.#mapAccessHandleToName.size
|
|
247
|
+
return this.#mapAccessHandleToName.size
|
|
255
248
|
}
|
|
256
249
|
|
|
257
250
|
/**
|
|
258
251
|
* Increase the capacity of the file system by n.
|
|
259
|
-
* @param {number} n
|
|
260
|
-
* @returns {Promise<number>}
|
|
252
|
+
* @param {number} n
|
|
253
|
+
* @returns {Promise<number>}
|
|
261
254
|
*/
|
|
262
255
|
async addCapacity(n) {
|
|
263
256
|
for (let i = 0; i < n; ++i) {
|
|
264
|
-
const name = Math.random().toString(36).replace('0.', '')
|
|
265
|
-
const handle = await this.#directoryHandle.getFileHandle(name, { create: true })
|
|
266
|
-
const accessHandle = await handle.createSyncAccessHandle()
|
|
267
|
-
this.#mapAccessHandleToName.set(accessHandle, name)
|
|
257
|
+
const name = Math.random().toString(36).replace('0.', '')
|
|
258
|
+
const handle = await this.#directoryHandle.getFileHandle(name, { create: true })
|
|
259
|
+
const accessHandle = await handle.createSyncAccessHandle()
|
|
260
|
+
this.#mapAccessHandleToName.set(accessHandle, name)
|
|
268
261
|
|
|
269
|
-
this.#setAssociatedPath(accessHandle, '', 0)
|
|
262
|
+
this.#setAssociatedPath(accessHandle, '', 0)
|
|
270
263
|
}
|
|
271
|
-
return n
|
|
264
|
+
return n
|
|
272
265
|
}
|
|
273
266
|
|
|
274
267
|
/**
|
|
275
268
|
* Decrease the capacity of the file system by n. The capacity cannot be
|
|
276
269
|
* decreased to fewer than the current number of SQLite files in the
|
|
277
270
|
* file system.
|
|
278
|
-
* @param {number} n
|
|
271
|
+
* @param {number} n
|
|
279
272
|
* @returns {Promise<number>}
|
|
280
273
|
*/
|
|
281
274
|
async removeCapacity(n) {
|
|
282
|
-
let nRemoved = 0
|
|
275
|
+
let nRemoved = 0
|
|
283
276
|
for (const accessHandle of Array.from(this.#availableAccessHandles)) {
|
|
284
|
-
if (nRemoved == n || this.getSize() === this.getCapacity()) return nRemoved
|
|
285
|
-
|
|
286
|
-
const name = this.#mapAccessHandleToName.get(accessHandle)
|
|
287
|
-
await accessHandle.close()
|
|
288
|
-
await this.#directoryHandle.removeEntry(name)
|
|
289
|
-
this.#mapAccessHandleToName.delete(accessHandle)
|
|
290
|
-
this.#availableAccessHandles.delete(accessHandle)
|
|
291
|
-
++nRemoved
|
|
277
|
+
if (nRemoved == n || this.getSize() === this.getCapacity()) return nRemoved
|
|
278
|
+
|
|
279
|
+
const name = this.#mapAccessHandleToName.get(accessHandle)
|
|
280
|
+
await accessHandle.close()
|
|
281
|
+
await this.#directoryHandle.removeEntry(name)
|
|
282
|
+
this.#mapAccessHandleToName.delete(accessHandle)
|
|
283
|
+
this.#availableAccessHandles.delete(accessHandle)
|
|
284
|
+
++nRemoved
|
|
292
285
|
}
|
|
293
|
-
return nRemoved
|
|
286
|
+
return nRemoved
|
|
294
287
|
}
|
|
295
288
|
|
|
296
289
|
async #acquireAccessHandles() {
|
|
297
290
|
// Enumerate all the files in the directory.
|
|
298
|
-
const files = []
|
|
291
|
+
const files = []
|
|
299
292
|
for await (const [name, handle] of this.#directoryHandle) {
|
|
300
293
|
if (handle.kind === 'file') {
|
|
301
|
-
files.push([name, handle])
|
|
294
|
+
files.push([name, handle])
|
|
302
295
|
}
|
|
303
296
|
}
|
|
304
297
|
|
|
305
298
|
// Open access handles in parallel, separating associated and unassociated.
|
|
306
|
-
await Promise.all(
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
299
|
+
await Promise.all(
|
|
300
|
+
files.map(async ([name, handle]) => {
|
|
301
|
+
const accessHandle = await handle.createSyncAccessHandle()
|
|
302
|
+
this.#mapAccessHandleToName.set(accessHandle, name)
|
|
303
|
+
const path = this.#getAssociatedPath(accessHandle)
|
|
304
|
+
if (path) {
|
|
305
|
+
this.#mapPathToAccessHandle.set(path, accessHandle)
|
|
306
|
+
} else {
|
|
307
|
+
this.#availableAccessHandles.add(accessHandle)
|
|
308
|
+
}
|
|
309
|
+
}),
|
|
310
|
+
)
|
|
316
311
|
}
|
|
317
312
|
|
|
318
313
|
#releaseAccessHandles() {
|
|
319
314
|
for (const accessHandle of this.#mapAccessHandleToName.keys()) {
|
|
320
|
-
accessHandle.close()
|
|
315
|
+
accessHandle.close()
|
|
321
316
|
}
|
|
322
|
-
this.#mapAccessHandleToName.clear()
|
|
323
|
-
this.#mapPathToAccessHandle.clear()
|
|
324
|
-
this.#availableAccessHandles.clear()
|
|
317
|
+
this.#mapAccessHandleToName.clear()
|
|
318
|
+
this.#mapPathToAccessHandle.clear()
|
|
319
|
+
this.#availableAccessHandles.clear()
|
|
325
320
|
}
|
|
326
321
|
|
|
327
322
|
/**
|
|
@@ -332,41 +327,39 @@ export class AccessHandlePoolVFS extends FacadeVFS {
|
|
|
332
327
|
*/
|
|
333
328
|
#getAssociatedPath(accessHandle) {
|
|
334
329
|
// Read the path and digest of the path from the file.
|
|
335
|
-
const corpus = new Uint8Array(HEADER_CORPUS_SIZE)
|
|
330
|
+
const corpus = new Uint8Array(HEADER_CORPUS_SIZE)
|
|
336
331
|
accessHandle.read(corpus, { at: 0 })
|
|
337
332
|
|
|
338
333
|
// Delete files not expected to be present.
|
|
339
|
-
const dataView = new DataView(corpus.buffer, corpus.byteOffset)
|
|
340
|
-
const flags = dataView.getUint32(HEADER_OFFSET_FLAGS)
|
|
341
|
-
if (corpus[0] &&
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
this.#setAssociatedPath(accessHandle, '', 0);
|
|
346
|
-
return '';
|
|
334
|
+
const dataView = new DataView(corpus.buffer, corpus.byteOffset)
|
|
335
|
+
const flags = dataView.getUint32(HEADER_OFFSET_FLAGS)
|
|
336
|
+
if (corpus[0] && (flags & VFS.SQLITE_OPEN_DELETEONCLOSE || (flags & PERSISTENT_FILE_TYPES) === 0)) {
|
|
337
|
+
console.warn(`Remove file with unexpected flags ${flags.toString(16)}`)
|
|
338
|
+
this.#setAssociatedPath(accessHandle, '', 0)
|
|
339
|
+
return ''
|
|
347
340
|
}
|
|
348
341
|
|
|
349
|
-
const fileDigest = new Uint32Array(HEADER_DIGEST_SIZE / 4)
|
|
350
|
-
accessHandle.read(fileDigest, { at: HEADER_OFFSET_DIGEST })
|
|
342
|
+
const fileDigest = new Uint32Array(HEADER_DIGEST_SIZE / 4)
|
|
343
|
+
accessHandle.read(fileDigest, { at: HEADER_OFFSET_DIGEST })
|
|
351
344
|
|
|
352
345
|
// Verify the digest.
|
|
353
|
-
const computedDigest = this.#computeDigest(corpus)
|
|
346
|
+
const computedDigest = this.#computeDigest(corpus)
|
|
354
347
|
if (fileDigest.every((value, i) => value === computedDigest[i])) {
|
|
355
348
|
// Good digest. Decode the null-terminated path string.
|
|
356
|
-
const pathBytes = corpus.findIndex(value => value === 0)
|
|
349
|
+
const pathBytes = corpus.findIndex((value) => value === 0)
|
|
357
350
|
if (pathBytes === 0) {
|
|
358
351
|
// Ensure that unassociated files are empty. Unassociated files are
|
|
359
352
|
// truncated in #setAssociatedPath after the header is written. If
|
|
360
353
|
// an interruption occurs right before the truncation then garbage
|
|
361
354
|
// may remain in the file.
|
|
362
|
-
accessHandle.truncate(HEADER_OFFSET_DATA)
|
|
355
|
+
accessHandle.truncate(HEADER_OFFSET_DATA)
|
|
363
356
|
}
|
|
364
|
-
return new TextDecoder().decode(corpus.subarray(0, pathBytes))
|
|
357
|
+
return new TextDecoder().decode(corpus.subarray(0, pathBytes))
|
|
365
358
|
} else {
|
|
366
359
|
// Bad digest. Repair this header.
|
|
367
|
-
console.warn('Disassociating file with bad digest.')
|
|
368
|
-
this.#setAssociatedPath(accessHandle, '', 0)
|
|
369
|
-
return ''
|
|
360
|
+
console.warn('Disassociating file with bad digest.')
|
|
361
|
+
this.#setAssociatedPath(accessHandle, '', 0)
|
|
362
|
+
return ''
|
|
370
363
|
}
|
|
371
364
|
}
|
|
372
365
|
|
|
@@ -378,81 +371,79 @@ export class AccessHandlePoolVFS extends FacadeVFS {
|
|
|
378
371
|
*/
|
|
379
372
|
#setAssociatedPath(accessHandle, path, flags) {
|
|
380
373
|
// Convert the path string to UTF-8.
|
|
381
|
-
const corpus = new Uint8Array(HEADER_CORPUS_SIZE)
|
|
382
|
-
const encodedResult = new TextEncoder().encodeInto(path, corpus)
|
|
374
|
+
const corpus = new Uint8Array(HEADER_CORPUS_SIZE)
|
|
375
|
+
const encodedResult = new TextEncoder().encodeInto(path, corpus)
|
|
383
376
|
if (encodedResult.written >= HEADER_MAX_PATH_SIZE) {
|
|
384
|
-
throw new Error('path too long')
|
|
377
|
+
throw new Error('path too long')
|
|
385
378
|
}
|
|
386
379
|
|
|
387
380
|
// Add the creation flags.
|
|
388
|
-
const dataView = new DataView(corpus.buffer, corpus.byteOffset)
|
|
389
|
-
dataView.setUint32(HEADER_OFFSET_FLAGS, flags)
|
|
381
|
+
const dataView = new DataView(corpus.buffer, corpus.byteOffset)
|
|
382
|
+
dataView.setUint32(HEADER_OFFSET_FLAGS, flags)
|
|
390
383
|
|
|
391
384
|
// Write the OPFS file header, including the digest.
|
|
392
|
-
const digest = this.#computeDigest(corpus)
|
|
393
|
-
accessHandle.write(corpus, { at: 0 })
|
|
394
|
-
accessHandle.write(digest, { at: HEADER_OFFSET_DIGEST })
|
|
395
|
-
accessHandle.flush()
|
|
385
|
+
const digest = this.#computeDigest(corpus)
|
|
386
|
+
accessHandle.write(corpus, { at: 0 })
|
|
387
|
+
accessHandle.write(digest, { at: HEADER_OFFSET_DIGEST })
|
|
388
|
+
accessHandle.flush()
|
|
396
389
|
|
|
397
390
|
if (path) {
|
|
398
|
-
this.#mapPathToAccessHandle.set(path, accessHandle)
|
|
399
|
-
this.#availableAccessHandles.delete(accessHandle)
|
|
391
|
+
this.#mapPathToAccessHandle.set(path, accessHandle)
|
|
392
|
+
this.#availableAccessHandles.delete(accessHandle)
|
|
400
393
|
} else {
|
|
401
394
|
// This OPFS file doesn't represent any SQLite file so it doesn't
|
|
402
395
|
// need to keep any data.
|
|
403
|
-
accessHandle.truncate(HEADER_OFFSET_DATA)
|
|
404
|
-
this.#availableAccessHandles.add(accessHandle)
|
|
396
|
+
accessHandle.truncate(HEADER_OFFSET_DATA)
|
|
397
|
+
this.#availableAccessHandles.add(accessHandle)
|
|
405
398
|
}
|
|
406
399
|
}
|
|
407
400
|
|
|
408
401
|
/**
|
|
409
402
|
* We need a synchronous digest function so can't use WebCrypto.
|
|
410
403
|
* Adapted from https://github.com/bryc/code/blob/master/jshash/experimental/cyrb53.js
|
|
411
|
-
* @param {Uint8Array} corpus
|
|
404
|
+
* @param {Uint8Array} corpus
|
|
412
405
|
* @returns {ArrayBuffer} 64-bit digest
|
|
413
406
|
*/
|
|
414
407
|
#computeDigest(corpus) {
|
|
415
408
|
if (!corpus[0]) {
|
|
416
409
|
// Optimization for deleted file.
|
|
417
|
-
return new Uint32Array([0xfecc5f80, 0xaccec037])
|
|
410
|
+
return new Uint32Array([0xfecc5f80, 0xaccec037])
|
|
418
411
|
}
|
|
419
412
|
|
|
420
|
-
let h1 = 0xdeadbeef
|
|
421
|
-
let h2 = 0x41c6ce57
|
|
422
|
-
|
|
413
|
+
let h1 = 0xdeadbeef
|
|
414
|
+
let h2 = 0x41c6ce57
|
|
415
|
+
|
|
423
416
|
for (const value of corpus) {
|
|
424
|
-
h1 = Math.imul(h1 ^ value, 2654435761)
|
|
425
|
-
h2 = Math.imul(h2 ^ value, 1597334677)
|
|
417
|
+
h1 = Math.imul(h1 ^ value, 2654435761)
|
|
418
|
+
h2 = Math.imul(h2 ^ value, 1597334677)
|
|
426
419
|
}
|
|
427
|
-
|
|
428
|
-
h1 = Math.imul(h1 ^ (h1 >>> 16), 2246822507) ^ Math.imul(h2 ^ (h2 >>> 13), 3266489909)
|
|
429
|
-
h2 = Math.imul(h2 ^ (h2 >>> 16), 2246822507) ^ Math.imul(h1 ^ (h1 >>> 13), 3266489909)
|
|
430
|
-
|
|
431
|
-
return new Uint32Array([h1 >>> 0, h2 >>> 0])
|
|
432
|
-
}
|
|
433
|
-
|
|
420
|
+
|
|
421
|
+
h1 = Math.imul(h1 ^ (h1 >>> 16), 2246822507) ^ Math.imul(h2 ^ (h2 >>> 13), 3266489909)
|
|
422
|
+
h2 = Math.imul(h2 ^ (h2 >>> 16), 2246822507) ^ Math.imul(h1 ^ (h1 >>> 13), 3266489909)
|
|
423
|
+
|
|
424
|
+
return new Uint32Array([h1 >>> 0, h2 >>> 0])
|
|
425
|
+
}
|
|
426
|
+
|
|
434
427
|
/**
|
|
435
428
|
* Convert a bare filename, path, or URL to a UNIX-style path.
|
|
436
429
|
* @param {string|URL} nameOrURL
|
|
437
430
|
* @returns {string} path
|
|
438
431
|
*/
|
|
439
432
|
#getPath(nameOrURL) {
|
|
440
|
-
const url = typeof nameOrURL === 'string' ?
|
|
441
|
-
|
|
442
|
-
nameOrURL;
|
|
443
|
-
return url.pathname;
|
|
433
|
+
const url = typeof nameOrURL === 'string' ? new URL(nameOrURL, 'file://localhost/') : nameOrURL
|
|
434
|
+
return url.pathname
|
|
444
435
|
}
|
|
445
436
|
|
|
446
437
|
/**
|
|
447
438
|
* Remove the association between a path and an OPFS file.
|
|
448
|
-
* @param {string} path
|
|
439
|
+
* @param {string} path
|
|
449
440
|
*/
|
|
450
441
|
#deletePath(path) {
|
|
451
|
-
const accessHandle = this.#mapPathToAccessHandle.get(path)
|
|
442
|
+
const accessHandle = this.#mapPathToAccessHandle.get(path)
|
|
452
443
|
if (accessHandle) {
|
|
453
444
|
// Un-associate the SQLite path from the OPFS file.
|
|
454
|
-
this.#mapPathToAccessHandle.delete(path)
|
|
455
|
-
this.#setAssociatedPath(accessHandle, '', 0)
|
|
445
|
+
this.#mapPathToAccessHandle.delete(path)
|
|
446
|
+
this.#setAssociatedPath(accessHandle, '', 0)
|
|
456
447
|
}
|
|
457
448
|
}
|
|
458
|
-
}
|
|
449
|
+
}
|