18c3b7501Sstephan/* 28c3b7501Sstephan 2022-05-20 38c3b7501Sstephan 48c3b7501Sstephan The author disclaims copyright to this source code. In place of a 58c3b7501Sstephan legal notice, here is a blessing: 68c3b7501Sstephan 78c3b7501Sstephan * May you do good and not evil. 88c3b7501Sstephan * May you find forgiveness for yourself and forgive others. 98c3b7501Sstephan * May you share freely, never taking more than you give. 108c3b7501Sstephan 118c3b7501Sstephan *********************************************************************** 128c3b7501Sstephan 138c3b7501Sstephan This is the JS Worker file for the sqlite3 fiddle app. It loads the 148c3b7501Sstephan sqlite3 wasm module and offers access to the db via the Worker 158c3b7501Sstephan message-passing interface. 168c3b7501Sstephan 178c3b7501Sstephan Forewarning: this API is still very much Under Construction and 188c3b7501Sstephan subject to any number of changes as experience reveals what those 198c3b7501Sstephan need to be. 208c3b7501Sstephan 218c3b7501Sstephan Because we can have only a single message handler, as opposed to an 228c3b7501Sstephan arbitrary number of discrete event listeners like with DOM elements, 238c3b7501Sstephan we have to define a lower-level message API. Messages abstractly 248c3b7501Sstephan look like: 258c3b7501Sstephan 268c3b7501Sstephan { type: string, data: type-specific value } 278c3b7501Sstephan 288c3b7501Sstephan Where 'type' is used for dispatching and 'data' is a 298c3b7501Sstephan 'type'-dependent value. 308c3b7501Sstephan 318c3b7501Sstephan The 'type' values expected by each side of the main/worker 328c3b7501Sstephan connection vary. The types are described below but subject to 338c3b7501Sstephan change at any time as this experiment evolves. 348c3b7501Sstephan 358c3b7501Sstephan Workers-to-Main types 368c3b7501Sstephan 378c3b7501Sstephan - stdout, stderr: indicate stdout/stderr output from the wasm 388c3b7501Sstephan layer. The data property is the string of the output, noting 398c3b7501Sstephan that the emscripten binding emits these one line at a time. Thus, 408c3b7501Sstephan if a C-side puts() emits multiple lines in a single call, the JS 418c3b7501Sstephan side will see that as multiple calls. Example: 428c3b7501Sstephan 438c3b7501Sstephan {type:'stdout', data: 'Hi, world.'} 448c3b7501Sstephan 458c3b7501Sstephan - module: Status text. This is intended to alert the main thread 468c3b7501Sstephan about module loading status so that, e.g., the main thread can 478c3b7501Sstephan update a progress widget and DTRT when the module is finished 488c3b7501Sstephan loading and available for work. Status messages come in the form 498c3b7501Sstephan 508c3b7501Sstephan {type:'module', data:{ 518c3b7501Sstephan type:'status', 528c3b7501Sstephan data: {text:string|null, step:1-based-integer} 538c3b7501Sstephan } 548c3b7501Sstephan 558c3b7501Sstephan with an incrementing step value for each subsequent message. When 568c3b7501Sstephan the module loading is complete, a message with a text value of 578c3b7501Sstephan null is posted. 588c3b7501Sstephan 598c3b7501Sstephan - working: data='start'|'end'. Indicates that work is about to be 608c3b7501Sstephan sent to the module or has just completed. This can be used, e.g., 618c3b7501Sstephan to disable UI elements which should not be activated while work 628c3b7501Sstephan is pending. Example: 638c3b7501Sstephan 648c3b7501Sstephan {type:'working', data:'start'} 658c3b7501Sstephan 668c3b7501Sstephan Main-to-Worker types: 678c3b7501Sstephan 688c3b7501Sstephan - shellExec: data=text to execute as if it had been entered in the 698c3b7501Sstephan sqlite3 CLI shell app (as opposed to sqlite3_exec()). This event 708c3b7501Sstephan causes the worker to emit a 'working' event (data='start') before 718c3b7501Sstephan it starts and a 'working' event (data='end') when it finished. If 728c3b7501Sstephan called while work is currently being executed it emits stderr 738c3b7501Sstephan message instead of doing actual work, as the underlying db cannot 748c3b7501Sstephan handle concurrent tasks. Example: 758c3b7501Sstephan 768c3b7501Sstephan {type:'shellExec', data: 'select * from sqlite_master'} 778c3b7501Sstephan 788c3b7501Sstephan - More TBD as the higher-level db layer develops. 798c3b7501Sstephan*/ 808c3b7501Sstephan 818c3b7501Sstephan/* 828c3b7501Sstephan Apparent browser(s) bug: console messages emitted may be duplicated 838c3b7501Sstephan in the console, even though they're provably only run once. See: 848c3b7501Sstephan 858c3b7501Sstephan https://stackoverflow.com/questions/49659464 868c3b7501Sstephan 878c3b7501Sstephan Noting that it happens in Firefox as well as Chrome. Harmless but 888c3b7501Sstephan annoying. 898c3b7501Sstephan*/ 908c3b7501Sstephan"use strict"; 918c3b7501Sstephan(function(){ 928c3b7501Sstephan /** 93395012e5Sstephan Posts a message in the form {type,data}. If passed more than 2 94395012e5Sstephan args, the 3rd must be an array of "transferable" values to pass 95395012e5Sstephan as the 2nd argument to postMessage(). */ 96395012e5Sstephan const wMsg = 97395012e5Sstephan (type,data,transferables)=>{ 98395012e5Sstephan postMessage({type, data}, transferables || []); 998c3b7501Sstephan }; 1006110a5d0Sstephan const stdout = (...args)=>wMsg('stdout', args); 1016110a5d0Sstephan const stderr = (...args)=>wMsg('stderr', args); 1021f095d48Sstephan const toss = (...args)=>{ 1031f095d48Sstephan throw new Error(args.join(' ')); 1041f095d48Sstephan }; 10560d9aa7cSstephan const fixmeOPFS = "(FIXME: won't work with vanilla OPFS.)"; 106278d3fafSstephan let sqlite3 /* gets assigned when the wasm module is loaded */; 10760d9aa7cSstephan 1088c3b7501Sstephan self.onerror = function(/*message, source, lineno, colno, error*/) { 1098c3b7501Sstephan const err = arguments[4]; 1108c3b7501Sstephan if(err && 'ExitStatus'==err.name){ 1118c3b7501Sstephan /* This is relevant for the sqlite3 shell binding but not the 1128c3b7501Sstephan lower-level binding. */ 1138c3b7501Sstephan fiddleModule.isDead = true; 1148c3b7501Sstephan stderr("FATAL ERROR:", err.message); 1158c3b7501Sstephan stderr("Restarting the app requires reloading the page."); 1168c3b7501Sstephan wMsg('error', err); 1178c3b7501Sstephan } 1188c3b7501Sstephan console.error(err); 1198c3b7501Sstephan fiddleModule.setStatus('Exception thrown, see JavaScript console: '+err); 1208c3b7501Sstephan }; 1218c3b7501Sstephan 1228c3b7501Sstephan const Sqlite3Shell = { 1238c3b7501Sstephan /** Returns the name of the currently-opened db. */ 1248c3b7501Sstephan dbFilename: function f(){ 125278d3fafSstephan if(!f._) f._ = sqlite3.capi.wasm.xWrap('fiddle_db_filename', "string", ['string']); 126278d3fafSstephan return f._(0); 127278d3fafSstephan }, 128278d3fafSstephan dbHandle: function f(){ 129278d3fafSstephan if(!f._) f._ = sqlite3.capi.wasm.xWrap("fiddle_db_handle", "sqlite3*"); 1308c3b7501Sstephan return f._(); 1318c3b7501Sstephan }, 132278d3fafSstephan dbIsOpfs: function f(){ 133278d3fafSstephan return sqlite3.opfs && sqlite3.capi.sqlite3_web_db_uses_vfs( 134278d3fafSstephan this.dbHandle(), "opfs" 135278d3fafSstephan ); 136278d3fafSstephan }, 13760d9aa7cSstephan runMain: function f(){ 138ef11fb91Sstephan if(f.argv) return 0===f.argv.rc; 13960d9aa7cSstephan const dbName = "/fiddle.sqlite3"; 14060d9aa7cSstephan f.argv = [ 14160d9aa7cSstephan 'sqlite3-fiddle.wasm', 14260d9aa7cSstephan '-bail', '-safe', 14360d9aa7cSstephan dbName 14460d9aa7cSstephan /* Reminder: because of how we run fiddle, we have to ensure 14560d9aa7cSstephan that any argv strings passed to its main() are valid until 14660d9aa7cSstephan the wasm environment shuts down. */ 14760d9aa7cSstephan ]; 148ef11fb91Sstephan const S = fiddleModule.sqlite3; 149ef11fb91Sstephan /* We need to call sqlite3_shutdown() in order to avoid numerous 150ef11fb91Sstephan legitimate warnings from the shell about it being initialized 1511f095d48Sstephan after sqlite3_initialize() has been called. This means, 152ef11fb91Sstephan however, that any initialization done by the JS code may need 153ef11fb91Sstephan to be re-done (e.g. re-registration of dynamically-loaded 154278d3fafSstephan VFSes). We need a more generic approach to running such 155278d3fafSstephan init-level code. */ 156ef11fb91Sstephan S.capi.sqlite3_shutdown(); 157ef11fb91Sstephan f.argv.pArgv = S.capi.wasm.allocMainArgv(f.argv); 158ef11fb91Sstephan f.argv.rc = S.capi.wasm.exports.fiddle_main( 15960d9aa7cSstephan f.argv.length, f.argv.pArgv 16060d9aa7cSstephan ); 16160d9aa7cSstephan if(f.argv.rc){ 16260d9aa7cSstephan stderr("Fatal error initializing sqlite3 shell."); 16360d9aa7cSstephan fiddleModule.isDead = true; 16460d9aa7cSstephan return false; 16560d9aa7cSstephan } 166ef11fb91Sstephan stdout("SQLite version", S.capi.sqlite3_libversion(), 167ef11fb91Sstephan S.capi.sqlite3_sourceid().substr(0,19)); 168ef11fb91Sstephan stdout('Welcome to the "fiddle" shell.'); 169ef11fb91Sstephan if(S.opfs){ 170ef11fb91Sstephan stdout("\nOPFS is available. To open a persistent db, use:\n\n", 171ef11fb91Sstephan " .open file:name?vfs=opfs\n\nbut note that some", 1721f095d48Sstephan "features (e.g. upload) do not yet work with OPFS."); 173*3d645484Sstephan S.opfs.registerVfs(); 174ef11fb91Sstephan } 175ef11fb91Sstephan stdout('\nEnter ".help" for usage hints.'); 176ef11fb91Sstephan this.exec([ // initialization commands... 177ef11fb91Sstephan '.nullvalue NULL', 178ef11fb91Sstephan '.headers on' 179ef11fb91Sstephan ].join('\n')); 18060d9aa7cSstephan return true; 18160d9aa7cSstephan }, 1828c3b7501Sstephan /** 1838c3b7501Sstephan Runs the given text through the shell as if it had been typed 1848c3b7501Sstephan in by a user. Fires a working/start event before it starts and 1858c3b7501Sstephan working/end event when it finishes. 1868c3b7501Sstephan */ 1878c3b7501Sstephan exec: function f(sql){ 18860d9aa7cSstephan if(!f._){ 18960d9aa7cSstephan if(!this.runMain()) return; 19060d9aa7cSstephan f._ = fiddleModule.cwrap('fiddle_exec', null, ['string']); 19160d9aa7cSstephan } 1928c3b7501Sstephan if(fiddleModule.isDead){ 1938c3b7501Sstephan stderr("shell module has exit()ed. Cannot run SQL."); 1948c3b7501Sstephan return; 1958c3b7501Sstephan } 1968c3b7501Sstephan wMsg('working','start'); 1978c3b7501Sstephan try { 1988c3b7501Sstephan if(f._running){ 1998c3b7501Sstephan stderr('Cannot run multiple commands concurrently.'); 20060d9aa7cSstephan }else if(sql){ 2018c3b7501Sstephan f._running = true; 2028c3b7501Sstephan f._(sql); 2038c3b7501Sstephan } 2048c3b7501Sstephan }finally{ 2058c3b7501Sstephan delete f._running; 2068c3b7501Sstephan wMsg('working','end'); 2078c3b7501Sstephan } 2088c3b7501Sstephan }, 2098c3b7501Sstephan resetDb: function f(){ 210278d3fafSstephan if(Sqlite3Shell.dbIsOpfs()){ 211278d3fafSstephan /* The problem is that fiddle_reset_db() uses the POSIX APIs 212278d3fafSstephan for file removal, which cannot see OPFS-hosted files. */ 213278d3fafSstephan stderr("TODO: cannot currently reset an OPFS-hosted db."); 214278d3fafSstephan return; 215278d3fafSstephan } 2168c3b7501Sstephan if(!f._) f._ = fiddleModule.cwrap('fiddle_reset_db', null); 21760d9aa7cSstephan stdout("Resetting database.",fixmeOPFS); 2188c3b7501Sstephan f._(); 2198c3b7501Sstephan stdout("Reset",this.dbFilename()); 2208c3b7501Sstephan }, 2218c3b7501Sstephan /* Interrupt can't work: this Worker is tied up working, so won't get the 2228c3b7501Sstephan interrupt event which would be needed to perform the interrupt. */ 2238c3b7501Sstephan interrupt: function f(){ 2248c3b7501Sstephan if(!f._) f._ = fiddleModule.cwrap('fiddle_interrupt', null); 2258c3b7501Sstephan stdout("Requesting interrupt."); 2268c3b7501Sstephan f._(); 2278c3b7501Sstephan } 2288c3b7501Sstephan }; 2298c3b7501Sstephan 2301f095d48Sstephan /** 2311f095d48Sstephan Exports the shell's current db file in such a way that it can 2321f095d48Sstephan export DBs hosted in the Emscripten-supplied FS or in native OPFS 2331f095d48Sstephan (and, hypothetically, kvvfs). Throws on error. On success returns 2341f095d48Sstephan a Blob containing the whole db contents. 2351f095d48Sstephan 236278d3fafSstephan Bug/to investigate: xFileSize() is returning garbage for the 237278d3fafSstephan default VFS but works in OPFS. Thus for exporting that impl we'll 238278d3fafSstephan use the fiddleModule.FS API for the time being. The equivalent 239278d3fafSstephan native impl, fiddle_export_db(), works okay with both VFSes, so 240278d3fafSstephan the bug is apparently in (or via) this code. 2411f095d48Sstephan */ 242278d3fafSstephan const brokenExportDbFileToBlob = function(){ 2431f095d48Sstephan const S = fiddleModule.sqlite3, capi = S.capi, wasm = capi.wasm; 244278d3fafSstephan const pDb = Sqlite3Shell.dbHandle(); 2451f095d48Sstephan if(!pDb) toss("No db is opened."); 2461f095d48Sstephan const scope = wasm.scopedAllocPush(); 2471f095d48Sstephan try{ 2481f095d48Sstephan const ppFile = wasm.scopedAlloc(12/*sizeof(i32 + i64)*/); 2491f095d48Sstephan const pFileSize = ppFile + 4; 250278d3fafSstephan wasm.setMemValue(ppFile, 0, '*'); 2511f095d48Sstephan let rc = capi.sqlite3_file_control( 2521f095d48Sstephan pDb, "main", capi.SQLITE_FCNTL_FILE_POINTER, ppFile 2531f095d48Sstephan ); 2541f095d48Sstephan if(rc) toss("Cannot get sqlite3_file handle."); 2551f095d48Sstephan const jFile = new capi.sqlite3_file(wasm.getPtrValue(ppFile)); 2561f095d48Sstephan const jIom = new capi.sqlite3_io_methods(jFile.$pMethods); 2571f095d48Sstephan const xFileSize = wasm.functionEntry(jIom.$xFileSize); 2581f095d48Sstephan const xRead = wasm.functionEntry(jIom.$xRead); 259278d3fafSstephan wasm.setMemValue(pFileSize, 0, 'i64'); 2601f095d48Sstephan //stderr("nFileSize =",wasm.getMemValue(pFileSize,'i64'),"pFileSize =",pFileSize); 2611f095d48Sstephan rc = xFileSize( jFile.pointer, pFileSize ); 2621f095d48Sstephan if(rc) toss("Cannot get db file size."); 2631f095d48Sstephan //stderr("nFileSize =",wasm.getMemValue(pFileSize,'i64'),"pFileSize =",pFileSize); 2641f095d48Sstephan const nFileSize = Number( wasm.getMemValue(pFileSize,'i64') ); 2651f095d48Sstephan if(nFileSize <= 0n || nFileSize>=Number.MAX_SAFE_INTEGER){ 2661f095d48Sstephan toss("Unexpected DB size:",nFileSize); 2671f095d48Sstephan } 2681f095d48Sstephan //stderr("nFileSize =",nFileSize,"pFileSize =",pFileSize); 2691f095d48Sstephan const nIobuf = 1024 * 4; 2701f095d48Sstephan const iobuf = wasm.scopedAlloc(nIobuf); 2711f095d48Sstephan let nPos = 0; 2721f095d48Sstephan const blobList = []; 2731f095d48Sstephan const heap = wasm.heap8u(); 2741f095d48Sstephan for( ; nPos < nFileSize; nPos += nIobuf ){ 2751f095d48Sstephan rc = xRead(jFile.pointer, iobuf, nIobuf, BigInt(nPos)); 2761f095d48Sstephan if(rc){ 2771f095d48Sstephan if(capi.SQLITE_IOERR_SHORT_READ === rc){ 2781f095d48Sstephan //stderr('rc =',rc,'nPos =',nPos,'nIobuf =',nIobuf,'nFileSize =',nFileSize); 2791f095d48Sstephan rc = ((nPos + nIobuf) < nFileSize) ? rc : 0/*assume EOF*/; 2801f095d48Sstephan } 2811f095d48Sstephan if(rc) toss("xRead() SQLITE_xxx error #"+rc,capi.sqlite3_wasm_rc_str(rc)); 2821f095d48Sstephan } 2831f095d48Sstephan blobList.push(heap.slice(iobuf, iobuf+nIobuf)); 2841f095d48Sstephan } 2851f095d48Sstephan return new Blob(blobList); 2861f095d48Sstephan }catch(e){ 2871f095d48Sstephan console.error('exportDbFileToBlob()',e); 2881f095d48Sstephan stderr("exportDbFileToBlob():",e.message); 2891f095d48Sstephan }finally{ 2901f095d48Sstephan wasm.scopedAllocPop(scope); 2911f095d48Sstephan } 2921f095d48Sstephan }/*exportDbFileToBlob()*/; 2931f095d48Sstephan 294278d3fafSstephan const exportDbFileToBlob = function f(){ 295278d3fafSstephan if(!f._){ 296278d3fafSstephan f._ = sqlite3.capi.wasm.xWrap('fiddle_export_db', 'int', '*'); 297278d3fafSstephan } 298278d3fafSstephan const capi = sqlite3.capi; 299278d3fafSstephan const wasm = capi.wasm; 300278d3fafSstephan const blobList = []; 301278d3fafSstephan const heap = wasm.heap8u(); 302278d3fafSstephan const callback = wasm.installFunction('ipi', function(buf, n){ 303278d3fafSstephan blobList.push(heap.slice(buf, buf+n)); 304278d3fafSstephan return 0; 305278d3fafSstephan }); 306278d3fafSstephan try { 307278d3fafSstephan const rc = wasm.exports.fiddle_export_db( callback ); 308278d3fafSstephan if(rc) toss("DB export failed with code", capi.sqlite3_wasm_rc_str(rc)); 309278d3fafSstephan return new Blob(blobList); 310278d3fafSstephan }catch(e){ 311278d3fafSstephan console.error("exportDbFileToBlob():",e.message); 312278d3fafSstephan throw(e); 313278d3fafSstephan }finally{ 314278d3fafSstephan wasm.uninstallFunction(callback); 315278d3fafSstephan } 316278d3fafSstephan }/*exportDbFileToBlob()*/; 317278d3fafSstephan 3188c3b7501Sstephan self.onmessage = function f(ev){ 3198c3b7501Sstephan ev = ev.data; 3208c3b7501Sstephan if(!f.cache){ 3218c3b7501Sstephan f.cache = { 3228c3b7501Sstephan prevFilename: null 3238c3b7501Sstephan }; 3248c3b7501Sstephan } 3258c3b7501Sstephan //console.debug("worker: onmessage.data",ev); 3268c3b7501Sstephan switch(ev.type){ 3278c3b7501Sstephan case 'shellExec': Sqlite3Shell.exec(ev.data); return; 3288c3b7501Sstephan case 'db-reset': Sqlite3Shell.resetDb(); return; 3298c3b7501Sstephan case 'interrupt': Sqlite3Shell.interrupt(); return; 3308c3b7501Sstephan /** Triggers the export of the current db. Fires an 3318c3b7501Sstephan event in the form: 3328c3b7501Sstephan 3338c3b7501Sstephan {type:'db-export', 3348c3b7501Sstephan data:{ 3358c3b7501Sstephan filename: name of db, 3368c3b7501Sstephan buffer: contents of the db file (Uint8Array), 3378c3b7501Sstephan error: on error, a message string and no buffer property. 3388c3b7501Sstephan } 3398c3b7501Sstephan } 3408c3b7501Sstephan */ 3418c3b7501Sstephan case 'db-export': { 3428c3b7501Sstephan const fn = Sqlite3Shell.dbFilename(); 3431f095d48Sstephan stdout("Exporting",fn+"."); 3448c3b7501Sstephan const fn2 = fn ? fn.split(/[/\\]/).pop() : null; 3458c3b7501Sstephan try{ 3468c3b7501Sstephan if(!fn2) throw new Error("DB appears to be closed."); 3471f095d48Sstephan exportDbFileToBlob().arrayBuffer().then((buffer)=>{ 3481f095d48Sstephan wMsg('db-export',{filename: fn2, buffer}, [buffer]); 3491f095d48Sstephan }); 3508c3b7501Sstephan }catch(e){ 3518c3b7501Sstephan /* Post a failure message so that UI elements disabled 3528c3b7501Sstephan during the export can be re-enabled. */ 3538c3b7501Sstephan wMsg('db-export',{ 3548c3b7501Sstephan filename: fn, 3558c3b7501Sstephan error: e.message 3568c3b7501Sstephan }); 3578c3b7501Sstephan } 3588c3b7501Sstephan return; 3598c3b7501Sstephan } 3608c3b7501Sstephan case 'open': { 3618c3b7501Sstephan /* Expects: { 3628c3b7501Sstephan buffer: ArrayBuffer | Uint8Array, 363395012e5Sstephan filename: the filename for the db. Any dir part is 364395012e5Sstephan stripped. 365395012e5Sstephan } 366395012e5Sstephan */ 3678c3b7501Sstephan const opt = ev.data; 3688c3b7501Sstephan let buffer = opt.buffer; 369ef11fb91Sstephan stderr('open():',fixmeOPFS); 370395012e5Sstephan if(buffer instanceof ArrayBuffer){ 3718c3b7501Sstephan buffer = new Uint8Array(buffer); 372395012e5Sstephan }else if(!(buffer instanceof Uint8Array)){ 3738c3b7501Sstephan stderr("'open' expects {buffer:Uint8Array} containing an uploaded db."); 3748c3b7501Sstephan return; 3758c3b7501Sstephan } 3768c3b7501Sstephan const fn = ( 3778c3b7501Sstephan opt.filename 3788c3b7501Sstephan ? opt.filename.split(/[/\\]/).pop().replace('"','_') 3798c3b7501Sstephan : ("db-"+((Math.random() * 10000000) | 0)+ 3808c3b7501Sstephan "-"+((Math.random() * 10000000) | 0)+".sqlite3") 3818c3b7501Sstephan ); 382395012e5Sstephan try { 3838c3b7501Sstephan /* We cannot delete the existing db file until the new one 3848c3b7501Sstephan is installed, which means that we risk overflowing our 3858c3b7501Sstephan quota (if any) by having both the previous and current 3868c3b7501Sstephan db briefly installed in the virtual filesystem. */ 387395012e5Sstephan const fnAbs = '/'+fn; 3888c3b7501Sstephan const oldName = Sqlite3Shell.dbFilename(); 389395012e5Sstephan if(oldName && oldName===fnAbs){ 390395012e5Sstephan /* We cannot create the replacement file while the current file 391395012e5Sstephan is opened, nor does the shell have a .close command, so we 392395012e5Sstephan must temporarily switch to another db... */ 393395012e5Sstephan Sqlite3Shell.exec('.open :memory:'); 394395012e5Sstephan fiddleModule.FS.unlink(fnAbs); 395395012e5Sstephan } 396395012e5Sstephan fiddleModule.FS.createDataFile("/", fn, buffer, true, true); 397395012e5Sstephan Sqlite3Shell.exec('.open "'+fnAbs+'"'); 398395012e5Sstephan if(oldName && oldName!==fnAbs){ 399395012e5Sstephan try{fiddleModule.fsUnlink(oldName)} 4008c3b7501Sstephan catch(e){/*ignored*/} 4018c3b7501Sstephan } 4028c3b7501Sstephan stdout("Replaced DB with",fn+"."); 403395012e5Sstephan }catch(e){ 404395012e5Sstephan stderr("Error installing db",fn+":",e.message); 405395012e5Sstephan } 4068c3b7501Sstephan return; 4078c3b7501Sstephan } 4088c3b7501Sstephan }; 4098c3b7501Sstephan console.warn("Unknown fiddle-worker message type:",ev); 4108c3b7501Sstephan }; 4118c3b7501Sstephan 4128c3b7501Sstephan /** 4138c3b7501Sstephan emscripten module for use with build mode -sMODULARIZE. 4148c3b7501Sstephan */ 4158c3b7501Sstephan const fiddleModule = { 4168c3b7501Sstephan print: stdout, 4178c3b7501Sstephan printErr: stderr, 4188c3b7501Sstephan /** 4198c3b7501Sstephan Intercepts status updates from the emscripting module init 4208c3b7501Sstephan and fires worker events with a type of 'status' and a 4218c3b7501Sstephan payload of: 4228c3b7501Sstephan 4238c3b7501Sstephan { 4248c3b7501Sstephan text: string | null, // null at end of load process 4258c3b7501Sstephan step: integer // starts at 1, increments 1 per call 4268c3b7501Sstephan } 4278c3b7501Sstephan 4288c3b7501Sstephan We have no way of knowing in advance how many steps will 4298c3b7501Sstephan be processed/posted, so creating a "percentage done" view is 4308c3b7501Sstephan not really practical. One can be approximated by giving it a 4318c3b7501Sstephan current value of message.step and max value of message.step+1, 4328c3b7501Sstephan though. 4338c3b7501Sstephan 4348c3b7501Sstephan When work is finished, a message with a text value of null is 4358c3b7501Sstephan submitted. 4368c3b7501Sstephan 4378c3b7501Sstephan After a message with text==null is posted, the module may later 4388c3b7501Sstephan post messages about fatal problems, e.g. an exit() being 4398c3b7501Sstephan triggered, so it is recommended that UI elements for posting 4408c3b7501Sstephan status messages not be outright removed from the DOM when 4418c3b7501Sstephan text==null, and that they instead be hidden until/unless 4428c3b7501Sstephan text!=null. 4438c3b7501Sstephan */ 4448c3b7501Sstephan setStatus: function f(text){ 4458c3b7501Sstephan if(!f.last) f.last = { step: 0, text: '' }; 4468c3b7501Sstephan else if(text === f.last.text) return; 4478c3b7501Sstephan f.last.text = text; 4488c3b7501Sstephan wMsg('module',{ 4498c3b7501Sstephan type:'status', 4508c3b7501Sstephan data:{step: ++f.last.step, text: text||null} 4518c3b7501Sstephan }); 4528c3b7501Sstephan } 4538c3b7501Sstephan }; 4548c3b7501Sstephan 4556110a5d0Sstephan importScripts('fiddle-module.js'+self.location.search); 4568c3b7501Sstephan /** 4578c3b7501Sstephan initFiddleModule() is installed via fiddle-module.js due to 4588c3b7501Sstephan building with: 4598c3b7501Sstephan 4608c3b7501Sstephan emcc ... -sMODULARIZE=1 -sEXPORT_NAME=initFiddleModule 4618c3b7501Sstephan */ 4628c3b7501Sstephan initFiddleModule(fiddleModule).then(function(thisModule){ 463278d3fafSstephan sqlite3 = thisModule.sqlite3; 4646110a5d0Sstephan const atEnd = ()=>{ 465ef11fb91Sstephan thisModule.fsUnlink = (fn)=>{ 46660d9aa7cSstephan stderr("unlink:",fixmeOPFS); 467278d3fafSstephan return sqlite3.capi.wasm.sqlite3_wasm_vfs_unlink(fn); 46860d9aa7cSstephan }; 4698c3b7501Sstephan wMsg('fiddle-ready'); 4706110a5d0Sstephan }; 471278d3fafSstephan if(sqlite3.installOpfsVfs) sqlite3.installOpfsVfs().finally(atEnd); 472ef11fb91Sstephan else atEnd(); 47360d9aa7cSstephan })/*then()*/; 4748c3b7501Sstephan})(); 475