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