@ehegnes/wa-sqlite 2.0.0-beta.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 +78 -0
- package/dist/wa-sqlite-async.mjs +2 -0
- package/dist/wa-sqlite-async.wasm +0 -0
- package/dist/wa-sqlite-jspi.mjs +2 -0
- package/dist/wa-sqlite-jspi.wasm +0 -0
- package/dist/wa-sqlite.mjs +2 -0
- package/dist/wa-sqlite.wasm +0 -0
- package/package.json +44 -0
- package/src/FacadeVFS.js +681 -0
- package/src/VFS.js +222 -0
- package/src/WebLocksMixin.js +411 -0
- package/src/examples/AccessHandlePoolVFS.js +458 -0
- package/src/examples/IDBBatchAtomicVFS.js +827 -0
- package/src/examples/IDBMirrorVFS.js +889 -0
- package/src/examples/LazyLock.js +90 -0
- package/src/examples/Lock.js +69 -0
- package/src/examples/MemoryAsyncVFS.js +100 -0
- package/src/examples/MemoryVFS.js +176 -0
- package/src/examples/OPFSAdaptiveVFS.js +437 -0
- package/src/examples/OPFSAnyContextVFS.js +300 -0
- package/src/examples/OPFSCoopSyncVFS.js +597 -0
- package/src/examples/OPFSPermutedVFS.js +1217 -0
- package/src/examples/OPFSWriteAheadVFS.js +960 -0
- package/src/examples/README.md +81 -0
- package/src/examples/WriteAhead.js +1174 -0
- package/src/examples/tag.js +82 -0
- package/src/sqlite-api.js +924 -0
- package/src/sqlite-constants.js +275 -0
- package/src/types/globals.d.ts +60 -0
- package/src/types/index.d.ts +1228 -0
- package/src/types/tsconfig.json +6 -0
- package/test/AccessHandlePoolVFS.test.js +27 -0
- package/test/IDBBatchAtomicVFS.test.js +97 -0
- package/test/IDBMirrorVFS.test.js +27 -0
- package/test/MemoryAsyncVFS.test.js +27 -0
- package/test/MemoryVFS.test.js +27 -0
- package/test/OPFSAdaptiveVFS.test.js +27 -0
- package/test/OPFSAnyContextVFS.test.js +27 -0
- package/test/OPFSCoopSyncVFS.test.js +27 -0
- package/test/OPFSWriteAheadVFS.test.js +27 -0
- package/test/TestContext.js +96 -0
- package/test/WebLocksMixin.test.js +521 -0
- package/test/api.test.js +49 -0
- package/test/api_exec.js +89 -0
- package/test/api_misc.js +63 -0
- package/test/api_statements.js +447 -0
- package/test/callbacks.test.js +581 -0
- package/test/data/idbv5.json +1 -0
- package/test/sql.test.js +64 -0
- package/test/sql_0001.js +49 -0
- package/test/sql_0002.js +52 -0
- package/test/sql_0003.js +83 -0
- package/test/sql_0004.js +81 -0
- package/test/sql_0005.js +76 -0
- package/test/test-worker.js +204 -0
- package/test/vfs_xAccess.js +2 -0
- package/test/vfs_xClose.js +52 -0
- package/test/vfs_xOpen.js +91 -0
- package/test/vfs_xRead.js +38 -0
- package/test/vfs_xWrite.js +36 -0
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
import { Lock } from './Lock.js';
|
|
2
|
+
|
|
3
|
+
export class LazyLock extends Lock {
|
|
4
|
+
#channel;
|
|
5
|
+
#isBusy = false;
|
|
6
|
+
#hasReleaseRequest = false;
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* @param {string} name
|
|
10
|
+
*/
|
|
11
|
+
constructor(name) {
|
|
12
|
+
super(name);
|
|
13
|
+
this.#channel = new BroadcastChannel(name);
|
|
14
|
+
this.#channel.onmessage = (event) => {
|
|
15
|
+
if (this.#isBusy) {
|
|
16
|
+
// We're using the lock so postpone the release.
|
|
17
|
+
this.#hasReleaseRequest = true;
|
|
18
|
+
} else {
|
|
19
|
+
this.release();
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
close() {
|
|
25
|
+
super.close();
|
|
26
|
+
this.#channel.onmessage = null;
|
|
27
|
+
this.#channel.close();
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* @param {LockMode} mode
|
|
32
|
+
* @param {number} timeout
|
|
33
|
+
* @returns {Promise<boolean>}
|
|
34
|
+
*/
|
|
35
|
+
async acquire(mode, timeout = -1) {
|
|
36
|
+
this.#isBusy = true;
|
|
37
|
+
try {
|
|
38
|
+
if (mode === this.mode) {
|
|
39
|
+
// We never had to release the lock.
|
|
40
|
+
return true;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
if (this.mode) {
|
|
44
|
+
// Release the lock to acquire it in a different mode.
|
|
45
|
+
super.release();
|
|
46
|
+
} else {
|
|
47
|
+
// Poll for the lock. This isn't necessary but if it works it avoids
|
|
48
|
+
// the BroadcastChannel traffic.
|
|
49
|
+
if (await super.acquire(mode, 0)) {
|
|
50
|
+
return true;
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
// Request the lock.
|
|
55
|
+
const pResult = super.acquire(mode, timeout)
|
|
56
|
+
this.#channel.postMessage({});
|
|
57
|
+
|
|
58
|
+
return await pResult;
|
|
59
|
+
} catch (e) {
|
|
60
|
+
this.release();
|
|
61
|
+
throw e;
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
* @param {LockMode} mode
|
|
67
|
+
* @returns {boolean}
|
|
68
|
+
*/
|
|
69
|
+
acquireIfHeld(mode) {
|
|
70
|
+
if (mode === this.mode) {
|
|
71
|
+
this.#isBusy = true;
|
|
72
|
+
return true;
|
|
73
|
+
}
|
|
74
|
+
return false;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
release() {
|
|
78
|
+
super.release();
|
|
79
|
+
this.#isBusy = false;
|
|
80
|
+
this.#hasReleaseRequest = false;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
releaseLazy() {
|
|
84
|
+
// Release the lock only if someone else wants it.
|
|
85
|
+
this.#isBusy = false;
|
|
86
|
+
if (this.#hasReleaseRequest) {
|
|
87
|
+
this.release();
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
}
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
// This is a convenience wrapper for the Web Locks API.
|
|
2
|
+
export class Lock {
|
|
3
|
+
#name;
|
|
4
|
+
/** @type {LockMode?} */ #mode = null;
|
|
5
|
+
/** @type {Promise<Function|null>} */ #releaser = Promise.resolve(null);
|
|
6
|
+
#isAcquiring = false;
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* @param {string} name
|
|
10
|
+
*/
|
|
11
|
+
constructor(name) {
|
|
12
|
+
this.#name = name;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
get name() { return this.#name; }
|
|
16
|
+
get mode() { return this.#mode; }
|
|
17
|
+
|
|
18
|
+
close() {
|
|
19
|
+
this.release();
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* @param {'shared'|'exclusive'} mode
|
|
24
|
+
* @param {number} timeout -1 for infinite, 0 for poll, >0 for milliseconds
|
|
25
|
+
* @return {Promise<boolean>} true if lock acquired, false on failed poll
|
|
26
|
+
*/
|
|
27
|
+
async acquire(mode, timeout = -1) {
|
|
28
|
+
if (this.#isAcquiring) throw new Error('Lock is already being acquired');
|
|
29
|
+
this.#isAcquiring = true;
|
|
30
|
+
try {
|
|
31
|
+
if (this.#mode) {
|
|
32
|
+
throw new Error(`Lock ${this.#name} is already acquired`);
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
this.#releaser = new Promise((resolve, reject) => {
|
|
36
|
+
/** @type {LockOptions} */
|
|
37
|
+
const options = { mode, ifAvailable: timeout === 0 };
|
|
38
|
+
if (timeout > 0) {
|
|
39
|
+
options.signal = AbortSignal.timeout(timeout);
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
navigator.locks.request(this.#name, options, lock => {
|
|
43
|
+
if (lock === null) {
|
|
44
|
+
// Polling (with timeout = 0) did not acquire the lock.
|
|
45
|
+
return resolve(null);
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
// Lock acquired. The lock is released when this returned
|
|
49
|
+
// Promise is resolved.
|
|
50
|
+
this.#mode = mode;
|
|
51
|
+
return new Promise(releaser => {
|
|
52
|
+
resolve(releaser);
|
|
53
|
+
})
|
|
54
|
+
}).catch(e => {
|
|
55
|
+
return reject(e);
|
|
56
|
+
});
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
return this.#releaser.then(releaser => !!releaser)
|
|
60
|
+
} finally {
|
|
61
|
+
this.#isAcquiring = false;
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
release() {
|
|
66
|
+
this.#releaser.then(releaser => releaser?.(), () => {});
|
|
67
|
+
this.#mode = null;
|
|
68
|
+
}
|
|
69
|
+
}
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
// Copyright 2024 Roy T. Hashimoto. All Rights Reserved.
|
|
2
|
+
import { MemoryVFS } from './MemoryVFS.js';
|
|
3
|
+
|
|
4
|
+
// Sample asynchronous in-memory filesystem. This filesystem requires an
|
|
5
|
+
// asynchronous WebAssembly build (Asyncify or JSPI).
|
|
6
|
+
export class MemoryAsyncVFS extends MemoryVFS {
|
|
7
|
+
|
|
8
|
+
static async create(name, module) {
|
|
9
|
+
const vfs = new MemoryVFS(name, module);
|
|
10
|
+
await vfs.isReady();
|
|
11
|
+
return vfs;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
constructor(name, module) {
|
|
15
|
+
super(name, module);
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
async close() {
|
|
19
|
+
for (const fileId of this.mapIdToFile.keys()) {
|
|
20
|
+
await this.xClose(fileId);
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* @param {string?} name
|
|
26
|
+
* @param {number} fileId
|
|
27
|
+
* @param {number} flags
|
|
28
|
+
* @param {DataView} pOutFlags
|
|
29
|
+
* @returns {Promise<number>}
|
|
30
|
+
*/
|
|
31
|
+
async jOpen(name, fileId, flags, pOutFlags) {
|
|
32
|
+
return super.jOpen(name, fileId, flags, pOutFlags);
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* @param {number} fileId
|
|
37
|
+
* @returns {Promise<number>}
|
|
38
|
+
*/
|
|
39
|
+
async jClose(fileId) {
|
|
40
|
+
return super.jClose(fileId);
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* @param {number} fileId
|
|
45
|
+
* @param {Uint8Array} pData
|
|
46
|
+
* @param {number} iOffset
|
|
47
|
+
* @returns {Promise<number>}
|
|
48
|
+
*/
|
|
49
|
+
async jRead(fileId, pData, iOffset) {
|
|
50
|
+
return super.jRead(fileId, pData, iOffset);
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* @param {number} fileId
|
|
55
|
+
* @param {Uint8Array} pData
|
|
56
|
+
* @param {number} iOffset
|
|
57
|
+
* @returns {Promise<number>}
|
|
58
|
+
*/
|
|
59
|
+
async jWrite(fileId, pData, iOffset) {
|
|
60
|
+
return super.jWrite(fileId, pData, iOffset);
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* @param {number} fileId
|
|
65
|
+
* @param {number} iSize
|
|
66
|
+
* @returns {Promise<number>}
|
|
67
|
+
*/
|
|
68
|
+
async xTruncate(fileId, iSize) {
|
|
69
|
+
return super.jTruncate(fileId, iSize);
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
/**
|
|
73
|
+
* @param {number} fileId
|
|
74
|
+
* @param {DataView} pSize64
|
|
75
|
+
* @returns {Promise<number>}
|
|
76
|
+
*/
|
|
77
|
+
async jFileSize(fileId, pSize64) {
|
|
78
|
+
return super.jFileSize(fileId, pSize64);
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
/**
|
|
82
|
+
*
|
|
83
|
+
* @param {string} name
|
|
84
|
+
* @param {number} syncDir
|
|
85
|
+
* @returns {Promise<number>}
|
|
86
|
+
*/
|
|
87
|
+
async jDelete(name, syncDir) {
|
|
88
|
+
return super.jDelete(name, syncDir);
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
/**
|
|
92
|
+
* @param {string} name
|
|
93
|
+
* @param {number} flags
|
|
94
|
+
* @param {DataView} pResOut
|
|
95
|
+
* @returns {Promise<number>}
|
|
96
|
+
*/
|
|
97
|
+
async jAccess(name, flags, pResOut) {
|
|
98
|
+
return super.jAccess(name, flags, pResOut);
|
|
99
|
+
}
|
|
100
|
+
}
|
|
@@ -0,0 +1,176 @@
|
|
|
1
|
+
// Copyright 2024 Roy T. Hashimoto. All Rights Reserved.
|
|
2
|
+
import { FacadeVFS } from '../FacadeVFS.js';
|
|
3
|
+
import * as VFS from '../VFS.js';
|
|
4
|
+
|
|
5
|
+
// Sample in-memory filesystem.
|
|
6
|
+
export class MemoryVFS extends FacadeVFS {
|
|
7
|
+
// Map of existing files, keyed by filename.
|
|
8
|
+
mapNameToFile = new Map();
|
|
9
|
+
|
|
10
|
+
// Map of open files, keyed by id (sqlite3_file pointer).
|
|
11
|
+
mapIdToFile = new Map();
|
|
12
|
+
|
|
13
|
+
static async create(name, module) {
|
|
14
|
+
const vfs = new MemoryVFS(name, module);
|
|
15
|
+
await vfs.isReady();
|
|
16
|
+
return vfs;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
constructor(name, module) {
|
|
20
|
+
super(name, module);
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
close() {
|
|
24
|
+
for (const fileId of this.mapIdToFile.keys()) {
|
|
25
|
+
this.jClose(fileId);
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* @param {string?} filename
|
|
31
|
+
* @param {number} fileId
|
|
32
|
+
* @param {number} flags
|
|
33
|
+
* @param {DataView} pOutFlags
|
|
34
|
+
* @returns {number|Promise<number>}
|
|
35
|
+
*/
|
|
36
|
+
jOpen(filename, fileId, flags, pOutFlags) {
|
|
37
|
+
const url = new URL(filename || Math.random().toString(36).slice(2), 'file://');
|
|
38
|
+
const pathname = url.pathname;
|
|
39
|
+
|
|
40
|
+
let file = this.mapNameToFile.get(pathname);
|
|
41
|
+
if (!file) {
|
|
42
|
+
if (flags & VFS.SQLITE_OPEN_CREATE) {
|
|
43
|
+
// Create a new file object.
|
|
44
|
+
file = {
|
|
45
|
+
pathname,
|
|
46
|
+
flags,
|
|
47
|
+
size: 0,
|
|
48
|
+
data: new ArrayBuffer(0)
|
|
49
|
+
};
|
|
50
|
+
this.mapNameToFile.set(pathname, file);
|
|
51
|
+
} else {
|
|
52
|
+
return VFS.SQLITE_CANTOPEN;
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
// Put the file in the opened files map.
|
|
57
|
+
this.mapIdToFile.set(fileId, file);
|
|
58
|
+
pOutFlags.setInt32(0, flags, true);
|
|
59
|
+
return VFS.SQLITE_OK;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* @param {number} fileId
|
|
64
|
+
* @returns {number|Promise<number>}
|
|
65
|
+
*/
|
|
66
|
+
jClose(fileId) {
|
|
67
|
+
const file = this.mapIdToFile.get(fileId);
|
|
68
|
+
this.mapIdToFile.delete(fileId);
|
|
69
|
+
|
|
70
|
+
if (file.flags & VFS.SQLITE_OPEN_DELETEONCLOSE) {
|
|
71
|
+
this.mapNameToFile.delete(file.pathname);
|
|
72
|
+
}
|
|
73
|
+
return VFS.SQLITE_OK;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
/**
|
|
77
|
+
* @param {number} fileId
|
|
78
|
+
* @param {Uint8Array} pData
|
|
79
|
+
* @param {number} iOffset
|
|
80
|
+
* @returns {number|Promise<number>}
|
|
81
|
+
*/
|
|
82
|
+
jRead(fileId, pData, iOffset) {
|
|
83
|
+
const file = this.mapIdToFile.get(fileId);
|
|
84
|
+
|
|
85
|
+
// Clip the requested read to the file boundary.
|
|
86
|
+
const bgn = Math.min(iOffset, file.size);
|
|
87
|
+
const end = Math.min(iOffset + pData.byteLength, file.size);
|
|
88
|
+
const nBytes = end - bgn;
|
|
89
|
+
|
|
90
|
+
if (nBytes) {
|
|
91
|
+
pData.set(new Uint8Array(file.data, bgn, nBytes));
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
if (nBytes < pData.byteLength) {
|
|
95
|
+
// Zero unused area of read buffer.
|
|
96
|
+
pData.fill(0, nBytes);
|
|
97
|
+
return VFS.SQLITE_IOERR_SHORT_READ;
|
|
98
|
+
}
|
|
99
|
+
return VFS.SQLITE_OK;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
/**
|
|
103
|
+
* @param {number} fileId
|
|
104
|
+
* @param {Uint8Array} pData
|
|
105
|
+
* @param {number} iOffset
|
|
106
|
+
* @returns {number|Promise<number>}
|
|
107
|
+
*/
|
|
108
|
+
jWrite(fileId, pData, iOffset) {
|
|
109
|
+
const file = this.mapIdToFile.get(fileId);
|
|
110
|
+
if (iOffset + pData.byteLength > file.data.byteLength) {
|
|
111
|
+
// Resize the ArrayBuffer to hold more data.
|
|
112
|
+
const newSize = Math.max(iOffset + pData.byteLength, 2 * file.data.byteLength);
|
|
113
|
+
const data = new ArrayBuffer(newSize);
|
|
114
|
+
new Uint8Array(data).set(new Uint8Array(file.data, 0, file.size));
|
|
115
|
+
file.data = data;
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
// Copy data.
|
|
119
|
+
new Uint8Array(file.data, iOffset, pData.byteLength).set(pData.subarray());
|
|
120
|
+
file.size = Math.max(file.size, iOffset + pData.byteLength);
|
|
121
|
+
return VFS.SQLITE_OK;
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
/**
|
|
125
|
+
* @param {number} fileId
|
|
126
|
+
* @param {number} iSize
|
|
127
|
+
* @returns {number|Promise<number>}
|
|
128
|
+
*/
|
|
129
|
+
jTruncate(fileId, iSize) {
|
|
130
|
+
const file = this.mapIdToFile.get(fileId);
|
|
131
|
+
|
|
132
|
+
// For simplicity we don't make the ArrayBuffer smaller.
|
|
133
|
+
file.size = Math.min(file.size, iSize);
|
|
134
|
+
return VFS.SQLITE_OK;
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
/**
|
|
138
|
+
* @param {number} fileId
|
|
139
|
+
* @param {DataView} pSize64
|
|
140
|
+
* @returns {number|Promise<number>}
|
|
141
|
+
*/
|
|
142
|
+
jFileSize(fileId, pSize64) {
|
|
143
|
+
const file = this.mapIdToFile.get(fileId);
|
|
144
|
+
|
|
145
|
+
pSize64.setBigInt64(0, BigInt(file.size), true);
|
|
146
|
+
return VFS.SQLITE_OK;
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
/**
|
|
150
|
+
* @param {string} name
|
|
151
|
+
* @param {number} syncDir
|
|
152
|
+
* @returns {number|Promise<number>}
|
|
153
|
+
*/
|
|
154
|
+
jDelete(name, syncDir) {
|
|
155
|
+
const url = new URL(name, 'file://');
|
|
156
|
+
const pathname = url.pathname;
|
|
157
|
+
|
|
158
|
+
this.mapNameToFile.delete(pathname);
|
|
159
|
+
return VFS.SQLITE_OK;
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
/**
|
|
163
|
+
* @param {string} name
|
|
164
|
+
* @param {number} flags
|
|
165
|
+
* @param {DataView} pResOut
|
|
166
|
+
* @returns {number|Promise<number>}
|
|
167
|
+
*/
|
|
168
|
+
jAccess(name, flags, pResOut) {
|
|
169
|
+
const url = new URL(name, 'file://');
|
|
170
|
+
const pathname = url.pathname;
|
|
171
|
+
|
|
172
|
+
const file = this.mapNameToFile.get(pathname);
|
|
173
|
+
pResOut.setInt32(0, file ? 1 : 0, true);
|
|
174
|
+
return VFS.SQLITE_OK;
|
|
175
|
+
}
|
|
176
|
+
}
|