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-oo1.js and before sqlite3-api-cleanup.js.
19*/
20'use strict';
21self.sqlite3ApiBootstrap.initializers.push(function(sqlite3){
22/**
23   installOpfsVfs() returns a Promise which, on success, installs an
24   sqlite3_vfs named "opfs", suitable for use with all sqlite3 APIs
25   which accept a VFS. It is intended to be called via
26   sqlite3ApiBootstrap.initializersAsync or an equivalent mechanism.
27
28   The installed VFS uses the Origin-Private FileSystem API for
29   all file storage. On error it is rejected with an exception
30   explaining the problem. Reasons for rejection include, but are
31   not limited to:
32
33   - The counterpart Worker (see below) could not be loaded.
34
35   - The environment does not support OPFS. That includes when
36     this function is called from the main window thread.
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. When called, this
55    function removes itself from the sqlite3 object.
56
57  All arguments to this function are for internal/development purposes
58  only. They do not constitute a public API and may change at any
59  time.
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*/
78const installOpfsVfs = function callee(options){
79  if(!self.SharedArrayBuffer ||
80     !self.Atomics ||
81     !self.FileSystemHandle ||
82     !self.FileSystemDirectoryHandle ||
83     !self.FileSystemFileHandle ||
84     !self.FileSystemFileHandle.prototype.createSyncAccessHandle ||
85     !navigator.storage.getDirectory){
86    return Promise.reject(
87      new Error("This environment does not have OPFS support.")
88    );
89  }
90  if(!options || 'object'!==typeof options){
91    options = Object.create(null);
92  }
93  const urlParams = new URL(self.location.href).searchParams;
94  if(undefined===options.verbose){
95    options.verbose = urlParams.has('opfs-verbose') ? 3 : 2;
96  }
97  if(undefined===options.sanityChecks){
98    options.sanityChecks = urlParams.has('opfs-sanity-check');
99  }
100  if(undefined===options.proxyUri){
101    options.proxyUri = callee.defaultProxyUri;
102  }
103
104  if('function' === typeof options.proxyUri){
105    options.proxyUri = options.proxyUri();
106  }
107  const thePromise = new Promise(function(promiseResolve, promiseReject_){
108    const loggers = {
109      0:console.error.bind(console),
110      1:console.warn.bind(console),
111      2:console.log.bind(console)
112    };
113    const logImpl = (level,...args)=>{
114      if(options.verbose>level) loggers[level]("OPFS syncer:",...args);
115    };
116    const log =    (...args)=>logImpl(2, ...args);
117    const warn =   (...args)=>logImpl(1, ...args);
118    const error =  (...args)=>logImpl(0, ...args);
119    const toss = function(...args){throw new Error(args.join(' '))};
120    const capi = sqlite3.capi;
121    const wasm = sqlite3.wasm;
122    const sqlite3_vfs = capi.sqlite3_vfs;
123    const sqlite3_file = capi.sqlite3_file;
124    const sqlite3_io_methods = capi.sqlite3_io_methods;
125    /**
126       Generic utilities for working with OPFS. This will get filled out
127       by the Promise setup and, on success, installed as sqlite3.opfs.
128    */
129    const opfsUtil = Object.create(null);
130    /**
131       Not part of the public API. Solely for internal/development
132       use.
133    */
134    opfsUtil.metrics = {
135      dump: function(){
136        let k, n = 0, t = 0, w = 0;
137        for(k in state.opIds){
138          const m = metrics[k];
139          n += m.count;
140          t += m.time;
141          w += m.wait;
142          m.avgTime = (m.count && m.time) ? (m.time / m.count) : 0;
143          m.avgWait = (m.count && m.wait) ? (m.wait / m.count) : 0;
144        }
145        console.log(self.location.href,
146                    "metrics for",self.location.href,":",metrics,
147                    "\nTotal of",n,"op(s) for",t,
148                    "ms (incl. "+w+" ms of waiting on the async side)");
149        console.log("Serialization metrics:",metrics.s11n);
150        W.postMessage({type:'opfs-async-metrics'});
151      },
152      reset: function(){
153        let k;
154        const r = (m)=>(m.count = m.time = m.wait = 0);
155        for(k in state.opIds){
156          r(metrics[k] = Object.create(null));
157        }
158        let s = metrics.s11n = Object.create(null);
159        s = s.serialize = Object.create(null);
160        s.count = s.time = 0;
161        s = metrics.s11n.deserialize = Object.create(null);
162        s.count = s.time = 0;
163      }
164    }/*metrics*/;
165    const promiseReject = function(err){
166      opfsVfs.dispose();
167      return promiseReject_(err);
168    };
169    const W = new Worker(options.proxyUri);
170    W._originalOnError = W.onerror /* will be restored later */;
171    W.onerror = function(err){
172      // The error object doesn't contain any useful info when the
173      // failure is, e.g., that the remote script is 404.
174      error("Error initializing OPFS asyncer:",err);
175      promiseReject(new Error("Loading OPFS async Worker failed for unknown reasons."));
176    };
177    const pDVfs = capi.sqlite3_vfs_find(null)/*pointer to default VFS*/;
178    const dVfs = pDVfs
179          ? new sqlite3_vfs(pDVfs)
180          : null /* dVfs will be null when sqlite3 is built with
181                    SQLITE_OS_OTHER. Though we cannot currently handle
182                    that case, the hope is to eventually be able to. */;
183    const opfsVfs = new sqlite3_vfs();
184    const opfsIoMethods = new sqlite3_io_methods();
185    opfsVfs.$iVersion = 2/*yes, two*/;
186    opfsVfs.$szOsFile = capi.sqlite3_file.structInfo.sizeof;
187    opfsVfs.$mxPathname = 1024/*sure, why not?*/;
188    opfsVfs.$zName = wasm.allocCString("opfs");
189    // All C-side memory of opfsVfs is zeroed out, but just to be explicit:
190    opfsVfs.$xDlOpen = opfsVfs.$xDlError = opfsVfs.$xDlSym = opfsVfs.$xDlClose = null;
191    opfsVfs.ondispose = [
192      '$zName', opfsVfs.$zName,
193      'cleanup default VFS wrapper', ()=>(dVfs ? dVfs.dispose() : null),
194      'cleanup opfsIoMethods', ()=>opfsIoMethods.dispose()
195    ];
196    /**
197       Pedantic sidebar about opfsVfs.ondispose: the entries in that array
198       are items to clean up when opfsVfs.dispose() is called, but in this
199       environment it will never be called. The VFS instance simply
200       hangs around until the WASM module instance is cleaned up. We
201       "could" _hypothetically_ clean it up by "importing" an
202       sqlite3_os_end() impl into the wasm build, but the shutdown order
203       of the wasm engine and the JS one are undefined so there is no
204       guaranty that the opfsVfs instance would be available in one
205       environment or the other when sqlite3_os_end() is called (_if_ it
206       gets called at all in a wasm build, which is undefined).
207    */
208    /**
209       State which we send to the async-api Worker or share with it.
210       This object must initially contain only cloneable or sharable
211       objects. After the worker's "inited" message arrives, other types
212       of data may be added to it.
213
214       For purposes of Atomics.wait() and Atomics.notify(), we use a
215       SharedArrayBuffer with one slot reserved for each of the API
216       proxy's methods. The sync side of the API uses Atomics.wait()
217       on the corresponding slot and the async side uses
218       Atomics.notify() on that slot.
219
220       The approach of using a single SAB to serialize comms for all
221       instances might(?) lead to deadlock situations in multi-db
222       cases. We should probably have one SAB here with a single slot
223       for locking a per-file initialization step and then allocate a
224       separate SAB like the above one for each file. That will
225       require a bit of acrobatics but should be feasible. The most
226       problematic part is that xOpen() would have to use
227       postMessage() to communicate its SharedArrayBuffer, and mixing
228       that approach with Atomics.wait/notify() gets a bit messy.
229    */
230    const state = Object.create(null);
231    state.verbose = options.verbose;
232    state.littleEndian = (()=>{
233      const buffer = new ArrayBuffer(2);
234      new DataView(buffer).setInt16(0, 256, true /* ==>littleEndian */);
235      // Int16Array uses the platform's endianness.
236      return new Int16Array(buffer)[0] === 256;
237    })();
238    /**
239       Whether the async counterpart should log exceptions to
240       the serialization channel. That produces a great deal of
241       noise for seemingly innocuous things like xAccess() checks
242       for missing files, so this option may have one of 3 values:
243
244       0 = no exception logging
245
246       1 = only log exceptions for "significant" ops like xOpen(),
247       xRead(), and xWrite().
248
249       2 = log all exceptions.
250    */
251    state.asyncS11nExceptions = 1;
252    /* Size of file I/O buffer block. 64k = max sqlite3 page size, and
253       xRead/xWrite() will never deal in blocks larger than that. */
254    state.fileBufferSize = 1024 * 64;
255    state.sabS11nOffset = state.fileBufferSize;
256    /**
257       The size of the block in our SAB for serializing arguments and
258       result values. Needs to be large enough to hold serialized
259       values of any of the proxied APIs. Filenames are the largest
260       part but are limited to opfsVfs.$mxPathname bytes.
261    */
262    state.sabS11nSize = opfsVfs.$mxPathname * 2;
263    /**
264       The SAB used for all data I/O between the synchronous and
265       async halves (file i/o and arg/result s11n).
266    */
267    state.sabIO = new SharedArrayBuffer(
268      state.fileBufferSize/* file i/o block */
269      + state.sabS11nSize/* argument/result serialization block */
270    );
271    state.opIds = Object.create(null);
272    const metrics = Object.create(null);
273    {
274      /* Indexes for use in our SharedArrayBuffer... */
275      let i = 0;
276      /* SAB slot used to communicate which operation is desired
277         between both workers. This worker writes to it and the other
278         listens for changes. */
279      state.opIds.whichOp = i++;
280      /* Slot for storing return values. This worker listens to that
281         slot and the other worker writes to it. */
282      state.opIds.rc = i++;
283      /* Each function gets an ID which this worker writes to
284         the whichOp slot. The async-api worker uses Atomic.wait()
285         on the whichOp slot to figure out which operation to run
286         next. */
287      state.opIds.xAccess = i++;
288      state.opIds.xClose = i++;
289      state.opIds.xDelete = i++;
290      state.opIds.xDeleteNoWait = i++;
291      state.opIds.xFileControl = i++;
292      state.opIds.xFileSize = i++;
293      state.opIds.xLock = i++;
294      state.opIds.xOpen = i++;
295      state.opIds.xRead = i++;
296      state.opIds.xSleep = i++;
297      state.opIds.xSync = i++;
298      state.opIds.xTruncate = i++;
299      state.opIds.xUnlock = i++;
300      state.opIds.xWrite = i++;
301      state.opIds.mkdir = i++;
302      state.opIds['opfs-async-metrics'] = i++;
303      state.opIds['opfs-async-shutdown'] = i++;
304      /* The retry slot is used by the async part for wait-and-retry
305         semantics. Though we could hypothetically use the xSleep slot
306         for that, doing so might lead to undesired side effects. */
307      state.opIds.retry = i++;
308      state.sabOP = new SharedArrayBuffer(
309        i * 4/* ==sizeof int32, noting that Atomics.wait() and friends
310                can only function on Int32Array views of an SAB. */);
311      opfsUtil.metrics.reset();
312    }
313    /**
314       SQLITE_xxx constants to export to the async worker
315       counterpart...
316    */
317    state.sq3Codes = Object.create(null);
318    [
319      'SQLITE_ACCESS_EXISTS',
320      'SQLITE_ACCESS_READWRITE',
321      'SQLITE_ERROR',
322      'SQLITE_IOERR',
323      'SQLITE_IOERR_ACCESS',
324      'SQLITE_IOERR_CLOSE',
325      'SQLITE_IOERR_DELETE',
326      'SQLITE_IOERR_FSYNC',
327      'SQLITE_IOERR_LOCK',
328      'SQLITE_IOERR_READ',
329      'SQLITE_IOERR_SHORT_READ',
330      'SQLITE_IOERR_TRUNCATE',
331      'SQLITE_IOERR_UNLOCK',
332      'SQLITE_IOERR_WRITE',
333      'SQLITE_LOCK_EXCLUSIVE',
334      'SQLITE_LOCK_NONE',
335      'SQLITE_LOCK_PENDING',
336      'SQLITE_LOCK_RESERVED',
337      'SQLITE_LOCK_SHARED',
338      'SQLITE_MISUSE',
339      'SQLITE_NOTFOUND',
340      'SQLITE_OPEN_CREATE',
341      'SQLITE_OPEN_DELETEONCLOSE',
342      'SQLITE_OPEN_READONLY'
343    ].forEach((k)=>{
344      if(undefined === (state.sq3Codes[k] = capi[k])){
345        toss("Maintenance required: not found:",k);
346      }
347    });
348
349    /**
350       Runs the given operation (by name) in the async worker
351       counterpart, waits for its response, and returns the result
352       which the async worker writes to SAB[state.opIds.rc]. The
353       2nd and subsequent arguments must be the aruguments for the
354       async op.
355    */
356    const opRun = (op,...args)=>{
357      const opNdx = state.opIds[op] || toss("Invalid op ID:",op);
358      state.s11n.serialize(...args);
359      Atomics.store(state.sabOPView, state.opIds.rc, -1);
360      Atomics.store(state.sabOPView, state.opIds.whichOp, opNdx);
361      Atomics.notify(state.sabOPView, state.opIds.whichOp)
362      /* async thread will take over here */;
363      const t = performance.now();
364      Atomics.wait(state.sabOPView, state.opIds.rc, -1)
365      /* When this wait() call returns, the async half will have
366         completed the operation and reported its results. */;
367      const rc = Atomics.load(state.sabOPView, state.opIds.rc);
368      metrics[op].wait += performance.now() - t;
369      if(rc && state.asyncS11nExceptions){
370        const err = state.s11n.deserialize();
371        if(err) error(op+"() async error:",...err);
372      }
373      return rc;
374    };
375
376    /**
377       Not part of the public API. Only for test/development use.
378    */
379    opfsUtil.debug = {
380      asyncShutdown: ()=>{
381        warn("Shutting down OPFS async listener. The OPFS VFS will no longer work.");
382        opRun('opfs-async-shutdown');
383      },
384      asyncRestart: ()=>{
385        warn("Attempting to restart OPFS VFS async listener. Might work, might not.");
386        W.postMessage({type: 'opfs-async-restart'});
387      }
388    };
389
390    const initS11n = ()=>{
391      /**
392         !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
393         ACHTUNG: this code is 100% duplicated in the other half of
394         this proxy! The documentation is maintained in the
395         "synchronous half".
396         !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
397
398         This proxy de/serializes cross-thread function arguments and
399         output-pointer values via the state.sabIO SharedArrayBuffer,
400         using the region defined by (state.sabS11nOffset,
401         state.sabS11nOffset]. Only one dataset is recorded at a time.
402
403         This is not a general-purpose format. It only supports the
404         range of operations, and data sizes, needed by the
405         sqlite3_vfs and sqlite3_io_methods operations. Serialized
406         data are transient and this serialization algorithm may
407         change at any time.
408
409         The data format can be succinctly summarized as:
410
411         Nt...Td...D
412
413         Where:
414
415         - N = number of entries (1 byte)
416
417         - t = type ID of first argument (1 byte)
418
419         - ...T = type IDs of the 2nd and subsequent arguments (1 byte
420         each).
421
422         - d = raw bytes of first argument (per-type size).
423
424         - ...D = raw bytes of the 2nd and subsequent arguments (per-type
425         size).
426
427         All types except strings have fixed sizes. Strings are stored
428         using their TextEncoder/TextDecoder representations. It would
429         arguably make more sense to store them as Int16Arrays of
430         their JS character values, but how best/fastest to get that
431         in and out of string form is an open point. Initial
432         experimentation with that approach did not gain us any speed.
433
434         Historical note: this impl was initially about 1% this size by
435         using using JSON.stringify/parse(), but using fit-to-purpose
436         serialization saves considerable runtime.
437      */
438      if(state.s11n) return state.s11n;
439      const textDecoder = new TextDecoder(),
440            textEncoder = new TextEncoder('utf-8'),
441            viewU8 = new Uint8Array(state.sabIO, state.sabS11nOffset, state.sabS11nSize),
442            viewDV = new DataView(state.sabIO, state.sabS11nOffset, state.sabS11nSize);
443      state.s11n = Object.create(null);
444      /* Only arguments and return values of these types may be
445         serialized. This covers the whole range of types needed by the
446         sqlite3_vfs API. */
447      const TypeIds = Object.create(null);
448      TypeIds.number  = { id: 1, size: 8, getter: 'getFloat64', setter: 'setFloat64' };
449      TypeIds.bigint  = { id: 2, size: 8, getter: 'getBigInt64', setter: 'setBigInt64' };
450      TypeIds.boolean = { id: 3, size: 4, getter: 'getInt32', setter: 'setInt32' };
451      TypeIds.string =  { id: 4 };
452
453      const getTypeId = (v)=>(
454        TypeIds[typeof v]
455          || toss("Maintenance required: this value type cannot be serialized.",v)
456      );
457      const getTypeIdById = (tid)=>{
458        switch(tid){
459            case TypeIds.number.id: return TypeIds.number;
460            case TypeIds.bigint.id: return TypeIds.bigint;
461            case TypeIds.boolean.id: return TypeIds.boolean;
462            case TypeIds.string.id: return TypeIds.string;
463            default: toss("Invalid type ID:",tid);
464        }
465      };
466
467      /**
468         Returns an array of the deserialized state stored by the most
469         recent serialize() operation (from from this thread or the
470         counterpart thread), or null if the serialization buffer is empty.
471      */
472      state.s11n.deserialize = function(){
473        ++metrics.s11n.deserialize.count;
474        const t = performance.now();
475        const argc = viewU8[0];
476        const rc = argc ? [] : null;
477        if(argc){
478          const typeIds = [];
479          let offset = 1, i, n, v;
480          for(i = 0; i < argc; ++i, ++offset){
481            typeIds.push(getTypeIdById(viewU8[offset]));
482          }
483          for(i = 0; i < argc; ++i){
484            const t = typeIds[i];
485            if(t.getter){
486              v = viewDV[t.getter](offset, state.littleEndian);
487              offset += t.size;
488            }else{/*String*/
489              n = viewDV.getInt32(offset, state.littleEndian);
490              offset += 4;
491              v = textDecoder.decode(viewU8.slice(offset, offset+n));
492              offset += n;
493            }
494            rc.push(v);
495          }
496        }
497        //log("deserialize:",argc, rc);
498        metrics.s11n.deserialize.time += performance.now() - t;
499        return rc;
500      };
501
502      /**
503         Serializes all arguments to the shared buffer for consumption
504         by the counterpart thread.
505
506         This routine is only intended for serializing OPFS VFS
507         arguments and (in at least one special case) result values,
508         and the buffer is sized to be able to comfortably handle
509         those.
510
511         If passed no arguments then it zeroes out the serialization
512         state.
513      */
514      state.s11n.serialize = function(...args){
515        const t = performance.now();
516        ++metrics.s11n.serialize.count;
517        if(args.length){
518          //log("serialize():",args);
519          const typeIds = [];
520          let i = 0, offset = 1;
521          viewU8[0] = args.length & 0xff /* header = # of args */;
522          for(; i < args.length; ++i, ++offset){
523            /* Write the TypeIds.id value into the next args.length
524               bytes. */
525            typeIds.push(getTypeId(args[i]));
526            viewU8[offset] = typeIds[i].id;
527          }
528          for(i = 0; i < args.length; ++i) {
529            /* Deserialize the following bytes based on their
530               corresponding TypeIds.id from the header. */
531            const t = typeIds[i];
532            if(t.setter){
533              viewDV[t.setter](offset, args[i], state.littleEndian);
534              offset += t.size;
535            }else{/*String*/
536              const s = textEncoder.encode(args[i]);
537              viewDV.setInt32(offset, s.byteLength, state.littleEndian);
538              offset += 4;
539              viewU8.set(s, offset);
540              offset += s.byteLength;
541            }
542          }
543          //log("serialize() result:",viewU8.slice(0,offset));
544        }else{
545          viewU8[0] = 0;
546        }
547        metrics.s11n.serialize.time += performance.now() - t;
548      };
549      return state.s11n;
550    }/*initS11n()*/;
551
552    /**
553       Generates a random ASCII string len characters long, intended for
554       use as a temporary file name.
555    */
556    const randomFilename = function f(len=16){
557      if(!f._chars){
558        f._chars = "abcdefghijklmnopqrstuvwxyz"+
559          "ABCDEFGHIJKLMNOPQRSTUVWXYZ"+
560          "012346789";
561        f._n = f._chars.length;
562      }
563      const a = [];
564      let i = 0;
565      for( ; i < len; ++i){
566        const ndx = Math.random() * (f._n * 64) % f._n | 0;
567        a[i] = f._chars[ndx];
568      }
569      return a.join('');
570    };
571
572    /**
573       Map of sqlite3_file pointers to objects constructed by xOpen().
574    */
575    const __openFiles = Object.create(null);
576
577    /**
578       Installs a StructBinder-bound function pointer member of the
579       given name and function in the given StructType target object.
580       It creates a WASM proxy for the given function and arranges for
581       that proxy to be cleaned up when tgt.dispose() is called.  Throws
582       on the slightest hint of error (e.g. tgt is-not-a StructType,
583       name does not map to a struct-bound member, etc.).
584
585       Returns a proxy for this function which is bound to tgt and takes
586       2 args (name,func). That function returns the same thing,
587       permitting calls to be chained.
588
589       If called with only 1 arg, it has no side effects but returns a
590       func with the same signature as described above.
591    */
592    const installMethod = function callee(tgt, name, func){
593      if(!(tgt instanceof sqlite3.StructBinder.StructType)){
594        toss("Usage error: target object is-not-a StructType.");
595      }
596      if(1===arguments.length){
597        return (n,f)=>callee(tgt,n,f);
598      }
599      if(!callee.argcProxy){
600        callee.argcProxy = function(func,sig){
601          return function(...args){
602            if(func.length!==arguments.length){
603              toss("Argument mismatch. Native signature is:",sig);
604            }
605            return func.apply(this, args);
606          }
607        };
608        callee.removeFuncList = function(){
609          if(this.ondispose.__removeFuncList){
610            this.ondispose.__removeFuncList.forEach(
611              (v,ndx)=>{
612                if('number'===typeof v){
613                  try{wasm.uninstallFunction(v)}
614                  catch(e){/*ignore*/}
615                }
616                /* else it's a descriptive label for the next number in
617                   the list. */
618              }
619            );
620            delete this.ondispose.__removeFuncList;
621          }
622        };
623      }/*static init*/
624      const sigN = tgt.memberSignature(name);
625      if(sigN.length<2){
626        toss("Member",name," is not a function pointer. Signature =",sigN);
627      }
628      const memKey = tgt.memberKey(name);
629      const fProxy = 0
630      /** This middle-man proxy is only for use during development, to
631          confirm that we always pass the proper number of
632          arguments. We know that the C-level code will always use the
633          correct argument count. */
634            ? callee.argcProxy(func, sigN)
635            : func;
636      const pFunc = wasm.installFunction(fProxy, tgt.memberSignature(name, true));
637      tgt[memKey] = pFunc;
638      if(!tgt.ondispose) tgt.ondispose = [];
639      if(!tgt.ondispose.__removeFuncList){
640        tgt.ondispose.push('ondispose.__removeFuncList handler',
641                           callee.removeFuncList);
642        tgt.ondispose.__removeFuncList = [];
643      }
644      tgt.ondispose.__removeFuncList.push(memKey, pFunc);
645      return (n,f)=>callee(tgt, n, f);
646    }/*installMethod*/;
647
648    const opTimer = Object.create(null);
649    opTimer.op = undefined;
650    opTimer.start = undefined;
651    const mTimeStart = (op)=>{
652      opTimer.start = performance.now();
653      opTimer.op = op;
654      ++metrics[op].count;
655    };
656    const mTimeEnd = ()=>(
657      metrics[opTimer.op].time += performance.now() - opTimer.start
658    );
659
660    /**
661       Impls for the sqlite3_io_methods methods. Maintenance reminder:
662       members are in alphabetical order to simplify finding them.
663    */
664    const ioSyncWrappers = {
665      xCheckReservedLock: function(pFile,pOut){
666        /**
667           As of late 2022, only a single lock can be held on an OPFS
668           file. We have no way of checking whether any _other_ db
669           connection has a lock except by trying to obtain and (on
670           success) release a sync-handle for it, but doing so would
671           involve an inherent race condition. For the time being,
672           pending a better solution, we simply report whether the
673           given pFile instance has a lock.
674        */
675        const f = __openFiles[pFile];
676        wasm.setMemValue(pOut, f.lockMode ? 1 : 0, 'i32');
677        return 0;
678      },
679      xClose: function(pFile){
680        mTimeStart('xClose');
681        let rc = 0;
682        const f = __openFiles[pFile];
683        if(f){
684          delete __openFiles[pFile];
685          rc = opRun('xClose', pFile);
686          if(f.sq3File) f.sq3File.dispose();
687        }
688        mTimeEnd();
689        return rc;
690      },
691      xDeviceCharacteristics: function(pFile){
692        //debug("xDeviceCharacteristics(",pFile,")");
693        return capi.SQLITE_IOCAP_UNDELETABLE_WHEN_OPEN;
694      },
695      xFileControl: function(pFile, opId, pArg){
696        mTimeStart('xFileControl');
697        const rc = (capi.SQLITE_FCNTL_SYNC===opId)
698              ? opRun('xSync', pFile, 0)
699              : capi.SQLITE_NOTFOUND;
700        mTimeEnd();
701        return rc;
702      },
703      xFileSize: function(pFile,pSz64){
704        mTimeStart('xFileSize');
705        const rc = opRun('xFileSize', pFile);
706        if(0==rc){
707          const sz = state.s11n.deserialize()[0];
708          wasm.setMemValue(pSz64, sz, 'i64');
709        }
710        mTimeEnd();
711        return rc;
712      },
713      xLock: function(pFile,lockType){
714        mTimeStart('xLock');
715        const f = __openFiles[pFile];
716        let rc = 0;
717        if( capi.SQLITE_LOCK_NONE === f.lockType ) {
718          rc = opRun('xLock', pFile, lockType);
719          if( 0===rc ) f.lockType = lockType;
720        }else{
721          f.lockType = lockType;
722        }
723        mTimeEnd();
724        return rc;
725      },
726      xRead: function(pFile,pDest,n,offset64){
727        mTimeStart('xRead');
728        const f = __openFiles[pFile];
729        let rc;
730        try {
731          rc = opRun('xRead',pFile, n, Number(offset64));
732          if(0===rc || capi.SQLITE_IOERR_SHORT_READ===rc){
733            /**
734               Results get written to the SharedArrayBuffer f.sabView.
735               Because the heap is _not_ a SharedArrayBuffer, we have
736               to copy the results. TypedArray.set() seems to be the
737               fastest way to copy this. */
738            wasm.heap8u().set(f.sabView.subarray(0, n), pDest);
739          }
740        }catch(e){
741          error("xRead(",arguments,") failed:",e,f);
742          rc = capi.SQLITE_IOERR_READ;
743        }
744        mTimeEnd();
745        return rc;
746      },
747      xSync: function(pFile,flags){
748        ++metrics.xSync.count;
749        return 0; // impl'd in xFileControl()
750      },
751      xTruncate: function(pFile,sz64){
752        mTimeStart('xTruncate');
753        const rc = opRun('xTruncate', pFile, Number(sz64));
754        mTimeEnd();
755        return rc;
756      },
757      xUnlock: function(pFile,lockType){
758        mTimeStart('xUnlock');
759        const f = __openFiles[pFile];
760        let rc = 0;
761        if( capi.SQLITE_LOCK_NONE === lockType
762          && f.lockType ){
763          rc = opRun('xUnlock', pFile, lockType);
764        }
765        if( 0===rc ) f.lockType = lockType;
766        mTimeEnd();
767        return rc;
768      },
769      xWrite: function(pFile,pSrc,n,offset64){
770        mTimeStart('xWrite');
771        const f = __openFiles[pFile];
772        let rc;
773        try {
774          f.sabView.set(wasm.heap8u().subarray(pSrc, pSrc+n));
775          rc = opRun('xWrite', pFile, n, Number(offset64));
776        }catch(e){
777          error("xWrite(",arguments,") failed:",e,f);
778          rc = capi.SQLITE_IOERR_WRITE;
779        }
780        mTimeEnd();
781        return rc;
782      }
783    }/*ioSyncWrappers*/;
784
785    /**
786       Impls for the sqlite3_vfs methods. Maintenance reminder: members
787       are in alphabetical order to simplify finding them.
788    */
789    const vfsSyncWrappers = {
790      xAccess: function(pVfs,zName,flags,pOut){
791        mTimeStart('xAccess');
792        const rc = opRun('xAccess', wasm.cstringToJs(zName));
793        wasm.setMemValue( pOut, (rc ? 0 : 1), 'i32' );
794        mTimeEnd();
795        return 0;
796      },
797      xCurrentTime: function(pVfs,pOut){
798        /* If it turns out that we need to adjust for timezone, see:
799           https://stackoverflow.com/a/11760121/1458521 */
800        wasm.setMemValue(pOut, 2440587.5 + (new Date().getTime()/86400000),
801                         'double');
802        return 0;
803      },
804      xCurrentTimeInt64: function(pVfs,pOut){
805        // TODO: confirm that this calculation is correct
806        wasm.setMemValue(pOut, (2440587.5 * 86400000) + new Date().getTime(),
807                         'i64');
808        return 0;
809      },
810      xDelete: function(pVfs, zName, doSyncDir){
811        mTimeStart('xDelete');
812        opRun('xDelete', wasm.cstringToJs(zName), doSyncDir, false);
813        /* We're ignoring errors because we cannot yet differentiate
814           between harmless and non-harmless failures. */
815        mTimeEnd();
816        return 0;
817      },
818      xFullPathname: function(pVfs,zName,nOut,pOut){
819        /* Until/unless we have some notion of "current dir"
820           in OPFS, simply copy zName to pOut... */
821        const i = wasm.cstrncpy(pOut, zName, nOut);
822        return i<nOut ? 0 : capi.SQLITE_CANTOPEN
823        /*CANTOPEN is required by the docs but SQLITE_RANGE would be a closer match*/;
824      },
825      xGetLastError: function(pVfs,nOut,pOut){
826        /* TODO: store exception.message values from the async
827           partner in a dedicated SharedArrayBuffer, noting that we'd have
828           to encode them... TextEncoder can do that for us. */
829        warn("OPFS xGetLastError() has nothing sensible to return.");
830        return 0;
831      },
832      //xSleep is optionally defined below
833      xOpen: function f(pVfs, zName, pFile, flags, pOutFlags){
834        mTimeStart('xOpen');
835        if(0===zName){
836          zName = randomFilename();
837        }else if('number'===typeof zName){
838          zName = wasm.cstringToJs(zName);
839        }
840        const fh = Object.create(null);
841        fh.fid = pFile;
842        fh.filename = zName;
843        fh.sab = new SharedArrayBuffer(state.fileBufferSize);
844        fh.flags = flags;
845        const rc = opRun('xOpen', pFile, zName, flags);
846        if(!rc){
847          /* Recall that sqlite3_vfs::xClose() will be called, even on
848             error, unless pFile->pMethods is NULL. */
849          if(fh.readOnly){
850            wasm.setMemValue(pOutFlags, capi.SQLITE_OPEN_READONLY, 'i32');
851          }
852          __openFiles[pFile] = fh;
853          fh.sabView = state.sabFileBufView;
854          fh.sq3File = new sqlite3_file(pFile);
855          fh.sq3File.$pMethods = opfsIoMethods.pointer;
856          fh.lockType = capi.SQLITE_LOCK_NONE;
857        }
858        mTimeEnd();
859        return rc;
860      }/*xOpen()*/
861    }/*vfsSyncWrappers*/;
862
863    if(dVfs){
864      opfsVfs.$xRandomness = dVfs.$xRandomness;
865      opfsVfs.$xSleep = dVfs.$xSleep;
866    }
867    if(!opfsVfs.$xRandomness){
868      /* If the default VFS has no xRandomness(), add a basic JS impl... */
869      vfsSyncWrappers.xRandomness = function(pVfs, nOut, pOut){
870        const heap = wasm.heap8u();
871        let i = 0;
872        for(; i < nOut; ++i) heap[pOut + i] = (Math.random()*255000) & 0xFF;
873        return i;
874      };
875    }
876    if(!opfsVfs.$xSleep){
877      /* If we can inherit an xSleep() impl from the default VFS then
878         assume it's sane and use it, otherwise install a JS-based
879         one. */
880      vfsSyncWrappers.xSleep = function(pVfs,ms){
881        Atomics.wait(state.sabOPView, state.opIds.xSleep, 0, ms);
882        return 0;
883      };
884    }
885
886    /* Install the vfs/io_methods into their C-level shared instances... */
887    for(let k of Object.keys(ioSyncWrappers)){
888      installMethod(opfsIoMethods, k, ioSyncWrappers[k]);
889    }
890    for(let k of Object.keys(vfsSyncWrappers)){
891      installMethod(opfsVfs, k, vfsSyncWrappers[k]);
892    }
893
894    /**
895       Expects an OPFS file path. It gets resolved, such that ".."
896       components are properly expanded, and returned. If the 2nd arg
897       is true, the result is returned as an array of path elements,
898       else an absolute path string is returned.
899    */
900    opfsUtil.getResolvedPath = function(filename,splitIt){
901      const p = new URL(filename, "file://irrelevant").pathname;
902      return splitIt ? p.split('/').filter((v)=>!!v) : p;
903    };
904
905    /**
906       Takes the absolute path to a filesystem element. Returns an
907       array of [handleOfContainingDir, filename]. If the 2nd argument
908       is truthy then each directory element leading to the file is
909       created along the way. Throws if any creation or resolution
910       fails.
911    */
912    opfsUtil.getDirForFilename = async function f(absFilename, createDirs = false){
913      const path = opfsUtil.getResolvedPath(absFilename, true);
914      const filename = path.pop();
915      let dh = opfsUtil.rootDirectory;
916      for(const dirName of path){
917        if(dirName){
918          dh = await dh.getDirectoryHandle(dirName, {create: !!createDirs});
919        }
920      }
921      return [dh, filename];
922    };
923
924    /**
925       Creates the given directory name, recursively, in
926       the OPFS filesystem. Returns true if it succeeds or the
927       directory already exists, else false.
928    */
929    opfsUtil.mkdir = async function(absDirName){
930      try {
931        await opfsUtil.getDirForFilename(absDirName+"/filepart", true);
932        return true;
933      }catch(e){
934        //console.warn("mkdir(",absDirName,") failed:",e);
935        return false;
936      }
937    };
938    /**
939       Checks whether the given OPFS filesystem entry exists,
940       returning true if it does, false if it doesn't.
941    */
942    opfsUtil.entryExists = async function(fsEntryName){
943      try {
944        const [dh, fn] = await opfsUtil.getDirForFilename(fsEntryName);
945        await dh.getFileHandle(fn);
946        return true;
947      }catch(e){
948        return false;
949      }
950    };
951
952    /**
953       Generates a random ASCII string, intended for use as a
954       temporary file name. Its argument is the length of the string,
955       defaulting to 16.
956    */
957    opfsUtil.randomFilename = randomFilename;
958
959    /**
960       Re-registers the OPFS VFS. This is intended only for odd use
961       cases which have to call sqlite3_shutdown() as part of their
962       initialization process, which will unregister the VFS
963       registered by installOpfsVfs(). If passed a truthy value, the
964       OPFS VFS is registered as the default VFS, else it is not made
965       the default. Returns the result of the the
966       sqlite3_vfs_register() call.
967
968       Design note: the problem of having to re-register things after
969       a shutdown/initialize pair is more general. How to best plug
970       that in to the library is unclear. In particular, we cannot
971       hook in to any C-side calls to sqlite3_initialize(), so we
972       cannot add an after-initialize callback mechanism.
973    */
974    opfsUtil.registerVfs = (asDefault=false)=>{
975      return wasm.exports.sqlite3_vfs_register(
976        opfsVfs.pointer, asDefault ? 1 : 0
977      );
978    };
979
980    /**
981       Returns a promise which resolves to an object which represents
982       all files and directories in the OPFS tree. The top-most object
983       has two properties: `dirs` is an array of directory entries
984       (described below) and `files` is a list of file names for all
985       files in that directory.
986
987       Traversal starts at sqlite3.opfs.rootDirectory.
988
989       Each `dirs` entry is an object in this form:
990
991       ```
992       { name: directoryName,
993         dirs: [...subdirs],
994         files: [...file names]
995       }
996       ```
997
998       The `files` and `subdirs` entries are always set but may be
999       empty arrays.
1000
1001       The returned object has the same structure but its `name` is
1002       an empty string. All returned objects are created with
1003       Object.create(null), so have no prototype.
1004
1005       Design note: the entries do not contain more information,
1006       e.g. file sizes, because getting such info is not only
1007       expensive but is subject to locking-related errors.
1008    */
1009    opfsUtil.treeList = async function(){
1010      const doDir = async function callee(dirHandle,tgt){
1011        tgt.name = dirHandle.name;
1012        tgt.dirs = [];
1013        tgt.files = [];
1014        for await (const handle of dirHandle.values()){
1015          if('directory' === handle.kind){
1016            const subDir = Object.create(null);
1017            tgt.dirs.push(subDir);
1018            await callee(handle, subDir);
1019          }else{
1020            tgt.files.push(handle.name);
1021          }
1022        }
1023      };
1024      const root = Object.create(null);
1025      await doDir(opfsUtil.rootDirectory, root);
1026      return root;
1027    };
1028
1029    /**
1030       Irrevocably deletes _all_ files in the current origin's OPFS.
1031       Obviously, this must be used with great caution. It may throw
1032       an exception if removal of anything fails (e.g. a file is
1033       locked), but the precise conditions under which it will throw
1034       are not documented (so we cannot tell you what they are).
1035    */
1036    opfsUtil.rmfr = async function(){
1037      const dir = opfsUtil.rootDirectory, opt = {recurse: true};
1038      for await (const handle of dir.values()){
1039        dir.removeEntry(handle.name, opt);
1040      }
1041    };
1042
1043    /**
1044       Deletes the given OPFS filesystem entry.  As this environment
1045       has no notion of "current directory", the given name must be an
1046       absolute path. If the 2nd argument is truthy, deletion is
1047       recursive (use with caution!).
1048
1049       The returned Promise resolves to true if the deletion was
1050       successful, else false (but...). The OPFS API reports the
1051       reason for the failure only in human-readable form, not
1052       exceptions which can be type-checked to determine the
1053       failure. Because of that...
1054
1055       If the final argument is truthy then this function will
1056       propagate any exception on error, rather than returning false.
1057    */
1058    opfsUtil.unlink = async function(fsEntryName, recursive = false,
1059                                          throwOnError = false){
1060      try {
1061        const [hDir, filenamePart] =
1062              await opfsUtil.getDirForFilename(fsEntryName, false);
1063        await hDir.removeEntry(filenamePart, {recursive});
1064        return true;
1065      }catch(e){
1066        if(throwOnError){
1067          throw new Error("unlink(",arguments[0],") failed: "+e.message,{
1068            cause: e
1069          });
1070        }
1071        return false;
1072      }
1073    };
1074
1075    /**
1076       Traverses the OPFS filesystem, calling a callback for each one.
1077       The argument may be either a callback function or an options object
1078       with any of the following properties:
1079
1080       - `callback`: function which gets called for each filesystem
1081         entry.  It gets passed 3 arguments: 1) the
1082         FileSystemFileHandle or FileSystemDirectoryHandle of each
1083         entry (noting that both are instanceof FileSystemHandle). 2)
1084         the FileSystemDirectoryHandle of the parent directory. 3) the
1085         current depth level, with 0 being at the top of the tree
1086         relative to the starting directory. If the callback returns a
1087         literal false, as opposed to any other falsy value, traversal
1088         stops without an error. Any exceptions it throws are
1089         propagated. Results are undefined if the callback manipulate
1090         the filesystem (e.g. removing or adding entries) because the
1091         how OPFS iterators behave in the face of such changes is
1092         undocumented.
1093
1094       - `recursive` [bool=true]: specifies whether to recurse into
1095         subdirectories or not. Whether recursion is depth-first or
1096         breadth-first is unspecified!
1097
1098       - `directory` [FileSystemDirectoryEntry=sqlite3.opfs.rootDirectory]
1099         specifies the starting directory.
1100
1101       If this function is passed a function, it is assumed to be the
1102       callback.
1103
1104       Returns a promise because it has to (by virtue of being async)
1105       but that promise has no specific meaning: the traversal it
1106       performs is synchronous. The promise must be used to catch any
1107       exceptions propagated by the callback, however.
1108
1109       TODO: add an option which specifies whether to traverse
1110       depth-first or breadth-first. We currently do depth-first but
1111       an incremental file browsing widget would benefit more from
1112       breadth-first.
1113    */
1114    opfsUtil.traverse = async function(opt){
1115      const defaultOpt = {
1116        recursive: true,
1117        directory: opfsUtil.rootDirectory
1118      };
1119      if('function'===typeof opt){
1120        opt = {callback:opt};
1121      }
1122      opt = Object.assign(defaultOpt, opt||{});
1123      const doDir = async function callee(dirHandle, depth){
1124        for await (const handle of dirHandle.values()){
1125          if(false === opt.callback(handle, dirHandle, depth)) return false;
1126          else if(opt.recursive && 'directory' === handle.kind){
1127            if(false === await callee(handle, depth + 1)) break;
1128          }
1129        }
1130      };
1131      doDir(opt.directory, 0);
1132    };
1133
1134    //TODO to support fiddle and worker1 db upload:
1135    //opfsUtil.createFile = function(absName, content=undefined){...}
1136
1137    if(sqlite3.oo1){
1138      opfsUtil.OpfsDb = function(...args){
1139        const opt = sqlite3.oo1.DB.dbCtorHelper.normalizeArgs(...args);
1140        opt.vfs = opfsVfs.$zName;
1141        sqlite3.oo1.DB.dbCtorHelper.call(this, opt);
1142      };
1143      opfsUtil.OpfsDb.prototype = Object.create(sqlite3.oo1.DB.prototype);
1144      sqlite3.oo1.DB.dbCtorHelper.setVfsPostOpenSql(
1145        opfsVfs.pointer,
1146        [
1147          /* Truncate journal mode is faster than delete or wal for
1148             this vfs, per speedtest1. */
1149          "pragma journal_mode=truncate;"
1150          /*
1151            This vfs benefits hugely from cache on moderate/large
1152            speedtest1 --size 50 and --size 100 workloads. We currently
1153            rely on setting a non-default cache size when building
1154            sqlite3.wasm. If that policy changes, the cache can
1155            be set here.
1156          */
1157          //"pragma cache_size=-8388608;"
1158        ].join('')
1159      );
1160    }
1161
1162    /**
1163       Potential TODOs:
1164
1165       - Expose one or both of the Worker objects via opfsUtil and
1166         publish an interface for proxying the higher-level OPFS
1167         features like getting a directory listing.
1168    */
1169    const sanityCheck = function(){
1170      const scope = wasm.scopedAllocPush();
1171      const sq3File = new sqlite3_file();
1172      try{
1173        const fid = sq3File.pointer;
1174        const openFlags = capi.SQLITE_OPEN_CREATE
1175              | capi.SQLITE_OPEN_READWRITE
1176        //| capi.SQLITE_OPEN_DELETEONCLOSE
1177              | capi.SQLITE_OPEN_MAIN_DB;
1178        const pOut = wasm.scopedAlloc(8);
1179        const dbFile = "/sanity/check/file"+randomFilename(8);
1180        const zDbFile = wasm.scopedAllocCString(dbFile);
1181        let rc;
1182        state.s11n.serialize("This is ä string.");
1183        rc = state.s11n.deserialize();
1184        log("deserialize() says:",rc);
1185        if("This is ä string."!==rc[0]) toss("String d13n error.");
1186        vfsSyncWrappers.xAccess(opfsVfs.pointer, zDbFile, 0, pOut);
1187        rc = wasm.getMemValue(pOut,'i32');
1188        log("xAccess(",dbFile,") exists ?=",rc);
1189        rc = vfsSyncWrappers.xOpen(opfsVfs.pointer, zDbFile,
1190                                   fid, openFlags, pOut);
1191        log("open rc =",rc,"state.sabOPView[xOpen] =",
1192            state.sabOPView[state.opIds.xOpen]);
1193        if(0!==rc){
1194          error("open failed with code",rc);
1195          return;
1196        }
1197        vfsSyncWrappers.xAccess(opfsVfs.pointer, zDbFile, 0, pOut);
1198        rc = wasm.getMemValue(pOut,'i32');
1199        if(!rc) toss("xAccess() failed to detect file.");
1200        rc = ioSyncWrappers.xSync(sq3File.pointer, 0);
1201        if(rc) toss('sync failed w/ rc',rc);
1202        rc = ioSyncWrappers.xTruncate(sq3File.pointer, 1024);
1203        if(rc) toss('truncate failed w/ rc',rc);
1204        wasm.setMemValue(pOut,0,'i64');
1205        rc = ioSyncWrappers.xFileSize(sq3File.pointer, pOut);
1206        if(rc) toss('xFileSize failed w/ rc',rc);
1207        log("xFileSize says:",wasm.getMemValue(pOut, 'i64'));
1208        rc = ioSyncWrappers.xWrite(sq3File.pointer, zDbFile, 10, 1);
1209        if(rc) toss("xWrite() failed!");
1210        const readBuf = wasm.scopedAlloc(16);
1211        rc = ioSyncWrappers.xRead(sq3File.pointer, readBuf, 6, 2);
1212        wasm.setMemValue(readBuf+6,0);
1213        let jRead = wasm.cstringToJs(readBuf);
1214        log("xRead() got:",jRead);
1215        if("sanity"!==jRead) toss("Unexpected xRead() value.");
1216        if(vfsSyncWrappers.xSleep){
1217          log("xSleep()ing before close()ing...");
1218          vfsSyncWrappers.xSleep(opfsVfs.pointer,2000);
1219          log("waking up from xSleep()");
1220        }
1221        rc = ioSyncWrappers.xClose(fid);
1222        log("xClose rc =",rc,"sabOPView =",state.sabOPView);
1223        log("Deleting file:",dbFile);
1224        vfsSyncWrappers.xDelete(opfsVfs.pointer, zDbFile, 0x1234);
1225        vfsSyncWrappers.xAccess(opfsVfs.pointer, zDbFile, 0, pOut);
1226        rc = wasm.getMemValue(pOut,'i32');
1227        if(rc) toss("Expecting 0 from xAccess(",dbFile,") after xDelete().");
1228        warn("End of OPFS sanity checks.");
1229      }finally{
1230        sq3File.dispose();
1231        wasm.scopedAllocPop(scope);
1232      }
1233    }/*sanityCheck()*/;
1234
1235    W.onmessage = function({data}){
1236      //log("Worker.onmessage:",data);
1237      switch(data.type){
1238          case 'opfs-async-loaded':
1239            /*Arrives as soon as the asyc proxy finishes loading.
1240              Pass our config and shared state on to the async worker.*/
1241            W.postMessage({type: 'opfs-async-init',args: state});
1242            break;
1243          case 'opfs-async-inited':{
1244            /*Indicates that the async partner has received the 'init'
1245              and has finished initializing, so the real work can
1246              begin...*/
1247            try {
1248              const rc = capi.sqlite3_vfs_register(opfsVfs.pointer, 0);
1249              if(rc){
1250                toss("sqlite3_vfs_register(OPFS) failed with rc",rc);
1251              }
1252              if(opfsVfs.pointer !== capi.sqlite3_vfs_find("opfs")){
1253                toss("BUG: sqlite3_vfs_find() failed for just-installed OPFS VFS");
1254              }
1255              capi.sqlite3_vfs_register.addReference(opfsVfs, opfsIoMethods);
1256              state.sabOPView = new Int32Array(state.sabOP);
1257              state.sabFileBufView = new Uint8Array(state.sabIO, 0, state.fileBufferSize);
1258              state.sabS11nView = new Uint8Array(state.sabIO, state.sabS11nOffset, state.sabS11nSize);
1259              initS11n();
1260              if(options.sanityChecks){
1261                warn("Running sanity checks because of opfs-sanity-check URL arg...");
1262                sanityCheck();
1263              }
1264              navigator.storage.getDirectory().then((d)=>{
1265                W.onerror = W._originalOnError;
1266                delete W._originalOnError;
1267                sqlite3.opfs = opfsUtil;
1268                opfsUtil.rootDirectory = d;
1269                log("End of OPFS sqlite3_vfs setup.", opfsVfs);
1270                promiseResolve(sqlite3);
1271              });
1272            }catch(e){
1273              error(e);
1274              promiseReject(e);
1275            }
1276            break;
1277          }
1278          default:
1279            promiseReject(e);
1280            error("Unexpected message from the async worker:",data);
1281            break;
1282      }/*switch(data.type)*/
1283    }/*W.onmessage()*/;
1284  })/*thePromise*/;
1285  return thePromise;
1286}/*installOpfsVfs()*/;
1287installOpfsVfs.defaultProxyUri =
1288  "sqlite3-opfs-async-proxy.js";
1289self.sqlite3ApiBootstrap.initializersAsync.push(async (sqlite3)=>{
1290  if(sqlite3.scriptInfo && !sqlite3.scriptInfo.isWorker){
1291    return;
1292  }
1293  try{
1294    let proxyJs = installOpfsVfs.defaultProxyUri;
1295    if(sqlite3.scriptInfo.sqlite3Dir){
1296      installOpfsVfs.defaultProxyUri =
1297        sqlite3.scriptInfo.sqlite3Dir + proxyJs;
1298      //console.warn("installOpfsVfs.defaultProxyUri =",installOpfsVfs.defaultProxyUri);
1299    }
1300    return installOpfsVfs().catch((e)=>{
1301      console.warn("Ignoring inability to install OPFS sqlite3_vfs:",e.message);
1302    });
1303  }catch(e){
1304    console.error("installOpfsVfs() exception:",e);
1305    throw e;
1306  }
1307});
1308}/*sqlite3ApiBootstrap.initializers.push()*/);
1309