--- /dev/null
+//#if not omit-kvvfs
+/*
+ 2025-11-21
+
+ 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.
+
+ ***********************************************************************
+
+ This file houses the "kvvfs" pieces of the JS API.
+
+ Main project home page: https://sqlite.org
+
+ Documentation home page: https://sqlite.org/wasm
+*/
+globalThis.sqlite3ApiBootstrap.initializers.push(function(sqlite3){
+ 'use strict';
+ /* We unregister the kvvfs VFS from Worker threads later on. */
+ const util = sqlite3.util,
+ capi = sqlite3.capi,
+ wasm = sqlite3.wasm,
+ sqlite3_kvvfs_methods = capi.sqlite3_kvvfs_methods,
+ pKvvfs = sqlite3.capi.sqlite3_vfs_find("kvvfs");
+ delete capi.sqlite3_kvvfs_methods /* this is JS plumbing, not part
+ of the public API */;
+ if( !pKvvfs ) return /* built without kvvfs */;
+
+ if( !util.isUIThread() ){
+ /* One test currently relies on this VFS not being visible
+ in Workers. If we add generic object storage, we can
+ retain this VFS in Workers. */
+ capi.sqlite3_vfs_unregister(pKvvfs);
+ return;
+ }
+
+ /**
+ Internal helper for sqlite3_js_kvvfs_clear() and friends.
+ Its argument should be one of ('local','session',"").
+ */
+ const __kvfsWhich = function(which){
+ const rc = Object.create(null);
+ rc.prefix = 'kvvfs-'+which;
+ rc.stores = [];
+ if('session'===which || ""===which) rc.stores.push(globalThis.sessionStorage);
+ if('local'===which || ""===which) rc.stores.push(globalThis.localStorage);
+ return rc;
+ };
+
+ /**
+ Clears all storage used by the kvvfs DB backend, deleting any
+ DB(s) stored there. Its argument must be either 'session',
+ 'local', or "". In the first two cases, only sessionStorage
+ resp. localStorage is cleared. If it's an empty string (the
+ default) then both are cleared. Only storage keys which match
+ the pattern used by kvvfs are cleared: any other client-side
+ data are retained.
+
+ This function is only available in the main window thread.
+
+ Returns the number of entries cleared.
+ */
+ capi.sqlite3_js_kvvfs_clear = function(which=""){
+ let rc = 0;
+ const kvWhich = __kvfsWhich(which);
+ kvWhich.stores.forEach((s)=>{
+ const toRm = [] /* keys to remove */;
+ let i;
+ for( i = 0; i < s.length; ++i ){
+ const k = s.key(i);
+ if(k.startsWith(kvWhich.prefix)) toRm.push(k);
+ }
+ toRm.forEach((kk)=>s.removeItem(kk));
+ rc += toRm.length;
+ });
+ return rc;
+ };
+
+ /**
+ 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 ('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 which match
+ the pattern used by kvvfs are counted. The returned value is
+ twice the "length" value of every matching key and value,
+ noting that JavaScript stores each character in 2 bytes.
+
+ The returned size is not authoritative from the perspective of
+ how much data can fit into localStorage and sessionStorage, as
+ the precise algorithms for determining those limits are
+ unspecified and may include per-entry overhead invisible to
+ clients.
+ */
+ capi.sqlite3_js_kvvfs_size = function(which=""){
+ let sz = 0;
+ const kvWhich = __kvfsWhich(which);
+ kvWhich.stores.forEach((s)=>{
+ let i;
+ for(i = 0; i < s.length; ++i){
+ const k = s.key(i);
+ if(k.startsWith(kvWhich.prefix)){
+ sz += k.length;
+ sz += s.getItem(k).length;
+ }
+ }
+ });
+ return sz * 2 /* because JS uses 2-byte char encoding */;
+ };
+
+ const kvvfsMakeKey = wasm.exports.sqlite3__wasm_kvvfsMakeKeyOnPstack;
+ const pstack = wasm.pstack;
+ const kvvfsStorage = (zClass)=>((115/*=='s'*/===wasm.peek(zClass))
+ ? sessionStorage : localStorage);
+
+ { /* Override native sqlite3_kvvfs_methods */
+ const kvvfsMethods = new sqlite3_kvvfs_methods(
+ /* Wraps the static sqlite3_api_methods singleton */
+ wasm.exports.sqlite3__wasm_kvvfs_methods()
+ );
+ try{
+ /**
+ Implementations for members of the object referred to by
+ sqlite3__wasm_kvvfs_methods(). We swap out the native
+ implementations with these, which use JS Storage for their
+ backing store.
+
+ The interface docs for these methods are in
+ src/os_kv.c:kvstorageRead(), kvstorageWrite(), and
+ kvstorageDelete().
+ */
+ for(const e of Object.entries({
+ xRead: (zClass, zKey, zBuf, nBuf)=>{
+ const stack = pstack.pointer,
+ astack = wasm.scopedAllocPush();
+ try {
+ const zXKey = kvvfsMakeKey(zClass,zKey);
+ if(!zXKey) return -3/*OOM*/;
+ const jKey = wasm.cstrToJs(zXKey);
+ const jV = kvvfsStorage(zClass).getItem(jKey);
+ if(!jV) return -1;
+ const nV = jV.length /* We are relying 100% on v being
+ ASCII so that jV.length is equal
+ to the C-string's byte length. */;
+ if(nBuf<=0) return nV;
+ else if(1===nBuf){
+ wasm.poke(zBuf, 0);
+ return nV;
+ }
+ const zV = wasm.scopedAllocCString(jV);
+ if(nBuf > nV + 1) nBuf = nV + 1;
+ wasm.heap8u().copyWithin(
+ Number(zBuf), Number(zV), wasm.ptr.addn(zV, nBuf,- 1)
+ );
+ wasm.poke(wasm.ptr.add(zBuf, nBuf, -1), 0);
+ return nBuf - 1;
+ }catch(e){
+ sqlite3.config.error("kvstorageRead()",e);
+ return -2;
+ }finally{
+ pstack.restore(stack);
+ wasm.scopedAllocPop(astack);
+ }
+ },
+ xWrite: (zClass, zKey, zData)=>{
+ const stack = pstack.pointer;
+ try {
+ const zXKey = kvvfsMakeKey(zClass,zKey);
+ if(!zXKey) return 1/*OOM*/;
+ const jKey = wasm.cstrToJs(zXKey);
+ kvvfsStorage(zClass).setItem(jKey, wasm.cstrToJs(zData));
+ return 0;
+ }catch(e){
+ sqlite3.config.error("kvstorageWrite()",e);
+ return capi.SQLITE_IOERR;
+ }finally{
+ pstack.restore(stack);
+ }
+ },
+ xDelete: (zClass, zKey)=>{
+ const stack = pstack.pointer;
+ try {
+ const zXKey = kvvfsMakeKey(zClass,zKey);
+ if(!zXKey) return 1/*OOM*/;
+ kvvfsStorage(zClass).removeItem(wasm.cstrToJs(zXKey));
+ return 0;
+ }catch(e){
+ sqlite3.config.error("kvstorageDelete()",e);
+ return capi.SQLITE_IOERR;
+ }finally{
+ pstack.restore(stack);
+ }
+ }
+ })){
+ kvvfsMethods[kvvfsMethods.memberKey(e[0])] =
+ wasm.installFunction(kvvfsMethods.memberSignature(e[0]), e[1]);
+ }
+ }finally{
+ kvvfsMethods.dispose();
+ }
+ }/* Override native sqlite3_api_methods */
+
+ /**
+ After initial refactoring to support the use of arbitrary Storage
+ objects (the interface from which localStorage and sessionStorage
+ dervie), we will apparently need to override some of the
+ associated sqlite3_vfs and sqlite3_io_methods members.
+
+ We can call back into the native impls when needed, but we need
+ to override certain operations here to bypass its strict
+ db-naming rules (which, funnily enough, are in place because
+ they're relevant (only) for this browser-side
+ implementation). Apropos: the port to generic objects would also
+ make non-persistent kvvfs available in Worker threads and
+ non-browser builds.
+ */
+ const eventualTodo = 1 || {
+ vfsMethods:{
+ /**
+ */
+ xOpen: function(pProtoVfs,zName,pProtoFile,flags,pOutFlags){},
+ xDelete: function(pVfs, zName, iSyncFlag){},
+ xAccess:function(pProtoVfs, zPath, flags, pResOut){},
+ xFullPathname: function(pVfs, zPath, nOut, zOut){},
+ xDlOpen: function(pVfs, zFilename){},
+ xSleep: function(pVfs,usec){},
+ xCurrentTime: function(pVfs,pOutDbl){},
+ xCurrentTimeInt64: function(pVfs,pOutI64){},
+ xRandomness: function(pVfs, nOut, pOut){
+ const heap = wasm.heap8u();
+ let i = 0;
+ const npOut = Number(pOut);
+ for(; i < nOut; ++i) heap[npOut + i] = (Math.random()*255000) & 0xFF;
+ return i;
+ }
+ },
+
+ /**
+ kvvfs has separate impls for some of the I/O methods,
+ depending on whether it's a db or journal file.
+ */
+ ioMethods:{
+ db:{
+ xClose: function(pFile){},
+ xRead: function(pFile,pTgt,n,iOff64){},
+ xWrite: function(pFile,pSrc,n,iOff64){},
+ xTruncate: function(pFile,i64){},
+ xSync: function(pFile,flags){},
+ xFileControl: function(pFile, opId, pArg){},
+ xFileSize: function(pFile,pi64Out){},
+ xLock: function(pFile,iLock){},
+ xUnlock: function(pFile,iLock){},
+ xCheckReservedLock: function(pFile,piOut){},
+ xFileControl: function(pFile,iOp,pArg){},
+ xSectorSize: function(pFile){},
+ xDeviceCharacteristics: function(pFile){}
+ },
+ jrnl:{
+ xClose: todoIOMethodsDb.xClose,
+ xRead: function(pFile,pTgt,n,iOff64){},
+ xWrite: function(pFile,pSrc,n,iOff64){},
+ xTruncate: function(pFile,i64){},
+ xSync: function(pFile,flags){},
+ xFileControl: function(pFile, opId, pArg){},
+ xFileSize: function(pFile,pi64Out){},
+ xLock: todoIOMethodsDb.xLock,
+ xUnlock: todoIOMethodsDb.xUnlock,
+ xCheckReservedLock: todoIOMethodsDb.xCheckReservedLock,
+ xFileControl: function(pFile,iOp,pArg){},
+ xSectorSize: todoIOMethodsDb.xSectorSize,
+ xDeviceCharacteristics: todoIOMethodsDb.xDeviceCharacteristics
+ }
+ }
+ }/*eventualTodo*/;
+
+ if(sqlite3?.oo1?.DB){
+ /**
+ Functionally equivalent to DB(storageName,'c','kvvfs') except
+ that it throws if the given storage name is not one of 'local'
+ or 'session'.
+
+ As of version 3.46, the argument may optionally be an options
+ object in the form:
+
+ {
+ filename: 'session'|'local',
+ ... etc. (all options supported by the DB ctor)
+ }
+
+ noting that the 'vfs' option supported by main DB
+ constructor is ignored here: the vfs is always 'kvvfs'.
+ */
+ sqlite3.oo1.JsStorageDb = function(
+ storageName = sqlite3.oo1.JsStorageDb.defaultStorageName
+ ){
+ const opt = sqlite3.oo1.DB.dbCtorHelper.normalizeArgs(...arguments);
+ storageName = opt.filename;
+ if('session'!==storageName && 'local'!==storageName){
+ toss3("JsStorageDb db name must be one of 'session' or 'local'.");
+ }
+ opt.vfs = 'kvvfs';
+ sqlite3.oo1.DB.dbCtorHelper.call(this, opt);
+ };
+ sqlite3.oo1.JsStorageDb.defaultStorageName = 'session';
+ const jdb = sqlite3.oo1.JsStorageDb;
+ jdb.prototype = Object.create(sqlite3.oo1.DB.prototype);
+ /** Equivalent to sqlite3_js_kvvfs_clear(). */
+ jdb.clearStorage = capi.sqlite3_js_kvvfs_clear;
+ /**
+ Clears this database instance's storage or throws if this
+ instance has been closed. Returns the number of
+ database blocks which were cleaned up.
+ */
+ jdb.prototype.clearStorage = function(){
+ return jdb.clearStorage(this.affirmDbOpen().filename);
+ };
+ /** Equivalent to sqlite3_js_kvvfs_size(). */
+ jdb.storageSize = capi.sqlite3_js_kvvfs_size;
+ /**
+ Returns the _approximate_ number of bytes this database takes
+ up in its storage or throws if this instance has been closed.
+ */
+ jdb.prototype.storageSize = function(){
+ return jdb.storageSize(this.affirmDbOpen().filename);
+ };
+ }/*sqlite3.oo1.JsStorageDb*/
+
+})/*globalThis.sqlite3ApiBootstrap.initializers*/;
+//#endif not omit-kvvfs