1/*
2  2022-09-18
3
4  The author disclaims copyright to this source code.  In place of a
5  legal notice, here is a blessing:
6
7  *   May you do good and not evil.
8  *   May you find forgiveness for yourself and forgive others.
9  *   May you share freely, never taking more than you give.
10
11  ***********************************************************************
12
13  This file holds the synchronous half of an sqlite3_vfs
14  implementation which proxies, in a synchronous fashion, the
15  asynchronous Origin-Private FileSystem (OPFS) APIs using a second
16  Worker, implemented in sqlite3-opfs-async-proxy.js.  This file is
17  intended to be appended to the main sqlite3 JS deliverable somewhere
18  after sqlite3-api-glue.js and before sqlite3-api-cleanup.js.
19
20*/
21
22'use strict';
23self.sqlite3ApiBootstrap.initializers.push(function(sqlite3){
24/**
25   sqlite3.installOpfsVfs() returns a Promise which, on success, installs
26   an sqlite3_vfs named "opfs", suitable for use with all sqlite3 APIs
27   which accept a VFS. It uses the Origin-Private FileSystem API for
28   all file storage. On error it is rejected with an exception
29   explaining the problem. Reasons for rejection include, but are
30   not limited to:
31
32   - The counterpart Worker (see below) could not be loaded.
33
34   - The environment does not support OPFS. That includes when
35     this function is called from the main window thread.
36
37
38  Significant notes and limitations:
39
40  - As of this writing, OPFS is still very much in flux and only
41    available in bleeding-edge versions of Chrome (v102+, noting that
42    that number will increase as the OPFS API matures).
43
44  - The OPFS features used here are only available in dedicated Worker
45    threads. This file tries to detect that case, resulting in a
46    rejected Promise if those features do not seem to be available.
47
48  - It requires the SharedArrayBuffer and Atomics classes, and the
49    former is only available if the HTTP server emits the so-called
50    COOP and COEP response headers. These features are required for
51    proxying OPFS's synchronous API via the synchronous interface
52    required by the sqlite3_vfs API.
53
54  - This function may only be called a single time and it must be
55    called from the client, as opposed to the library initialization,
56    in case the client requires a custom path for this API's
57    "counterpart": this function's argument is the relative URI to
58    this module's "asynchronous half". When called, this function removes
59    itself from the sqlite3 object.
60
61   The argument may optionally be a plain object with the following
62   configuration options:
63
64   - proxyUri: as described above
65
66   - verbose (=2): an integer 0-3. 0 disables all logging, 1 enables
67     logging of errors. 2 enables logging of warnings and errors. 3
68     additionally enables debugging info.
69
70   - sanityChecks (=false): if true, some basic sanity tests are
71     run on the OPFS VFS API after it's initialized, before the
72     returned Promise resolves.
73
74   On success, the Promise resolves to the top-most sqlite3 namespace
75   object and that object gets a new object installed in its
76   `opfs` property, containing several OPFS-specific utilities.
77*/
78sqlite3.installOpfsVfs = function callee(asyncProxyUri = callee.defaultProxyUri){
79  delete sqlite3.installOpfsVfs;
80  if(self.window===self ||
81     !self.SharedArrayBuffer ||
82     !self.FileSystemHandle ||
83     !self.FileSystemDirectoryHandle ||
84     !self.FileSystemFileHandle ||
85     !self.FileSystemFileHandle.prototype.createSyncAccessHandle ||
86     !navigator.storage.getDirectory){
87    return Promise.reject(
88      new Error("This environment does not have OPFS support.")
89    );
90  }
91  const options = (asyncProxyUri && 'object'===asyncProxyUri) ? asyncProxyUri : {
92    proxyUri: asyncProxyUri
93  };
94  const urlParams = new URL(self.location.href).searchParams;
95  if(undefined===options.verbose){
96    options.verbose = urlParams.has('opfs-verbose') ? 3 : 2;
97  }
98  if(undefined===options.sanityChecks){
99    options.sanityChecks = urlParams.has('opfs-sanity-check');
100  }
101  if(undefined===options.proxyUri){
102    options.proxyUri = callee.defaultProxyUri;
103  }
104
105  const thePromise = new Promise(function(promiseResolve, promiseReject){
106    const loggers = {
107      0:console.error.bind(console),
108      1:console.warn.bind(console),
109      2:console.log.bind(console)
110    };
111    const logImpl = (level,...args)=>{
112      if(options.verbose>level) loggers[level]("OPFS syncer:",...args);
113    };
114    const log =    (...args)=>logImpl(2, ...args);
115    const warn =   (...args)=>logImpl(1, ...args);
116    const error =  (...args)=>logImpl(0, ...args);
117    warn("The OPFS VFS feature is very much experimental and under construction.");
118    const toss = function(...args){throw new Error(args.join(' '))};
119    const capi = sqlite3.capi;
120    const wasm = capi.wasm;
121    const sqlite3_vfs = capi.sqlite3_vfs;
122    const sqlite3_file = capi.sqlite3_file;
123    const sqlite3_io_methods = capi.sqlite3_io_methods;
124    const W = new Worker(options.proxyUri);
125    W._originalOnError = W.onerror /* will be restored later */;
126    W.onerror = function(err){
127      // The error object doesn't contain any useful info when the
128      // failure is, e.g., that the remote script is 404.
129      promiseReject(new Error("Loading OPFS async Worker failed for unknown reasons."));
130    };
131    const wMsg = (type,payload)=>W.postMessage({type,payload});
132    /**
133       Generic utilities for working with OPFS. This will get filled out
134       by the Promise setup and, on success, installed as sqlite3.opfs.
135    */
136    const opfsUtil = Object.create(null);
137    /**
138       Not part of the public API. Solely for internal/development
139       use.
140    */
141    opfsUtil.metrics = {
142      dump: function(){
143        let k, n = 0, t = 0;
144        for(k in metrics){
145          const m = metrics[k];
146          n += m.count;
147          t += m.time;
148          m.avgTime = (m.count && m.time) ? (m.time / m.count) : 0;
149          m.avgWait = (m.count && m.wait) ? (m.wait / m.count) : 0;
150        }
151        console.log("metrics for",self.location.href,":",metrics,
152                    "\nTotal of",n,"op(s) for",t,"ms");
153      },
154      reset: function(){
155        let k;
156        const r = (m)=>(m.count = m.time = m.wait = 0);
157        for(k in state.opIds){
158          r(metrics[k] = Object.create(null));
159        }
160        [ // timed routines which are not in state.opIds
161          'xFileControl'
162        ].forEach((k)=>r(metrics[k] = Object.create(null)));
163      }
164    }/*metrics*/;
165
166    /**
167       State which we send to the async-api Worker or share with it.
168       This object must initially contain only cloneable or sharable
169       objects. After the worker's "inited" message arrives, other types
170       of data may be added to it.
171
172       For purposes of Atomics.wait() and Atomics.notify(), we use a
173       SharedArrayBuffer with one slot reserved for each of the API
174       proxy's methods. The sync side of the API uses Atomics.wait()
175       on the corresponding slot and the async side uses
176       Atomics.notify() on that slot.
177
178       The approach of using a single SAB to serialize comms for all
179       instances might(?) lead to deadlock situations in multi-db
180       cases. We should probably have one SAB here with a single slot
181       for locking a per-file initialization step and then allocate a
182       separate SAB like the above one for each file. That will
183       require a bit of acrobatics but should be feasible.
184    */
185    const state = Object.create(null);
186    state.verbose = options.verbose;
187    state.fileBufferSize =
188      1024 * 64 + 8 /* size of aFileHandle.sab. 64k = max sqlite3 page
189                       size. The additional bytes are space for
190                       holding BigInt results, since we cannot store
191                       those via the Atomics API (which only works on
192                       an Int32Array). */;
193    state.fbInt64Offset =
194      state.fileBufferSize - 8 /*spot in fileHandle.sab to store an int64 result */;
195    state.opIds = Object.create(null);
196    const metrics = Object.create(null);
197    {
198      let i = 0;
199      state.opIds.xAccess = i++;
200      state.opIds.xClose = i++;
201      state.opIds.xDelete = i++;
202      state.opIds.xDeleteNoWait = i++;
203      state.opIds.xFileSize = i++;
204      state.opIds.xOpen = i++;
205      state.opIds.xRead = i++;
206      state.opIds.xSleep = i++;
207      state.opIds.xSync = i++;
208      state.opIds.xTruncate = i++;
209      state.opIds.xWrite = i++;
210      state.opIds.mkdir = i++;
211      state.opSAB = new SharedArrayBuffer(i * 4/*sizeof int32*/);
212      opfsUtil.metrics.reset();
213    }
214
215    state.sq3Codes = Object.create(null);
216    state.sq3Codes._reverse = Object.create(null);
217    [ // SQLITE_xxx constants to export to the async worker counterpart...
218      'SQLITE_ERROR', 'SQLITE_IOERR',
219      'SQLITE_NOTFOUND', 'SQLITE_MISUSE',
220      'SQLITE_IOERR_READ', 'SQLITE_IOERR_SHORT_READ',
221      'SQLITE_IOERR_WRITE', 'SQLITE_IOERR_FSYNC',
222      'SQLITE_IOERR_TRUNCATE', 'SQLITE_IOERR_DELETE',
223      'SQLITE_IOERR_ACCESS', 'SQLITE_IOERR_CLOSE',
224      'SQLITE_IOERR_DELETE'
225    ].forEach(function(k){
226      state.sq3Codes[k] = capi[k] || toss("Maintenance required: not found:",k);
227      state.sq3Codes._reverse[capi[k]] = k;
228    });
229
230    const isWorkerErrCode = (n)=>!!state.sq3Codes._reverse[n];
231
232    /**
233       Runs the given operation in the async worker counterpart, waits
234       for its response, and returns the result which the async worker
235       writes to the given op's index in state.opSABView. The 2nd argument
236       must be a single object or primitive value, depending on the
237       given operation's signature in the async API counterpart.
238    */
239    const opRun = (op,args)=>{
240      const t = performance.now();
241      Atomics.store(state.opSABView, state.opIds[op], -1);
242      wMsg(op, args);
243      Atomics.wait(state.opSABView, state.opIds[op], -1);
244      metrics[op].wait += performance.now() - t;
245      return Atomics.load(state.opSABView, state.opIds[op]);
246    };
247
248    /**
249       Generates a random ASCII string len characters long, intended for
250       use as a temporary file name.
251    */
252    const randomFilename = function f(len=16){
253      if(!f._chars){
254        f._chars = "abcdefghijklmnopqrstuvwxyz"+
255          "ABCDEFGHIJKLMNOPQRSTUVWXYZ"+
256          "012346789";
257        f._n = f._chars.length;
258      }
259      const a = [];
260      let i = 0;
261      for( ; i < len; ++i){
262        const ndx = Math.random() * (f._n * 64) % f._n | 0;
263        a[i] = f._chars[ndx];
264      }
265      return a.join('');
266    };
267
268    /**
269       Map of sqlite3_file pointers to objects constructed by xOpen().
270    */
271    const __openFiles = Object.create(null);
272
273    const pDVfs = capi.sqlite3_vfs_find(null)/*pointer to default VFS*/;
274    const dVfs = pDVfs
275          ? new sqlite3_vfs(pDVfs)
276          : null /* dVfs will be null when sqlite3 is built with
277                    SQLITE_OS_OTHER. Though we cannot currently handle
278                    that case, the hope is to eventually be able to. */;
279    const opfsVfs = new sqlite3_vfs();
280    const opfsIoMethods = new sqlite3_io_methods();
281    opfsVfs.$iVersion = 2/*yes, two*/;
282    opfsVfs.$szOsFile = capi.sqlite3_file.structInfo.sizeof;
283    opfsVfs.$mxPathname = 1024/*sure, why not?*/;
284    opfsVfs.$zName = wasm.allocCString("opfs");
285    // All C-side memory of opfsVfs is zeroed out, but just to be explicit:
286    opfsVfs.$xDlOpen = opfsVfs.$xDlError = opfsVfs.$xDlSym = opfsVfs.$xDlClose = null;
287    opfsVfs.ondispose = [
288      '$zName', opfsVfs.$zName,
289      'cleanup default VFS wrapper', ()=>(dVfs ? dVfs.dispose() : null),
290      'cleanup opfsIoMethods', ()=>opfsIoMethods.dispose()
291    ];
292    /**
293       Pedantic sidebar about opfsVfs.ondispose: the entries in that array
294       are items to clean up when opfsVfs.dispose() is called, but in this
295       environment it will never be called. The VFS instance simply
296       hangs around until the WASM module instance is cleaned up. We
297       "could" _hypothetically_ clean it up by "importing" an
298       sqlite3_os_end() impl into the wasm build, but the shutdown order
299       of the wasm engine and the JS one are undefined so there is no
300       guaranty that the opfsVfs instance would be available in one
301       environment or the other when sqlite3_os_end() is called (_if_ it
302       gets called at all in a wasm build, which is undefined).
303    */
304
305    /**
306       Installs a StructBinder-bound function pointer member of the
307       given name and function in the given StructType target object.
308       It creates a WASM proxy for the given function and arranges for
309       that proxy to be cleaned up when tgt.dispose() is called.  Throws
310       on the slightest hint of error (e.g. tgt is-not-a StructType,
311       name does not map to a struct-bound member, etc.).
312
313       Returns a proxy for this function which is bound to tgt and takes
314       2 args (name,func). That function returns the same thing,
315       permitting calls to be chained.
316
317       If called with only 1 arg, it has no side effects but returns a
318       func with the same signature as described above.
319    */
320    const installMethod = function callee(tgt, name, func){
321      if(!(tgt instanceof sqlite3.StructBinder.StructType)){
322        toss("Usage error: target object is-not-a StructType.");
323      }
324      if(1===arguments.length){
325        return (n,f)=>callee(tgt,n,f);
326      }
327      if(!callee.argcProxy){
328        callee.argcProxy = function(func,sig){
329          return function(...args){
330            if(func.length!==arguments.length){
331              toss("Argument mismatch. Native signature is:",sig);
332            }
333            return func.apply(this, args);
334          }
335        };
336        callee.removeFuncList = function(){
337          if(this.ondispose.__removeFuncList){
338            this.ondispose.__removeFuncList.forEach(
339              (v,ndx)=>{
340                if('number'===typeof v){
341                  try{wasm.uninstallFunction(v)}
342                  catch(e){/*ignore*/}
343                }
344                /* else it's a descriptive label for the next number in
345                   the list. */
346              }
347            );
348            delete this.ondispose.__removeFuncList;
349          }
350        };
351      }/*static init*/
352      const sigN = tgt.memberSignature(name);
353      if(sigN.length<2){
354        toss("Member",name," is not a function pointer. Signature =",sigN);
355      }
356      const memKey = tgt.memberKey(name);
357      //log("installMethod",tgt, name, sigN);
358      const fProxy = 1
359      // We can remove this proxy middle-man once the VFS is working
360            ? callee.argcProxy(func, sigN)
361            : func;
362      const pFunc = wasm.installFunction(fProxy, tgt.memberSignature(name, true));
363      tgt[memKey] = pFunc;
364      if(!tgt.ondispose) tgt.ondispose = [];
365      if(!tgt.ondispose.__removeFuncList){
366        tgt.ondispose.push('ondispose.__removeFuncList handler',
367                           callee.removeFuncList);
368        tgt.ondispose.__removeFuncList = [];
369      }
370      tgt.ondispose.__removeFuncList.push(memKey, pFunc);
371      return (n,f)=>callee(tgt, n, f);
372    }/*installMethod*/;
373
374    const opTimer = Object.create(null);
375    opTimer.op = undefined;
376    opTimer.start = undefined;
377    const mTimeStart = (op)=>{
378      opTimer.start = performance.now();
379      opTimer.op = op;
380      //metrics[op] || toss("Maintenance required: missing metrics for",op);
381      ++metrics[op].count;
382    };
383    const mTimeEnd = ()=>(
384      metrics[opTimer.op].time += performance.now() - opTimer.start
385    );
386
387    /**
388       Impls for the sqlite3_io_methods methods. Maintenance reminder:
389       members are in alphabetical order to simplify finding them.
390    */
391    const ioSyncWrappers = {
392      xCheckReservedLock: function(pFile,pOut){
393        // Exclusive lock is automatically acquired when opened
394        //warn("xCheckReservedLock(",arguments,") is a no-op");
395        wasm.setMemValue(pOut,1,'i32');
396        return 0;
397      },
398      xClose: function(pFile){
399        mTimeStart('xClose');
400        let rc = 0;
401        const f = __openFiles[pFile];
402        if(f){
403          delete __openFiles[pFile];
404          rc = opRun('xClose', pFile);
405          if(f.sq3File) f.sq3File.dispose();
406        }
407        mTimeEnd();
408        return rc;
409      },
410      xDeviceCharacteristics: function(pFile){
411        //debug("xDeviceCharacteristics(",pFile,")");
412        return capi.SQLITE_IOCAP_UNDELETABLE_WHEN_OPEN;
413      },
414      xFileControl: function(pFile, opId, pArg){
415        mTimeStart('xFileControl');
416        if(capi.SQLITE_FCNTL_SYNC===opId){
417          return opRun('xSync', {fid:pFile, flags:0});
418        }
419        mTimeEnd();
420        return capi.SQLITE_NOTFOUND;
421      },
422      xFileSize: function(pFile,pSz64){
423        mTimeStart('xFileSize');
424        const rc = opRun('xFileSize', pFile);
425        if(!isWorkerErrCode(rc)){
426          const f = __openFiles[pFile];
427          wasm.setMemValue(pSz64, f.sabViewFileSize.getBigInt64(0,true) ,'i64');
428        }
429        mTimeEnd();
430        return rc;
431      },
432      xLock: function(pFile,lockType){
433        //2022-09: OPFS handles lock when opened
434        //warn("xLock(",arguments,") is a no-op");
435        return 0;
436      },
437      xRead: function(pFile,pDest,n,offset){
438        /* int (*xRead)(sqlite3_file*, void*, int iAmt, sqlite3_int64 iOfst) */
439        mTimeStart('xRead');
440        const f = __openFiles[pFile];
441        let rc;
442        try {
443          // FIXME(?): block until we finish copying the xRead result buffer. How?
444          rc = opRun('xRead',{fid:pFile, n, offset});
445          if(0===rc || capi.SQLITE_IOERR_SHORT_READ===rc){
446            // set() seems to be the fastest way to copy this...
447            wasm.heap8u().set(f.sabView.subarray(0, n), pDest);
448          }
449        }catch(e){
450          error("xRead(",arguments,") failed:",e,f);
451          rc = capi.SQLITE_IOERR_READ;
452        }
453        mTimeEnd();
454        return rc;
455      },
456      xSync: function(pFile,flags){
457        return 0; // impl'd in xFileControl(). opRun('xSync', {fid:pFile, flags});
458      },
459      xTruncate: function(pFile,sz64){
460        mTimeStart('xTruncate');
461        const rc = opRun('xTruncate', {fid:pFile, size: sz64});
462        mTimeEnd();
463        return rc;
464      },
465      xUnlock: function(pFile,lockType){
466        //2022-09: OPFS handles lock when opened
467        //warn("xUnlock(",arguments,") is a no-op");
468        return 0;
469      },
470      xWrite: function(pFile,pSrc,n,offset){
471        /* int (*xWrite)(sqlite3_file*, const void*, int iAmt, sqlite3_int64 iOfst) */
472        mTimeStart('xWrite');
473        const f = __openFiles[pFile];
474        let rc;
475        try {
476          // FIXME(?): block from here until we finish the xWrite. How?
477          f.sabView.set(wasm.heap8u().subarray(pSrc, pSrc+n));
478          rc = opRun('xWrite',{fid:pFile, n, offset});
479        }catch(e){
480          error("xWrite(",arguments,") failed:",e,f);
481          rc = capi.SQLITE_IOERR_WRITE;
482        }
483        mTimeEnd();
484        return rc;
485      }
486    }/*ioSyncWrappers*/;
487
488    /**
489       Impls for the sqlite3_vfs methods. Maintenance reminder: members
490       are in alphabetical order to simplify finding them.
491    */
492    const vfsSyncWrappers = {
493      xAccess: function(pVfs,zName,flags,pOut){
494        mTimeStart('xAccess');
495        const rc = opRun('xAccess', wasm.cstringToJs(zName));
496        wasm.setMemValue(pOut, rc ? 0 : 1, 'i32');
497        mTimeEnd();
498        return 0;
499      },
500      xCurrentTime: function(pVfs,pOut){
501        /* If it turns out that we need to adjust for timezone, see:
502           https://stackoverflow.com/a/11760121/1458521 */
503        wasm.setMemValue(pOut, 2440587.5 + (new Date().getTime()/86400000),
504                         'double');
505        return 0;
506      },
507      xCurrentTimeInt64: function(pVfs,pOut){
508        // TODO: confirm that this calculation is correct
509        wasm.setMemValue(pOut, (2440587.5 * 86400000) + new Date().getTime(),
510                         'i64');
511        return 0;
512      },
513      xDelete: function(pVfs, zName, doSyncDir){
514        mTimeStart('xDelete');
515        opRun('xDelete', {filename: wasm.cstringToJs(zName), syncDir: doSyncDir});
516        /* We're ignoring errors because we cannot yet differentiate
517           between harmless and non-harmless failures. */
518        mTimeEnd();
519        return 0;
520      },
521      xFullPathname: function(pVfs,zName,nOut,pOut){
522        /* Until/unless we have some notion of "current dir"
523           in OPFS, simply copy zName to pOut... */
524        const i = wasm.cstrncpy(pOut, zName, nOut);
525        return i<nOut ? 0 : capi.SQLITE_CANTOPEN
526        /*CANTOPEN is required by the docs but SQLITE_RANGE would be a closer match*/;
527      },
528      xGetLastError: function(pVfs,nOut,pOut){
529        /* TODO: store exception.message values from the async
530           partner in a dedicated SharedArrayBuffer, noting that we'd have
531           to encode them... TextEncoder can do that for us. */
532        warn("OPFS xGetLastError() has nothing sensible to return.");
533        return 0;
534      },
535      //xSleep is optionally defined below
536      xOpen: function f(pVfs, zName, pFile, flags, pOutFlags){
537        mTimeStart('xOpen');
538        if(!f._){
539          f._ = {
540            fileTypes: {
541              SQLITE_OPEN_MAIN_DB: 'mainDb',
542              SQLITE_OPEN_MAIN_JOURNAL: 'mainJournal',
543              SQLITE_OPEN_TEMP_DB: 'tempDb',
544              SQLITE_OPEN_TEMP_JOURNAL: 'tempJournal',
545              SQLITE_OPEN_TRANSIENT_DB: 'transientDb',
546              SQLITE_OPEN_SUBJOURNAL: 'subjournal',
547              SQLITE_OPEN_SUPER_JOURNAL: 'superJournal',
548              SQLITE_OPEN_WAL: 'wal'
549            },
550            getFileType: function(filename,oflags){
551              const ft = f._.fileTypes;
552              for(let k of Object.keys(ft)){
553                if(oflags & capi[k]) return ft[k];
554              }
555              warn("Cannot determine fileType based on xOpen() flags for file",filename);
556              return '???';
557            }
558          };
559        }
560        if(0===zName){
561          zName = randomFilename();
562        }else if('number'===typeof zName){
563          zName = wasm.cstringToJs(zName);
564        }
565        const args = Object.create(null);
566        args.fid = pFile;
567        args.filename = zName;
568        args.sab = new SharedArrayBuffer(state.fileBufferSize);
569        args.fileType = f._.getFileType(args.filename, flags);
570        args.create = !!(flags & capi.SQLITE_OPEN_CREATE);
571        args.deleteOnClose = !!(flags & capi.SQLITE_OPEN_DELETEONCLOSE);
572        args.readOnly = !!(flags & capi.SQLITE_OPEN_READONLY);
573        const rc = opRun('xOpen', args);
574        if(!rc){
575          /* Recall that sqlite3_vfs::xClose() will be called, even on
576             error, unless pFile->pMethods is NULL. */
577          if(args.readOnly){
578            wasm.setMemValue(pOutFlags, capi.SQLITE_OPEN_READONLY, 'i32');
579          }
580          __openFiles[pFile] = args;
581          args.sabView = new Uint8Array(args.sab);
582          args.sabViewFileSize = new DataView(args.sab, state.fbInt64Offset, 8);
583          args.sq3File = new sqlite3_file(pFile);
584          args.sq3File.$pMethods = opfsIoMethods.pointer;
585          args.ba = new Uint8Array(args.sab);
586        }
587        mTimeEnd();
588        return rc;
589      }/*xOpen()*/
590    }/*vfsSyncWrappers*/;
591
592    if(dVfs){
593      opfsVfs.$xRandomness = dVfs.$xRandomness;
594      opfsVfs.$xSleep = dVfs.$xSleep;
595    }
596    if(!opfsVfs.$xRandomness){
597      /* If the default VFS has no xRandomness(), add a basic JS impl... */
598      vfsSyncWrappers.xRandomness = function(pVfs, nOut, pOut){
599        const heap = wasm.heap8u();
600        let i = 0;
601        for(; i < nOut; ++i) heap[pOut + i] = (Math.random()*255000) & 0xFF;
602        return i;
603      };
604    }
605    if(!opfsVfs.$xSleep){
606      /* If we can inherit an xSleep() impl from the default VFS then
607         assume it's sane and use it, otherwise install a JS-based
608         one. */
609      vfsSyncWrappers.xSleep = function(pVfs,ms){
610        Atomics.wait(state.opSABView, state.opIds.xSleep, 0, ms);
611        return 0;
612      };
613    }
614
615    /* Install the vfs/io_methods into their C-level shared instances... */
616    let inst = installMethod(opfsIoMethods);
617    for(let k of Object.keys(ioSyncWrappers)) inst(k, ioSyncWrappers[k]);
618    inst = installMethod(opfsVfs);
619    for(let k of Object.keys(vfsSyncWrappers)) inst(k, vfsSyncWrappers[k]);
620
621    /**
622       Syncronously deletes the given OPFS filesystem entry, ignoring
623       any errors. As this environment has no notion of "current
624       directory", the given name must be an absolute path. If the 2nd
625       argument is truthy, deletion is recursive (use with caution!).
626
627       Returns true if the deletion succeeded and fails if it fails,
628       but cannot report the nature of the failure.
629    */
630    opfsUtil.deleteEntry = function(fsEntryName,recursive=false){
631      return 0===opRun('xDelete', {filename:fsEntryName, recursive});
632    };
633    /**
634       Exactly like deleteEntry() but runs asynchronously.
635    */
636    opfsUtil.deleteEntryAsync = async function(fsEntryName,recursive=false){
637      wMsg('xDeleteNoWait', {filename: fsEntryName, recursive});
638    };
639    /**
640       Synchronously creates the given directory name, recursively, in
641       the OPFS filesystem. Returns true if it succeeds or the
642       directory already exists, else false.
643    */
644    opfsUtil.mkdir = async function(absDirName){
645      return 0===opRun('mkdir', absDirName);
646    };
647    /**
648       Synchronously checks whether the given OPFS filesystem exists,
649       returning true if it does, false if it doesn't.
650    */
651    opfsUtil.entryExists = function(fsEntryName){
652      return 0===opRun('xAccess', fsEntryName);
653    };
654
655    /**
656       Generates a random ASCII string, intended for use as a
657       temporary file name. Its argument is the length of the string,
658       defaulting to 16.
659    */
660    opfsUtil.randomFilename = randomFilename;
661
662    if(sqlite3.oo1){
663      opfsUtil.OpfsDb = function(...args){
664        const opt = sqlite3.oo1.dbCtorHelper.normalizeArgs(...args);
665        opt.vfs = opfsVfs.$zName;
666        sqlite3.oo1.dbCtorHelper.call(this, opt);
667      };
668      opfsUtil.OpfsDb.prototype = Object.create(sqlite3.oo1.DB.prototype);
669    }
670
671    /**
672       Potential TODOs:
673
674       - Expose one or both of the Worker objects via opfsUtil and
675         publish an interface for proxying the higher-level OPFS
676         features like getting a directory listing.
677    */
678
679    const sanityCheck = async function(){
680      const scope = wasm.scopedAllocPush();
681      const sq3File = new sqlite3_file();
682      try{
683        const fid = sq3File.pointer;
684        const openFlags = capi.SQLITE_OPEN_CREATE
685              | capi.SQLITE_OPEN_READWRITE
686        //| capi.SQLITE_OPEN_DELETEONCLOSE
687              | capi.SQLITE_OPEN_MAIN_DB;
688        const pOut = wasm.scopedAlloc(8);
689        const dbFile = "/sanity/check/file";
690        const zDbFile = wasm.scopedAllocCString(dbFile);
691        let rc;
692        vfsSyncWrappers.xAccess(opfsVfs.pointer, zDbFile, 0, pOut);
693        rc = wasm.getMemValue(pOut,'i32');
694        log("xAccess(",dbFile,") exists ?=",rc);
695        rc = vfsSyncWrappers.xOpen(opfsVfs.pointer, zDbFile,
696                                   fid, openFlags, pOut);
697        log("open rc =",rc,"state.opSABView[xOpen] =",
698            state.opSABView[state.opIds.xOpen]);
699        if(isWorkerErrCode(rc)){
700          error("open failed with code",rc);
701          return;
702        }
703        vfsSyncWrappers.xAccess(opfsVfs.pointer, zDbFile, 0, pOut);
704        rc = wasm.getMemValue(pOut,'i32');
705        if(!rc) toss("xAccess() failed to detect file.");
706        rc = ioSyncWrappers.xSync(sq3File.pointer, 0);
707        if(rc) toss('sync failed w/ rc',rc);
708        rc = ioSyncWrappers.xTruncate(sq3File.pointer, 1024);
709        if(rc) toss('truncate failed w/ rc',rc);
710        wasm.setMemValue(pOut,0,'i64');
711        rc = ioSyncWrappers.xFileSize(sq3File.pointer, pOut);
712        if(rc) toss('xFileSize failed w/ rc',rc);
713        log("xFileSize says:",wasm.getMemValue(pOut, 'i64'));
714        rc = ioSyncWrappers.xWrite(sq3File.pointer, zDbFile, 10, 1);
715        if(rc) toss("xWrite() failed!");
716        const readBuf = wasm.scopedAlloc(16);
717        rc = ioSyncWrappers.xRead(sq3File.pointer, readBuf, 6, 2);
718        wasm.setMemValue(readBuf+6,0);
719        let jRead = wasm.cstringToJs(readBuf);
720        log("xRead() got:",jRead);
721        if("sanity"!==jRead) toss("Unexpected xRead() value.");
722        if(vfsSyncWrappers.xSleep){
723          log("xSleep()ing before close()ing...");
724          vfsSyncWrappers.xSleep(opfsVfs.pointer,2000);
725          log("waking up from xSleep()");
726        }
727        rc = ioSyncWrappers.xClose(fid);
728        log("xClose rc =",rc,"opSABView =",state.opSABView);
729        log("Deleting file:",dbFile);
730        vfsSyncWrappers.xDelete(opfsVfs.pointer, zDbFile, 0x1234);
731        vfsSyncWrappers.xAccess(opfsVfs.pointer, zDbFile, 0, pOut);
732        rc = wasm.getMemValue(pOut,'i32');
733        if(rc) toss("Expecting 0 from xAccess(",dbFile,") after xDelete().");
734      }finally{
735        sq3File.dispose();
736        wasm.scopedAllocPop(scope);
737      }
738    }/*sanityCheck()*/;
739
740
741    W.onmessage = function({data}){
742      //log("Worker.onmessage:",data);
743      switch(data.type){
744          case 'loaded':
745            /*Pass our config and shared state on to the async worker.*/
746            wMsg('init',state);
747            break;
748          case 'inited':{
749            /*Indicates that the async partner has received the 'init',
750              so we now know that the state object is no longer subject to
751              being copied by a pending postMessage() call.*/
752            try {
753              const rc = capi.sqlite3_vfs_register(opfsVfs.pointer, 0);
754              if(rc){
755                opfsVfs.dispose();
756                toss("sqlite3_vfs_register(OPFS) failed with rc",rc);
757              }
758              if(opfsVfs.pointer !== capi.sqlite3_vfs_find("opfs")){
759                toss("BUG: sqlite3_vfs_find() failed for just-installed OPFS VFS");
760              }
761              capi.sqlite3_vfs_register.addReference(opfsVfs, opfsIoMethods);
762              state.opSABView = new Int32Array(state.opSAB);
763              if(options.sanityChecks){
764                warn("Running sanity checks because of opfs-sanity-check URL arg...");
765                sanityCheck();
766              }
767              W.onerror = W._originalOnError;
768              delete W._originalOnError;
769              sqlite3.opfs = opfsUtil;
770              log("End of OPFS sqlite3_vfs setup.", opfsVfs);
771              promiseResolve(sqlite3);
772            }catch(e){
773              error(e);
774              promiseReject(e);
775            }
776            break;
777          }
778          default:
779            promiseReject(e);
780            error("Unexpected message from the async worker:",data);
781            break;
782      }
783    };
784  })/*thePromise*/;
785  return thePromise;
786}/*installOpfsVfs()*/;
787sqlite3.installOpfsVfs.defaultProxyUri = "sqlite3-opfs-async-proxy.js";
788}/*sqlite3ApiBootstrap.initializers.push()*/);
789