/**
A proxy for DB class constructors. It must be called with the
- being-construct DB object as its "this".
+ being-construct DB object as its "this". See the DB constructor
+ for the argument docs. This is split into a separate function
+ in order to enable simple creation of special-case DB constructors,
+ e.g. a hypothetical LocalStorageDB or OpfsDB.
+
+ Expects to be passed a configuration object with the following
+ properties:
+
+ - `.filename`: the db filename. It may be a special name like ":memory:"
+ or "".
+
+ - `.flags`: as documented in the DB constructor.
+
+ - `.vfs`: as documented in the DB constructor.
+
+ It also accepts those as the first 3 arguments.
*/
- const dbCtorHelper = function ctor(fn=':memory:', flags='c', vfsName){
+ const dbCtorHelper = function ctor(...args){
if(!ctor._name2vfs){
// Map special filenames which we handle here (instead of in C)
// to some helpful metadata...
filename: isWorkerThread || (()=>'session')
};
}
- if('string'!==typeof fn){
- toss3("Invalid filename for DB constructor.");
+ const opt = ctor.normalizeArgs(...args);
+ let fn = opt.filename, vfsName = opt.vfs, flagsStr = opt.flags;
+ if(('string'!==typeof fn && 'number'!==typeof fn)
+ || 'string'!==typeof flagsStr
+ || (vfsName && ('string'!==typeof vfsName && 'number'!==typeof vfsName))){
+ console.error("Invalid DB ctor args",opt,arguments);
+ toss3("Invalid arguments for DB constructor.");
}
- const vfsCheck = ctor._name2vfs[fn];
+ let fnJs = ('number'===typeof fn) ? capi.wasm.cstringToJs(fn) : fn;
+ const vfsCheck = ctor._name2vfs[fnJs];
if(vfsCheck){
vfsName = vfsCheck.vfs;
- fn = vfsCheck.filename(fn);
+ fn = fnJs = vfsCheck.filename(fnJs);
}
let ptr, oflags = 0;
- if( flags.indexOf('c')>=0 ){
+ if( flagsStr.indexOf('c')>=0 ){
oflags |= capi.SQLITE_OPEN_CREATE | capi.SQLITE_OPEN_READWRITE;
}
- if( flags.indexOf('w')>=0 ) oflags |= capi.SQLITE_OPEN_READWRITE;
+ if( flagsStr.indexOf('w')>=0 ) oflags |= capi.SQLITE_OPEN_READWRITE;
if( 0===oflags ) oflags |= capi.SQLITE_OPEN_READONLY;
oflags |= capi.SQLITE_OPEN_EXRESCODE;
const stack = capi.wasm.scopedAllocPush();
try {
const ppDb = capi.wasm.scopedAllocPtr() /* output (sqlite3**) arg */;
- const pVfsName = vfsName ? capi.wasm.scopedAllocCString(vfsName) : 0;
+ const pVfsName = vfsName ? (
+ ('number'===typeof vfsName ? vfsName : capi.wasm.scopedAllocCString(vfsName))
+ ): 0;
const rc = capi.sqlite3_open_v2(fn, ppDb, oflags, pVfsName);
ptr = capi.wasm.getPtrValue(ppDb);
checkSqlite3Rc(ptr, rc);
}finally{
capi.wasm.scopedAllocPop(stack);
}
- this.filename = fn;
+ this.filename = fnJs;
__ptrMap.set(this, ptr);
__stmtMap.set(this, Object.create(null));
__udfMap.set(this, Object.create(null));
};
+
+ /**
+ A helper for DB constructors. It accepts either a single
+ config-style object or up to 3 arguments (filename, dbOpenFlags,
+ dbVfsName). It returns a new object containing:
+
+ { filename: ..., flags: ..., vfs: ... }
+
+ If passed an object, any additional properties it has are copied
+ as-is into the new object.
+ */
+ dbCtorHelper.normalizeArgs = function(filename,flags = 'c',vfs = null){
+ const arg = {};
+ if(1===arguments.length && 'object'===typeof arguments[0]){
+ const x = arguments[0];
+ Object.keys(x).forEach((k)=>arg[k] = x[k]);
+ if(undefined===arg.flags) arg.flags = 'c';
+ if(undefined===arg.vfs) arg.vfs = null;
+ }else{
+ arg.filename = filename;
+ arg.flags = flags;
+ arg.vfs = vfs;
+ }
+ return arg;
+ };
/**
The DB class provides a high-level OO wrapper around an sqlite3
or not at all, to use the default. If passed a value, it must
be the string name of a VFS
+ The constructor optionally (and preferably) takes its arguments
+ in the form of a single configuration object with the following
+ properties:
+
+ - `.filename`: database file name
+ - `.flags`: open-mode flags
+ - `.vfs`: the VFS fname
+
+ The `filename` and `vfs` arguments may be either JS strings or
+ C-strings allocated via WASM.
+
For purposes of passing a DB instance to C-style sqlite3
functions, the DB object's read-only `pointer` property holds its
`sqlite3*` pointer value. That property can also be used to check
the database. In this mode, only a single database is permitted
in each storage object. This feature is experimental and subject
to any number of changes (including outright removal). This
- support requires a specific build of sqlite3, the existence of
- which can be determined at runtime by checking for a non-0 return
- value from sqlite3.capi.sqlite3_vfs_find("kvvfs").
+ support requires the kvvfs sqlite3 VFS, the existence of which
+ can be determined at runtime by checking for a non-0 return value
+ from sqlite3.capi.sqlite3_vfs_find("kvvfs").
*/
- const DB = function ctor(fn=':memory:', flags='c', vfsName){
- dbCtorHelper.apply(this, Array.prototype.slice.call(arguments));
+ const DB = function(...args){
+ dbCtorHelper.apply(this, args);
};
/**
closed. After calling close(), `this.pointer` will resolve to
`undefined`, so that can be used to check whether the db
instance is still opened.
+
+ If this.onclose.before is a function then it is called before
+ any close-related cleanup.
+
+ If this.onclose.after is a function then it is called after the
+ db is closed but before auxiliary state like this.filename is
+ cleared.
+
+ Both onclose handlers are passed this object. If this db is not
+ opened, neither of the handlers are called. Any exceptions the
+ handlers throw are ignored because "destructors must not
+ throw."
+
+ Note that garbage collection of a db handle, if it happens at
+ all, will never trigger close(), so onclose handlers are not a
+ reliable way to implement close-time cleanup or maintenance of
+ a db.
*/
close: function(){
if(this.pointer){
+ if(this.onclose && (this.onclose.before instanceof Function)){
+ try{this.onclose.before(this)}
+ catch(e){/*ignore*/}
+ }
const pDb = this.pointer;
- let s;
- const that = this;
Object.keys(__stmtMap.get(this)).forEach((k,s)=>{
if(s && s.pointer) s.finalize();
});
__stmtMap.delete(this);
__udfMap.delete(this);
capi.sqlite3_close_v2(pDb);
+ if(this.onclose && (this.onclose.after instanceof Function)){
+ try{this.onclose.after(this)}
+ catch(e){/*ignore*/}
+ }
delete this.filename;
}
},
}
},
/**
- Similar to this.filename but will return NULL for special names
- like ":memory:". Not of much use until we have filesystem
- support. Throws if the DB has been closed. If passed an
- argument it then it will return the filename of the ATTACHEd db
- with that name, else it assumes a name of `main`.
+ Similar to this.filename but will return a falsy value for
+ special names like ":memory:". Throws if the DB has been
+ closed. If passed an argument it then it will return the
+ filename of the ATTACHEd db with that name, else it assumes a
+ name of `main`.
*/
- fileName: function(dbName='main'){
+ getFilename: function(dbName='main'){
return capi.sqlite3_db_filename(affirmDbOpen(this).pointer, dbName);
},
/**
ooApi: "0.1"
},
DB,
- Stmt
+ Stmt,
+ dbCtorHelper
}/*oo1 object*/;
});
that number will increase as the OPFS API matures).
- The OPFS features used here are only available in dedicated Worker
- threads. This file tries to detect that case and becomes a no-op
- if those features do not seem to be available.
+ threads. This file tries to detect that case, resulting in a
+ rejected Promise if those features do not seem to be available.
- It requires the SharedArrayBuffer and Atomics classes, and the
former is only available if the HTTP server emits the so-called
returned Promise resolves.
On success, the Promise resolves to the top-most sqlite3 namespace
- object.
+ object and that object gets a new object installed in its
+ `opfs` property, containing several OPFS-specific utilities.
*/
sqlite3.installOpfsVfs = function callee(asyncProxyUri = callee.defaultProxyUri){
const options = (asyncProxyUri && 'object'===asyncProxyUri) ? asyncProxyUri : {
options.proxyUri = callee.defaultProxyUri;
}
delete sqlite3.installOpfsVfs;
+
+ /**
+ Generic utilities for working with OPFS. This will get filled out
+ by the Promise setup and, on success, installed as sqlite3.opfs.
+ */
+ const opfsUtil = Object.create(null);
+
const thePromise = new Promise(function(promiseResolve, promiseReject){
const logPrefix = "OPFS syncer:";
const warn = (...args)=>{
const sqlite3_vfs = capi.sqlite3_vfs;
const sqlite3_file = capi.sqlite3_file;
const sqlite3_io_methods = capi.sqlite3_io_methods;
- const StructBinder = sqlite3.StructBinder;
const W = new Worker(options.proxyUri);
- const workerOrigOnError = W.onrror;
+ W._originalOnError = W.onerror /* will be restored later */;
W.onerror = function(err){
promiseReject(new Error("Loading OPFS async Worker failed for unknown reasons."));
};
This object must initially contain only cloneable or sharable
objects. After the worker's "inited" message arrives, other types
of data may be added to it.
+
+ For purposes of Atomics.wait() and Atomics.notify(), we use a
+ SharedArrayBuffer with one slot reserved for each of the API
+ proxy's methods. The sync side of the API uses Atomics.wait()
+ on the corresponding slot and the async side uses
+ Atomics.notify() on that slot.
+
+ The approach of using a single SAB to serialize comms for all
+ instances might(?) lead to deadlock situations in multi-db
+ cases. We should probably have one SAB here with a single slot
+ for locking a per-file initialization step and then allocate a
+ separate SAB like the above one for each file. That will
+ require a bit of acrobatics but should be feasible.
*/
const state = Object.create(null);
state.verbose = options.verbose;
- state.fileBufferSize = 1024 * 64 + 8 /* size of fileHandle.sab. 64k = max sqlite3 page size */;
- state.fbInt64Offset = state.fileBufferSize - 8 /*spot in fileHandle.sab to store an int64*/;
+ state.fileBufferSize =
+ 1024 * 64 + 8 /* size of aFileHandle.sab. 64k = max sqlite3 page
+ size. The additional bytes are space for
+ holding BigInt results, since we cannot store
+ those via the Atomics API (which only works on
+ an Int32Array). */;
+ state.fbInt64Offset =
+ state.fileBufferSize - 8 /*spot in fileHandle.sab to store an int64 result */;
state.opIds = Object.create(null);
{
let i = 0;
state.opIds.xAccess = i++;
state.opIds.xClose = i++;
state.opIds.xDelete = i++;
+ state.opIds.xDeleteNoWait = i++;
state.opIds.xFileSize = i++;
state.opIds.xOpen = i++;
state.opIds.xRead = i++;
state.opIds.xSync = i++;
state.opIds.xTruncate = i++;
state.opIds.xWrite = i++;
+ state.opIds.mkdir = i++;
state.opSAB = new SharedArrayBuffer(i * 4/*sizeof int32*/);
- /* The approach of using a single SAB to serialize comms for all
- instances may(?) lead to deadlock situations in multi-db
- cases. We should probably have one SAB here with a single slot
- for locking a per-file initialization step and then allocate a
- separate SAB like the above one for each file. That will
- require a bit of acrobatics but should be feasible.
- */
}
state.sq3Codes = Object.create(null);
'SQLITE_IOERR_READ', 'SQLITE_IOERR_SHORT_READ',
'SQLITE_IOERR_WRITE', 'SQLITE_IOERR_FSYNC',
'SQLITE_IOERR_TRUNCATE', 'SQLITE_IOERR_DELETE',
- 'SQLITE_IOERR_ACCESS', 'SQLITE_IOERR_CLOSE'
+ 'SQLITE_IOERR_ACCESS', 'SQLITE_IOERR_CLOSE',
+ 'SQLITE_IOERR_DELETE'
].forEach(function(k){
state.sq3Codes[k] = capi[k] || toss("Maintenance required: not found:",k);
state.sq3Codes._reverse[capi[k]] = k;
});
const isWorkerErrCode = (n)=>!!state.sq3Codes._reverse[n];
- const opStore = (op,val=-1)=>Atomics.store(state.opSABView, state.opIds[op], val);
- const opWait = (op,val=-1)=>Atomics.wait(state.opSABView, state.opIds[op], val);
/**
Runs the given operation in the async worker counterpart, waits
given operation's signature in the async API counterpart.
*/
const opRun = (op,args)=>{
- opStore(op);
+ Atomics.store(state.opSABView, state.opIds[op], -1);
wMsg(op, args);
- opWait(op);
+ Atomics.wait(state.opSABView, state.opIds[op], -1);
return Atomics.load(state.opSABView, state.opIds[op]);
};
func with the same signature as described above.
*/
const installMethod = function callee(tgt, name, func){
- if(!(tgt instanceof StructBinder.StructType)){
+ if(!(tgt instanceof sqlite3.StructBinder.StructType)){
toss("Usage error: target object is-not-a StructType.");
}
if(1===arguments.length){
return 0;
},
xDelete: function(pVfs, zName, doSyncDir){
- return opRun('xDelete', {filename: wasm.cstringToJs(zName), syncDir: doSyncDir});
+ opRun('xDelete', {filename: wasm.cstringToJs(zName), syncDir: doSyncDir});
+ /* We're ignoring errors because we cannot yet differentiate
+ between harmless and non-harmless failures. */
+ return 0;
},
xFullPathname: function(pVfs,zName,nOut,pOut){
/* Until/unless we have some notion of "current dir"
for(let k of Object.keys(ioSyncWrappers)) inst(k, ioSyncWrappers[k]);
inst = installMethod(opfsVfs);
for(let k of Object.keys(vfsSyncWrappers)) inst(k, vfsSyncWrappers[k]);
+
+
+ /**
+ Syncronously deletes the given OPFS filesystem entry, ignoring
+ any errors. As this environment has no notion of "current
+ directory", the given name must be an absolute path. If the 2nd
+ argument is truthy, deletion is recursive (use with caution!).
+
+ Returns true if the deletion succeeded and fails if it fails,
+ but cannot report the nature of the failure.
+ */
+ opfsUtil.deleteEntry = function(fsEntryName,recursive){
+ return 0===opRun('xDelete', {filename:fsEntryName, recursive});
+ };
+ /**
+ Exactly like deleteEntry() but runs asynchronously.
+ */
+ opfsUtil.deleteEntryAsync = async function(fsEntryName,recursive){
+ wMsg('xDeleteNoWait', {filename: fsEntryName, recursive});
+ };
+ /**
+ Synchronously creates the given directory name, recursively, in
+ the OPFS filesystem. Returns true if it succeeds or the
+ directory already exists, else false.
+ */
+ opfsUtil.mkdir = async function(absDirName){
+ return 0===opRun('mkdir', absDirName);
+ };
+ /**
+ Synchronously checks whether the given OPFS filesystem exists,
+ returning true if it does, false if it doesn't.
+ */
+ opfsUtil.entryExists = function(fsEntryName){
+ return 0===opRun('xAccess', fsEntryName);
+ };
+
+ /**
+ Generates a random ASCII string, intended for use as a
+ temporary file name. Its argument is the length of the string,
+ defaulting to 16.
+ */
+ opfsUtil.randomFilename = randomFilename;
+
+ if(sqlite3.oo1){
+ opfsUtil.OpfsDb = function(...args){
+ const opt = sqlite3.oo1.dbCtorHelper.normalizeArgs(...args);
+ opt.vfs = opfsVfs.$zName;
+ sqlite3.oo1.dbCtorHelper.call(this, opt);
+ };
+ opfsUtil.OpfsDb.prototype = Object.create(sqlite3.oo1.DB.prototype);
+ }
+
+ /**
+ Potential TODOs:
+
+ - Expose one or both of the Worker objects via opfsUtil and
+ publish an interface for proxying the higher-level OPFS
+ features like getting a directory listing.
+ */
const sanityCheck = async function(){
const scope = wasm.scopedAllocPush();
warn("Running sanity checks because of opfs-sanity-check URL arg...");
sanityCheck();
}
- W.onerror = workerOrigOnError;
+ W.onerror = W._originalOnError;
+ delete W._originalOnError;
+ sqlite3.opfs = opfsUtil;
promiseResolve(sqlite3);
log("End of OPFS sqlite3_vfs setup.", opfsVfs);
}catch(e){
/** State for sqlite3_web_persistent_dir(). */
let __persistentDir;
/**
- An experiment. Do not use.
+ An experiment. Do not use in client code.
If the wasm environment has a persistent storage directory,
its path is returned by this function. If it does not then
environment to determine whether persistence filesystem support
is available and, if it is, enables it (if needed).
+ This function currently only recognizes the WASMFS/OPFS storage
+ combination. "Plain" OPFS is provided via a separate VFS which
+ can optionally be installed (if OPFS is available on the system)
+ using sqlite3.installOpfsVfs().
+
TODOs and caveats:
- If persistent storage is available at the root of the virtual
filesystem, this interface cannot currently distinguish that
- from the lack of persistence. That case cannot currently (with
- WASMFS/OPFS) happen, but it is conceivably possible in future
- environments or non-browser runtimes (none of which are yet
- supported targets).
+ from the lack of persistence. That can (in the mean time)
+ happen when using the JS-native "opfs" VFS, as opposed to the
+ WASMFS/OPFS combination.
*/
capi.sqlite3_web_persistent_dir = function(){
if(undefined !== __persistentDir) return __persistentDir;
capi.wasm.exports.sqlite3_initialize();
}
+ /**
+ Given an `sqlite3*` and an sqlite3_vfs name, returns a truthy
+ value (see below) if that db handle uses that VFS, else returns
+ false. If pDb is falsy then this function returns a truthy value
+ if the default VFS is that VFS. Results are undefined if pDb is
+ truthy but refers to an invalid pointer.
+
+ The 2nd argument may either be a JS string or a C-string
+ allocated from the wasm environment.
+
+ The truthy value it returns is a pointer to the `sqlite3_vfs`
+ object.
+
+ To permit safe use of this function from APIs which may be called
+ via the C stack (like SQL UDFs), this function does not throw: if
+ bad arguments cause a conversion error when passing into
+ wasm-space, false is returned.
+ */
+ capi.sqlite3_web_db_uses_vfs = function(pDb,vfsName){
+ try{
+ const pK = ('number'===vfsName)
+ ? capi.wasm.exports.sqlite3_vfs_find(vfsName)
+ : capi.sqlite3_vfs_find(vfsName);
+ if(!pK) return false;
+ else if(!pDb){
+ return capi.sqlite3_vfs_find(0)===pK ? pK : false;
+ }
+ const ppVfs = capi.wasm.allocPtr();
+ try{
+ return (
+ (0===capi.sqlite3_file_control(
+ pDb, "main", capi.SQLITE_FCNTL_VFS_POINTER, ppVfs
+ )) && (capi.wasm.getPtrValue(ppVfs) === pK)
+ ) ? pK : false;
+ }finally{
+ capi.wasm.dealloc(ppVfs);
+ }
+ }catch(e){
+ /* Ignore - probably bad args to a wasm-bound function. */
+ return false;
+ }
+ };
+
if( self.window===self ){
/* Features specific to the main window thread... */
/**
This routine guesses the approximate amount of
window.localStorage and/or window.sessionStorage in use by the
- kvvfs database backend. Its argument must be one of
+ kvvfs database backend. Its argument must be one of
('session', 'local', ''). In the first two cases, only
sessionStorage resp. localStorage is counted. If it's an empty
string (the default) then both are counted. Only storage keys
return sz * 2 /* because JS uses UC16 encoding */;
};
- /**
- Given an `sqlite3*`, returns a truthy value (see below) if that
- db handle uses the "kvvfs" VFS, else returns false. If pDb is
- NULL then this function returns true if the default VFS is
- "kvvfs". Results are undefined if pDb is truthy but refers to
- an invalid pointer.
-
- The truthy value it returns is a pointer to the kvvfs
- `sqlite3_vfs` object.
- */
- capi.sqlite3_web_db_is_kvvfs = function(pDb){
- const pK = capi.sqlite3_vfs_find("kvvfs");
- if(!pK) return false;
- else if(!pDb){
- return capi.sqlite3_vfs_find(0) && pK;
- }
- const scope = capi.wasm.scopedAllocPush();
- try{
- const ppVfs = capi.wasm.scopedAllocPtr();
- return (
- (0===capi.sqlite3_file_control(
- pDb, "main", capi.SQLITE_FCNTL_VFS_POINTER, ppVfs
- )) && (capi.wasm.getPtrValue(ppVfs) === pK)
- ) ? pK : false;
- }finally{
- capi.wasm.scopedAllocPop(scope);
- }
- };
}/* main-window-only bits */
/* The remainder of the API will be set up in later steps. */
close: function(db,alsoUnlink){
if(db){
delete this.dbs[getDbId(db)];
- const filename = db.fileName();
+ const filename = db.getFilename();
db.close();
if(db===this.defaultDb) this.defaultDb = undefined;
if(alsoUnlink && filename){
+ /* This isn't necessarily correct: the db might be using a
+ VFS other than the default. How do we best resolve this
+ without having to special-case the kvvfs and opfs
+ VFSes? */
sqlite3.capi.wasm.sqlite3_wasm_vfs_unlink(filename);
}
}
experimenting with WASMFS/OPFS-based persistence. Maintenance
reminder: we cannot currently (2022-09-15) load WASMFS in a
worker due to an Emscripten limitation.</li>
- <li><a href='scratchpad-opfs-worker.html'>scratchpad-opfs-worker</a>:
- experimenting with OPFS from a Worker thread (without WASMFS).</li>
<li><a href='test-opfs-vfs.html'>test-opfs-vfs</a>
(<a href='test-opfs-vfs.html?opfs-sanity-check&opfs-verbose'>same
with verbose output and sanity-checking tests</a>) is an
+++ /dev/null
-<!doctype html>
-<html lang="en-us">
- <head>
- <meta charset="utf-8">
- <meta http-equiv="Content-Type" content="text/html; charset=utf-8">
- <link rel="shortcut icon" href="data:image/x-icon;," type="image/x-icon">
- <link rel="stylesheet" href="common/emscripten.css"/>
- <link rel="stylesheet" href="common/testing.css"/>
- <title>sqlite3 OPFS Worker-thread Scratchpad</title>
- </head>
- <body>
- <header id='titlebar'><span>sqlite3 OPFS Worker-thread Scratchpad</span></header>
- <p>All stuff on this page happens in the dev console.</p>
- <hr>
- <div id='test-output'></div>
- <script src="scratchpad-opfs-worker.js"></script>
- </body>
-</html>
+++ /dev/null
-/*
- 2022-05-22
-
- The author disclaims copyright to this source code. In place of a
- legal notice, here is a blessing:
-
- * May you do good and not evil.
- * May you find forgiveness for yourself and forgive others.
- * May you share freely, never taking more than you give.
-
- ***********************************************************************
-
- A basic test script for sqlite3-api.js. This file must be run in
- main JS thread. It will load sqlite3.js in a worker thread.
-*/
-'use strict';
-(function(){
- const toss = function(...args){throw new Error(args.join(' '))};
- const eOutput = document.querySelector('#test-output');
- const logHtml = function(cssClass,...args){
- if(Array.isArray(args[0])) args = args[0];
- const ln = document.createElement('div');
- if(cssClass) ln.classList.add(cssClass);
- ln.append(document.createTextNode(args.join(' ')));
- eOutput.append(ln);
- };
- const log = function(...args){
- logHtml('',...args);
- };
- const error = function(...args){
- logHtml('error',...args);
- };
- const warn = function(...args){
- logHtml('warning',...args);
- };
-
- const W = new Worker("scratchpad-opfs-worker2.js");
- W.onmessage = function(ev){
- ev = ev.data;
- const d = ev.data;
- switch(ev.type){
- case 'stdout': log(d); break;
- case 'stderr': error(d); break;
- default: warn("Unhandled message type:",ev); break;
- }
- };
-})();
+++ /dev/null
-/*
- 2022-05-22
-
- The author disclaims copyright to this source code. In place of a
- legal notice, here is a blessing:
-
- * May you do good and not evil.
- * May you find forgiveness for yourself and forgive others.
- * May you share freely, never taking more than you give.
-
- ***********************************************************************
-
- An experiment for wasmfs/opfs. This file MUST be in the same dir as
- the sqlite3.js emscripten module or that module won't be able to
- resolve the relative URIs (importScript()'s relative URI handling
- is, quite frankly, broken).
-*/
-'use strict';
-
-const toss = function(...args){throw new Error(args.join(' '))};
-/**
- Posts a message in the form {type,data} unless passed more than 2
- args, in which case it posts {type, data:[arg1...argN]}.
-*/
-const wMsg = function(type,data){
- postMessage({
- type,
- data: arguments.length<3
- ? data
- : Array.prototype.slice.call(arguments,1)
- });
-};
-
-const stdout = function(...args){
- wMsg('stdout',args);
- console.log(...args);
-};
-const stderr = function(...args){
- wMsg('stderr',args);
- console.error(...args);
-};
-
-const log = console.log.bind(console);
-const warn = console.warn.bind(console);
-const error = console.error.bind(console);
-
-
-const initOpfsBits = async function(sqlite3){
- if(!self.importScripts || !self.FileSystemFileHandle){
- //|| !self.FileSystemFileHandle.prototype.createSyncAccessHandle){
- // ^^^ sync API is not required with WASMFS/OPFS backend.
- warn("OPFS is not available in this environment.");
- return;
- }else if(!sqlite3.capi.wasm.bigIntEnabled){
- error("OPFS requires BigInt support but sqlite3.capi.wasm.bigIntEnabled is false.");
- return;
- }
- //warn('self.FileSystemFileHandle =',self.FileSystemFileHandle);
- //warn('self.FileSystemFileHandle.prototype =',self.FileSystemFileHandle.prototype);
- const capi = sqlite3.capi,
- wasm = capi.wasm;
- const sqlite3_vfs = capi.sqlite3_vfs
- || toss("Missing sqlite3.capi.sqlite3_vfs object.");
- const sqlite3_file = capi.sqlite3_file
- || toss("Missing sqlite3.capi.sqlite3_file object.");
- const sqlite3_io_methods = capi.sqlite3_io_methods
- || toss("Missing sqlite3.capi.sqlite3_io_methods object.");
- const StructBinder = sqlite3.StructBinder || toss("Missing sqlite3.StructBinder.");
- const debug = console.debug.bind(console),
- log = console.log.bind(console);
- warn("UNDER CONSTRUCTION: setting up OPFS VFS...");
-
- const pDVfs = capi.sqlite3_vfs_find(null)/*pointer to default VFS*/;
- const dVfs = pDVfs
- ? new sqlite3_vfs(pDVfs)
- : null /* dVfs will be null when sqlite3 is built with
- SQLITE_OS_OTHER. Though we cannot currently handle
- that case, the hope is to eventually be able to. */;
- const oVfs = new sqlite3_vfs();
- const oIom = new sqlite3_io_methods();
- oVfs.$iVersion = 2/*yes, two*/;
- oVfs.$szOsFile = capi.sqlite3_file.structInfo.sizeof;
- oVfs.$mxPathname = 1024/*sure, why not?*/;
- oVfs.$zName = wasm.allocCString("opfs");
- oVfs.ondispose = [
- '$zName', oVfs.$zName,
- 'cleanup dVfs', ()=>(dVfs ? dVfs.dispose() : null)
- ];
- if(dVfs){
- oVfs.$xSleep = dVfs.$xSleep;
- oVfs.$xRandomness = dVfs.$xRandomness;
- }
- // All C-side memory of oVfs is zeroed out, but just to be explicit:
- oVfs.$xDlOpen = oVfs.$xDlError = oVfs.$xDlSym = oVfs.$xDlClose = null;
-
- /**
- Pedantic sidebar about oVfs.ondispose: the entries in that array
- are items to clean up when oVfs.dispose() is called, but in this
- environment it will never be called. The VFS instance simply
- hangs around until the WASM module instance is cleaned up. We
- "could" _hypothetically_ clean it up by "importing" an
- sqlite3_os_end() impl into the wasm build, but the shutdown order
- of the wasm engine and the JS one are undefined so there is no
- guaranty that the oVfs instance would be available in one
- environment or the other when sqlite3_os_end() is called (_if_ it
- gets called at all in a wasm build, which is undefined).
- */
-
- /**
- Installs a StructBinder-bound function pointer member of the
- given name and function in the given StructType target object.
- It creates a WASM proxy for the given function and arranges for
- that proxy to be cleaned up when tgt.dispose() is called. Throws
- on the slightest hint of error (e.g. tgt is-not-a StructType,
- name does not map to a struct-bound member, etc.).
-
- Returns a proxy for this function which is bound to tgt and takes
- 2 args (name,func). That function returns the same thing,
- permitting calls to be chained.
-
- If called with only 1 arg, it has no side effects but returns a
- func with the same signature as described above.
- */
- const installMethod = function callee(tgt, name, func){
- if(!(tgt instanceof StructBinder.StructType)){
- toss("Usage error: target object is-not-a StructType.");
- }
- if(1===arguments.length){
- return (n,f)=>callee(tgt,n,f);
- }
- if(!callee.argcProxy){
- callee.argcProxy = function(func,sig){
- return function(...args){
- if(func.length!==arguments.length){
- toss("Argument mismatch. Native signature is:",sig);
- }
- return func.apply(this, args);
- }
- };
- callee.removeFuncList = function(){
- if(this.ondispose.__removeFuncList){
- this.ondispose.__removeFuncList.forEach(
- (v,ndx)=>{
- if('number'===typeof v){
- try{wasm.uninstallFunction(v)}
- catch(e){/*ignore*/}
- }
- /* else it's a descriptive label for the next number in
- the list. */
- }
- );
- delete this.ondispose.__removeFuncList;
- }
- };
- }/*static init*/
- const sigN = tgt.memberSignature(name);
- if(sigN.length<2){
- toss("Member",name," is not a function pointer. Signature =",sigN);
- }
- const memKey = tgt.memberKey(name);
- //log("installMethod",tgt, name, sigN);
- const fProxy = 1
- // We can remove this proxy middle-man once the VFS is working
- ? callee.argcProxy(func, sigN)
- : func;
- const pFunc = wasm.installFunction(fProxy, tgt.memberSignature(name, true));
- tgt[memKey] = pFunc;
- if(!tgt.ondispose) tgt.ondispose = [];
- if(!tgt.ondispose.__removeFuncList){
- tgt.ondispose.push('ondispose.__removeFuncList handler',
- callee.removeFuncList);
- tgt.ondispose.__removeFuncList = [];
- }
- tgt.ondispose.__removeFuncList.push(memKey, pFunc);
- return (n,f)=>callee(tgt, n, f);
- }/*installMethod*/;
-
- /**
- Map of sqlite3_file pointers to OPFS handles.
- */
- const __opfsHandles = Object.create(null);
-
- /**
- Generates a random ASCII string len characters long, intended for
- use as a temporary file name.
- */
- const randomFilename = function f(len=16){
- if(!f._chars){
- f._chars = "abcdefghijklmnopqrstuvwxyz"+
- "ABCDEFGHIJKLMNOPQRSTUVWXYZ"+
- "012346789";
- f._n = f._chars.length;
- }
- const a = [];
- let i = 0;
- for( ; i < len; ++i){
- const ndx = Math.random() * (f._n * 64) % f._n | 0;
- a[i] = f._chars[ndx];
- }
- return a.join('');
- };
-
- const rootDir = await navigator.storage.getDirectory();
- log("rootDir =",rootDir);
-
- ////////////////////////////////////////////////////////////////////////
- // Set up OPFS VFS methods...
- let inst = installMethod(oVfs);
- inst('xOpen', function(pVfs, zName, pFile, flags, pOutFlags){
- const f = new sqlite3_file(pFile);
- f.$pMethods = oIom.pointer;
- __opfsHandles[pFile] = f;
- f.opfsHandle = null /* TODO */;
- if(flags & capi.SQLITE_OPEN_DELETEONCLOSE){
- f.deleteOnClose = true;
- }
- f.filename = zName ? wasm.cstringToJs(zName) : 'sqlite3-xOpen-'+randomFilename();
- error("OPFS sqlite3_vfs::xOpen is not yet full implemented.");
- return capi.SQLITE_IOERR;
- })
- ('xFullPathname', function(pVfs,zName,nOut,pOut){
- /* Until/unless we have some notion of "current dir"
- in OPFS, simply copy zName to pOut... */
- const i = wasm.cstrncpy(pOut, zName, nOut);
- return i<nOut ? 0 : capi.SQLITE_CANTOPEN
- /*CANTOPEN is required by the docs but SQLITE_RANGE would be a closer match*/;
- })
- ('xAccess', function(pVfs,zName,flags,pOut){
- error("OPFS sqlite3_vfs::xAccess is not yet implemented.");
- let fileExists = 0;
- switch(flags){
- case capi.SQLITE_ACCESS_EXISTS: break;
- case capi.SQLITE_ACCESS_READWRITE: break;
- case capi.SQLITE_ACCESS_READ/*docs say this is never used*/:
- default:
- error("Unexpected flags value for sqlite3_vfs::xAccess():",flags);
- return capi.SQLITE_MISUSE;
- }
- wasm.setMemValue(pOut, fileExists, 'i32');
- return 0;
- })
- ('xDelete', function(pVfs, zName, doSyncDir){
- error("OPFS sqlite3_vfs::xDelete is not yet implemented.");
- // https://source.chromium.org/chromium/chromium/src/+/main:third_party/blink/renderer/modules/file_system_access/file_system_handle.idl
- // ==> remove()
- return capi.SQLITE_IOERR;
- })
- ('xGetLastError', function(pVfs,nOut,pOut){
- debug("OPFS sqlite3_vfs::xGetLastError() has nothing sensible to return.");
- return 0;
- })
- ('xCurrentTime', function(pVfs,pOut){
- /* If it turns out that we need to adjust for timezone, see:
- https://stackoverflow.com/a/11760121/1458521 */
- wasm.setMemValue(pOut, 2440587.5 + (new Date().getTime()/86400000),
- 'double');
- return 0;
- })
- ('xCurrentTimeInt64',function(pVfs,pOut){
- // TODO: confirm that this calculation is correct
- wasm.setMemValue(pOut, (2440587.5 * 86400000) + new Date().getTime(),
- 'i64');
- return 0;
- });
- if(!oVfs.$xSleep){
- inst('xSleep', function(pVfs,ms){
- error("sqlite3_vfs::xSleep(",ms,") cannot be implemented from "+
- "JS and we have no default VFS to copy the impl from.");
- return 0;
- });
- }
- if(!oVfs.$xRandomness){
- inst('xRandomness', function(pVfs, nOut, pOut){
- const heap = wasm.heap8u();
- let i = 0;
- for(; i < nOut; ++i) heap[pOut + i] = (Math.random()*255000) & 0xFF;
- return i;
- });
- }
-
- ////////////////////////////////////////////////////////////////////////
- // Set up OPFS sqlite3_io_methods...
- inst = installMethod(oIom);
- inst('xClose', async function(pFile){
- warn("xClose(",arguments,") uses await");
- const f = __opfsHandles[pFile];
- delete __opfsHandles[pFile];
- if(f.opfsHandle){
- await f.opfsHandle.close();
- if(f.deleteOnClose){
- // TODO
- }
- }
- f.dispose();
- return 0;
- })
- ('xRead', /*i(ppij)*/function(pFile,pDest,n,offset){
- /* int (*xRead)(sqlite3_file*, void*, int iAmt, sqlite3_int64 iOfst) */
- try {
- const f = __opfsHandles[pFile];
- const heap = wasm.heap8u();
- const b = new Uint8Array(heap.buffer, pDest, n);
- const nRead = f.opfsHandle.read(b, {at: offset});
- if(nRead<n){
- // MUST zero-fill short reads (per the docs)
- heap.fill(0, dest + nRead, n - nRead);
- }
- return 0;
- }catch(e){
- error("xRead(",arguments,") failed:",e);
- return capi.SQLITE_IOERR_READ;
- }
- })
- ('xWrite', /*i(ppij)*/function(pFile,pSrc,n,offset){
- /* int (*xWrite)(sqlite3_file*, const void*, int iAmt, sqlite3_int64 iOfst) */
- try {
- const f = __opfsHandles[pFile];
- const b = new Uint8Array(wasm.heap8u().buffer, pSrc, n);
- const nOut = f.opfsHandle.write(b, {at: offset});
- if(nOut<n){
- error("xWrite(",arguments,") short write!");
- return capi.SQLITE_IOERR_WRITE;
- }
- return 0;
- }catch(e){
- error("xWrite(",arguments,") failed:",e);
- return capi.SQLITE_IOERR_WRITE;
- }
- })
- ('xTruncate', /*i(pj)*/async function(pFile,sz){
- /* int (*xTruncate)(sqlite3_file*, sqlite3_int64 size) */
- try{
- warn("xTruncate(",arguments,") uses await");
- const f = __opfsHandles[pFile];
- await f.opfsHandle.truncate(sz);
- return 0;
- }
- catch(e){
- error("xTruncate(",arguments,") failed:",e);
- return capi.SQLITE_IOERR_TRUNCATE;
- }
- })
- ('xSync', /*i(pi)*/async function(pFile,flags){
- /* int (*xSync)(sqlite3_file*, int flags) */
- try {
- warn("xSync(",arguments,") uses await");
- const f = __opfsHandles[pFile];
- await f.opfsHandle.flush();
- return 0;
- }catch(e){
- error("xSync(",arguments,") failed:",e);
- return capi.SQLITE_IOERR_SYNC;
- }
- })
- ('xFileSize', /*i(pp)*/async function(pFile,pSz){
- /* int (*xFileSize)(sqlite3_file*, sqlite3_int64 *pSize) */
- try {
- warn("xFileSize(",arguments,") uses await");
- const f = __opfsHandles[pFile];
- const fsz = await f.opfsHandle.getSize();
- capi.wasm.setMemValue(pSz, fsz,'i64');
- return 0;
- }catch(e){
- error("xFileSize(",arguments,") failed:",e);
- return capi.SQLITE_IOERR_SEEK;
- }
- })
- ('xLock', /*i(pi)*/function(pFile,lockType){
- /* int (*xLock)(sqlite3_file*, int) */
- // Opening a handle locks it automatically.
- warn("xLock(",arguments,") is a no-op");
- return 0;
- })
- ('xUnlock', /*i(pi)*/function(pFile,lockType){
- /* int (*xUnlock)(sqlite3_file*, int) */
- // Opening a handle locks it automatically.
- warn("xUnlock(",arguments,") is a no-op");
- return 0;
- })
- ('xCheckReservedLock', /*i(pp)*/function(pFile,pOut){
- /* int (*xCheckReservedLock)(sqlite3_file*, int *pResOut) */
- // Exclusive lock is automatically acquired when opened
- warn("xCheckReservedLock(",arguments,") is a no-op");
- wasm.setMemValue(pOut,1,'i32');
- return 0;
- })
- ('xFileControl', /*i(pip)*/function(pFile,op,pArg){
- /* int (*xFileControl)(sqlite3_file*, int op, void *pArg) */
- debug("xFileControl(",arguments,") is a no-op");
- return capi.SQLITE_NOTFOUND;
- })
- ('xDeviceCharacteristics',/*i(p)*/function(pFile){
- /* int (*xDeviceCharacteristics)(sqlite3_file*) */
- debug("xDeviceCharacteristics(",pFile,")");
- return capi.SQLITE_IOCAP_UNDELETABLE_WHEN_OPEN;
- });
- // xSectorSize may be NULL
- //('xSectorSize', function(pFile){
- // /* int (*xSectorSize)(sqlite3_file*) */
- // log("xSectorSize(",pFile,")");
- // return 4096 /* ==> SQLITE_DEFAULT_SECTOR_SIZE */;
- //})
-
- const rc = capi.sqlite3_vfs_register(oVfs.pointer, 0);
- if(rc){
- oVfs.dispose();
- toss("sqlite3_vfs_register(OPFS) failed with rc",rc);
- }
- capi.sqlite3_vfs_register.addReference(oVfs, oIom);
- warn("End of (very incomplete) OPFS setup.", oVfs);
- //oVfs.dispose()/*only because we can't yet do anything with it*/;
-
-}/*initOpfsBits()*/;
-
-(async function(){
- importScripts('sqlite3.js');
-
- const test1 = function(db){
- db.exec("create table if not exists t(a);")
- .transaction(function(db){
- db.prepare("insert into t(a) values(?)")
- .bind(new Date().getTime())
- .stepFinalize();
- stdout("Number of values in table t:",
- db.selectValue("select count(*) from t"));
- });
- };
-
- const runTests = async function(Module){
- //stdout("Module",Module);
- self._MODULE = Module /* this is only to facilitate testing from the console */;
- const sqlite3 = Module.sqlite3,
- capi = sqlite3.capi,
- oo = sqlite3.oo1,
- wasm = capi.wasm;
- stdout("Loaded sqlite3:",capi.sqlite3_libversion(), capi.sqlite3_sourceid());
-
- if(1){
- let errCount = 0;
- [
- 'FileSystemHandle', 'FileSystemFileHandle', 'FileSystemDirectoryHandle',
- 'FileSystemSyncAccessHandle'
- ].forEach(function(n){
- const f = self[n];
- if(f){
- warn(n,f);
- warn(n+'.prototype',f.prototype);
- }else{
- stderr("MISSING",n);
- ++errCount;
- }
- });
- if(errCount) return;
- }
- warn('self',self);
- await initOpfsBits(sqlite3);
-
- if(1) return;
-
- let persistentDir;
- if(1){
- persistentDir = '';
- }else{
- persistentDir = capi.sqlite3_web_persistent_dir();
- if(persistentDir){
- stderr("Persistent storage dir:",persistentDir);
- }else{
- stderr("No persistent storage available.");
- return;
- }
- }
- const startTime = performance.now();
- let db;
- try {
- db = new oo.DB(persistentDir+'/foo.db');
- stdout("DB filename:",db.filename,db.fileName());
- const banner1 = '>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>',
- banner2 = '<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<';
- [
- test1
- ].forEach((f)=>{
- const n = performance.now();
- stdout(banner1,"Running",f.name+"()...");
- f(db, sqlite3, Module);
- stdout(banner2,f.name+"() took ",(performance.now() - n),"ms");
- });
- }finally{
- if(db) db.close();
- }
- stdout("Total test time:",(performance.now() - startTime),"ms");
- };
-
- sqlite3InitModule(self.sqlite3TestModule).then(runTests);
-})();
let db;
try {
db = new oo.DB(persistentDir+'/foo.db');
- stdout("DB filename:",db.filename,db.fileName());
+ stdout("DB filename:",db.filename,db.getFilename());
const banner1 = '>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>',
banner2 = '<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<';
[
storeAndNotify(opName, state.sq3Codes.SQLITE_NOFOUND);
}
},
- xDelete: async function({filename, syncDir/*ignored*/}){
+ xDeleteNoWait: async function({filename, syncDir, recursive = false}){
/* The syncDir flag is, for purposes of the VFS API's semantics,
ignored here. However, if it has the value 0x1234 then: after
deleting the given file, recursively try to delete any empty
is false.
*/
log("xDelete(",arguments[0],")");
+ let rc = 0;
try {
while(filename){
const [hDir, filenamePart] = await getDirForPath(filename, false);
//log("Removing:",hDir, filenamePart);
if(!filenamePart) break;
- await hDir.removeEntry(filenamePart);
+ await hDir.removeEntry(filenamePart, {recursive});
if(0x1234 !== syncDir) break;
filename = getResolvedPath(filename, true);
filename.pop();
/* Ignoring: _presumably_ the file can't be found or a dir is
not empty. */
//error("Delete failed",filename, e.message);
+ rc = state.sq3Codes.SQLITE_IOERR_DELETE;
+ }
+ return rc;
+ },
+ xDelete: async function(...args){
+ const rc = await vfsAsyncImpls.xDeleteNoWait(...args);
+ storeAndNotify('xDelete', rc);
+ },
+ mkdir: async function(dirname){
+ let rc = 0;
+ try {
+ await getDirForPath(dirname+"/filepart", true);
+ }catch(e){
+ //error("mkdir failed",filename, e.message);
+ rc = state.sq3Codes.SQLITE_IOERR;
}
- storeAndNotify('xDelete', 0);
+ storeAndNotify('mkdir', rc);
},
xFileSize: async function(fid){
log("xFileSize(",arguments,")");
very much incomplete, under construction, and experimental.
<strong>See the dev console for all output.</strong>
</div>
- <div id='test-output'>
+ <div>
+ <a href='?delete'>Use this link</a> to delete the persistent OPFS-side db (if any).
</div>
+ <div id='test-output'></div>
<script>new Worker("test-opfs-vfs.js"+self.location.search);</script>
</body>
</html>
const oVfs = capi.sqlite3_vfs.instanceForPointer(pVfs) || toss("Unexpected instanceForPointer() result.");;
log("OPFS VFS:",pVfs, oVfs);
+ const urlArgs = new URL(self.location.href).searchParams;
const dbFile = "my-persistent.db";
- const db = new sqlite3.oo1.DB(dbFile, "c", "opfs");
+ if(urlArgs.has('delete')) sqlite3.opfs.deleteEntry(dbFile);
+
+ const opfs = sqlite3.opfs;
+ const db = new opfs.OpfsDb(dbFile);
log("db file:",db.filename);
try{
- let n = db.selectValue("select count(*) from sqlite_schema");
- if(n){
+ if(opfs.entryExists(dbFile)){
+ let n = db.selectValue("select count(*) from sqlite_schema");
log("Persistent data found. sqlite_schema entry count =",n);
}
db.transaction((db)=>{
});
});
log("count(*) from t =",db.selectValue("select count(*) from t"));
+
+ // Some sanity checks of the opfs utility functions...
+ const testDir = '/sqlite3-opfs-'+opfs.randomFilename(12);
+ const aDir = testDir+'/test/dir';
+ opfs.mkdir(aDir) || toss("mkdir failed");
+ opfs.mkdir(aDir) || toss("mkdir must pass if the dir exists");
+ opfs.deleteEntry(testDir+'/test') && toss("delete 1 should have failed (dir not empty)");
+ opfs.deleteEntry(testDir+'/test/dir') || toss("delete 2 failed");
+ opfs.deleteEntry(testDir+'/test/dir') && toss("delete 2b should have failed (dir already deleted)");
+ opfs.deleteEntry(testDir,true) || toss("delete 3 failed");
+ opfs.entryExists(testDir) && toss("entryExists(",testDir,") should have failed");
}finally{
db.close();
}
}/*tryOpfsVfs()*/;
importScripts('sqlite3.js');
-self.sqlite3InitModule().then((EmscriptenModule)=>{
- EmscriptenModule.sqlite3.installOpfsVfs()
- .then((sqlite3)=>tryOpfsVfs(sqlite3))
- .catch((e)=>{
- console.error("Error initializing OPFS VFS:",e);
- });
-});
+self.sqlite3InitModule()
+ .then((EmscriptenModule)=>EmscriptenModule.sqlite3.installOpfsVfs())
+ .then((sqlite3)=>tryOpfsVfs(sqlite3))
+ .catch((e)=>{
+ console.error("Error initializing module:",e);
+ });
let dbName = "/testing1.sqlite3";
let vfsName = undefined;
- if(capi.sqlite3_web_db_is_kvvfs()){
+ if(capi.sqlite3_web_db_uses_vfs(0,"kvvfs")){
dbName = "local";
vfsName = 'kvvfs';
logHtml("Found kvvfs. Clearing db(s) from sessionStorage and localStorage",
clearKvvfs();
}
const db = new oo.DB(dbName,'c',vfsName), startTime = performance.now();
- log("capi.sqlite3_web_db_is_kvvfs() ==",capi.sqlite3_web_db_is_kvvfs(db.pointer));
+ log("db is kvvfs?",capi.sqlite3_web_db_uses_vfs(db.pointer,"kvvfs"));
try {
- log("db.filename =",db.filename,"db.fileName() =",db.fileName());
+ log("db.filename =",db.filename,"db.fileName() =",db.getFilename());
const banner1 = '>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>',
banner2 = '<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<';
[
-C Move\sthe\sOPFS\sVFS\sbits\sback\sinto\sapi/sqlite3-api-opfs.js.\sRefactor\sthe\sOPFS\sVFS\sinit\sprocess\sto\suse\sa\sPromise-returning\sfunction\swhich\sthe\sclient\smust\scall,\sas\sthat\seliminates\sany\suncertainty\sabout\swhen\sthe\sVFS\s(necessarily\sactivated\sasynchronously)\sactually\sbecomes\savailable\sto\sthe\sclient.\sRename\sx-sync-async.*\sto\stest-opfs-vfs.*\sMilestone:\sfirst\ssuccessful\stest\sof\sOPFS\swithout\sWASMFS.
-D 2022-09-18T03:05:55.278
+C Numerous\scleanups\sin\sthe\sJS\sbits.\sRemoved\ssome\snow-defunct\swasm\stest\sfiles.\sExpose\ssqlite3.opfs\sobject\scontaining\svarious\sOPFS-specific\sutilities.
+D 2022-09-18T17:32:35.336
F .fossil-settings/empty-dirs dbb81e8fc0401ac46a1491ab34a7f2c7c0452f2f06b54ebb845d024ca8283ef1
F .fossil-settings/ignore-glob 35175cdfcf539b2318cb04a9901442804be81cd677d8b889fcc9149c21f239ea
F LICENSE.md df5091916dbb40e6e9686186587125e1b2ff51f022cc334e886c19a0e9982724
F ext/wasm/api/post-js-header.js 0e853b78db83cb1c06b01663549e0e8b4f377f12f5a2d9a4a06cb776c003880b
F ext/wasm/api/sqlite3-api-cleanup.js 8564a6077cdcaea9a9f428a019af8a05887f0131e6a2a1e72a7ff1145fadfe77
F ext/wasm/api/sqlite3-api-glue.js 366d580c8e5bf7fcf4c6dee6f646c31f5549bd417ea03a59a0acca00e8ecce30
-F ext/wasm/api/sqlite3-api-oo1.js d7526517f7ad3f6bda16ad66d373bbb71b43168deef7af60eda5c9fe873d1387
-F ext/wasm/api/sqlite3-api-opfs.js 87d98f2449d5790efd7044e492166e4ed767e3320a03ed5a173b2b9364fc4896
-F ext/wasm/api/sqlite3-api-prologue.js 48ebca4ae340b0242d4f39bbded01bd0588393c8023628be1c454b4db6f7bd6e
-F ext/wasm/api/sqlite3-api-worker1.js d33062afa045fd4be01ba4abc266801807472558b862b30056211b00c9c347b4
+F ext/wasm/api/sqlite3-api-oo1.js 2d13dddf0d2b4168a9249f124134d37924331e5b55e05dba18b6d661fbeefe48
+F ext/wasm/api/sqlite3-api-opfs.js 4090abf4e16b460543ff665e96822048e37a2703e0ba46a01fed3a15c024c034
+F ext/wasm/api/sqlite3-api-prologue.js 4e3e26880d444000cca1b4f3ddfa9d49581dfecd1de9426080239ecc208c447d
+F ext/wasm/api/sqlite3-api-worker1.js e8456bd9b93eab297d065b25cb7a253835f606f9349383f2aa5c585e8d3b3aef
F ext/wasm/api/sqlite3-wasi.h 25356084cfe0d40458a902afb465df8c21fc4152c1d0a59b563a3fba59a068f9
F ext/wasm/api/sqlite3-wasm.c 4130e2df9587f4e4c3afc04c3549d682c8a5c0cfe5b22819a0a86edb7f01b9bd
F ext/wasm/batch-runner-kvvfs.html ef3b2f553abad4f17a2a29ce6526023793a88e8597b7a24b8c7855a030b90a16
F ext/wasm/fiddle/fiddle-worker.js bccf46045be8824752876f3eec01c223be0616ccac184bffd0024cfe7a3262b8
F ext/wasm/fiddle/fiddle.html 550c5aafce40bd218de9bf26192749f69f9b10bc379423ecd2e162bcef885c08
F ext/wasm/fiddle/fiddle.js 4ffcfc9a235beebaddec689a549e9e0dfad6dca5c1f0b41f03468d7e76480686
-F ext/wasm/index.html 492eb6c9023c9cda391c61702a28bd18e6c986ca2588ec3f384275bb77275285
+F ext/wasm/index.html d698cc021c25ca940f67805c2cc2848c303705d98b4c4f9f55565b9a9c37d2bb
F ext/wasm/jaccwabyt/jaccwabyt.js 0d7f32817456a0f3937fcfd934afeb32154ca33580ab264dab6c285e6dbbd215
F ext/wasm/jaccwabyt/jaccwabyt.md 447cc02b598f7792edaa8ae6853a7847b8178a18ed356afacbdbf312b2588106
F ext/wasm/jaccwabyt/jaccwabyt_test.c 39e4b865a33548f943e2eb9dd0dc8d619a80de05d5300668e9960fff30d0d36f
F ext/wasm/kvvfs.make 4b2ba6d061f3a52da9f5812f86f4faa80fb4d9456a152f6b0585dccd667a4e22
F ext/wasm/kvvfs1.html 13bb24190bfb276a57b228499519badcc1bf39ed07e4b37bc2a425ce6418fed1
F ext/wasm/kvvfs1.js ec1c1d071bb055711f9151df05616111432cf3e6bf7ac7f8dcbcfb56c9d9ed48
-F ext/wasm/scratchpad-opfs-worker.html 5fdda167571264300f388847d34f00b77dd48984a8dba2ee9c099c3ffa05db66
-F ext/wasm/scratchpad-opfs-worker.js cf6c4554d3b099c1a50013e50d19b3dc60e183511b4b4dbe7fabc2b9d3360567
-F ext/wasm/scratchpad-opfs-worker2.js 8c980370bbd5a262d96af8627c443936e11b87d0263a02123769d5953fc146da
F ext/wasm/scratchpad-wasmfs-main.html 20cf6f1a8f368e70d01e8c17200e3eaa90f1c8e1029186d836d14b83845fbe06
-F ext/wasm/scratchpad-wasmfs-main.js 69e960e9161f6412fd0c30f355d4112f1894d6609eb431e2d16d207d1380518e
+F ext/wasm/scratchpad-wasmfs-main.js f0836e3576df7a89390d777bb53e142e559e8a79becfb2a5a976490b05a1c4fa
F ext/wasm/speedtest1-kvvfs.html c8b65c20e2b35298dc02d8e0a394d5e1eb857fd22e504468388234aee13aef08
F ext/wasm/speedtest1-wasmfs.html 6a67a6812f03a2058eb5c6ad0c8dea4bf749d0160ed9d6b826dabe7b766c3cf7
F ext/wasm/speedtest1-worker.html d8881ae802d15fb8adb94049265173e99f350e07e1d4e6f9e1cbd8969fe63a04
F ext/wasm/split-speedtest1-script.sh a3e271938d4d14ee49105eb05567c6a69ba4c1f1293583ad5af0cd3a3779e205 x
F ext/wasm/sql/000-mandelbrot.sql 775337a4b80938ac8146aedf88808282f04d02d983d82675bd63d9c2d97a15f0
F ext/wasm/sql/001-sudoku.sql 35b7cb7239ba5d5f193bc05ec379bcf66891bce6f2a5b3879f2f78d0917299b5
-F ext/wasm/sqlite3-opfs-async-proxy.js 456bef1253fd4732f133b601a4450b7f8461e67af6e8d30bf8a239ad775c77a2
+F ext/wasm/sqlite3-opfs-async-proxy.js 6e89e1af7c616afdd877cbcf5d0ec3d5f47ba252b9a19e696140b9495dc1e653
F ext/wasm/sqlite3-worker1-promiser.js 92b8da5f38439ffec459a8215775d30fa498bc0f1ab929ff341fc3dd479660b9
F ext/wasm/sqlite3-worker1.js 0c1e7626304543969c3846573e080c082bf43bcaa47e87d416458af84f340a9e
-F ext/wasm/test-opfs-vfs.html 3e11c875c28f041891deeea6b2375121845ee1269cac6747df957ec0c7d4d37a w ext/wasm/x-sync-async.html
-F ext/wasm/test-opfs-vfs.js bf70cd553a443b4eda63b577787ef73144f879fa062a20a73bb44e3242c81a15 w ext/wasm/x-sync-async.js
+F ext/wasm/test-opfs-vfs.html eb69dda21eb414b8f5e3f7c1cc0f774103cc9c0f87b2d28a33419e778abfbab5
+F ext/wasm/test-opfs-vfs.js 753c6b86dd8ce0813121add44726a038ba1b83acebdc8414189cb163faf23e6d
F ext/wasm/testing-worker1-promiser.html 6eaec6e04a56cf24cf4fa8ef49d78ce8905dde1354235c9125dca6885f7ce893
F ext/wasm/testing-worker1-promiser.js 63448fddfd3b8c89ff667d17c8b31c6c2259dd4647ebbbd28f3a921c48e924da
F ext/wasm/testing1.html 50575755e43232dbe4c2f97c9086b3118eb91ec2ee1fae931e6d7669fb17fcae
-F ext/wasm/testing1.js 7cd8ab255c238b030d928755ae8e91e7d90a12f2ae601b1b8f7827aaa4fb258e
+F ext/wasm/testing1.js 507001a970fe8a8eb67b6c8d783e1c1daa3db2719f727c4551af29349410e538
F ext/wasm/testing2.html a66951c38137ff1d687df79466351f3c734fa9c6d9cce71d3cf97c291b2167e3
F ext/wasm/testing2.js 25584bcc30f19673ce13a6f301f89f8820a59dfe044e0c4f2913941f4097fe3c
F ext/wasm/wasmfs.make 21a5cf297954a689e0dc2a95299ae158f681cae5e90c10b99d986097815fd42d
F vsixtest/vsixtest.vcxproj.data 2ed517e100c66dc455b492e1a33350c1b20fbcdc
F vsixtest/vsixtest.vcxproj.filters 37e51ffedcdb064aad6ff33b6148725226cd608e
F vsixtest/vsixtest_TemporaryKey.pfx e5b1b036facdb453873e7084e1cae9102ccc67a0
-P 1c660970d0f62bcfd6e698a72b050d99972a1e39f45a5ac24194a190f8f78ab3
-R 039a2cd7ec4da87358ad6184da91bc68
+P b2abf60dbfa6648f671a3932cb65feb28d05a0d5b7f792351d14f9c13d9798c5
+R 79bf69f52699f5d039971f9fe79e1ec3
U stephan
-Z 58e9ce410c47c756bc153984b857c4cf
+Z 77273ebd9a43ebba5449759a950a89f3
# Remove this line to create a well-formed Fossil manifest.
-b2abf60dbfa6648f671a3932cb65feb28d05a0d5b7f792351d14f9c13d9798c5
\ No newline at end of file
+26e625d05d9820033b23536f18ad3ddc59ed712ad507d4b0c7fe88abd15d2be8
\ No newline at end of file