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
471         empty.  If passed a truthy argument, the serialization buffer
472         is cleared after deserialization.
473      */
474      state.s11n.deserialize = function(clear=false){
475        ++metrics.s11n.deserialize.count;
476        const t = performance.now();
477        const argc = viewU8[0];
478        const rc = argc ? [] : null;
479        if(argc){
480          const typeIds = [];
481          let offset = 1, i, n, v;
482          for(i = 0; i < argc; ++i, ++offset){
483            typeIds.push(getTypeIdById(viewU8[offset]));
484          }
485          for(i = 0; i < argc; ++i){
486            const t = typeIds[i];
487            if(t.getter){
488              v = viewDV[t.getter](offset, state.littleEndian);
489              offset += t.size;
490            }else{/*String*/
491              n = viewDV.getInt32(offset, state.littleEndian);
492              offset += 4;
493              v = textDecoder.decode(viewU8.slice(offset, offset+n));
494              offset += n;
495            }
496            rc.push(v);
497          }
498        }
499        if(clear) viewU8[0] = 0;
500        //log("deserialize:",argc, rc);
501        metrics.s11n.deserialize.time += performance.now() - t;
502        return rc;
503      };
504
505      /**
506         Serializes all arguments to the shared buffer for consumption
507         by the counterpart thread.
508
509         This routine is only intended for serializing OPFS VFS
510         arguments and (in at least one special case) result values,
511         and the buffer is sized to be able to comfortably handle
512         those.
513
514         If passed no arguments then it zeroes out the serialization
515         state.
516      */
517      state.s11n.serialize = function(...args){
518        const t = performance.now();
519        ++metrics.s11n.serialize.count;
520        if(args.length){
521          //log("serialize():",args);
522          const typeIds = [];
523          let i = 0, offset = 1;
524          viewU8[0] = args.length & 0xff /* header = # of args */;
525          for(; i < args.length; ++i, ++offset){
526            /* Write the TypeIds.id value into the next args.length
527               bytes. */
528            typeIds.push(getTypeId(args[i]));
529            viewU8[offset] = typeIds[i].id;
530          }
531          for(i = 0; i < args.length; ++i) {
532            /* Deserialize the following bytes based on their
533               corresponding TypeIds.id from the header. */
534            const t = typeIds[i];
535            if(t.setter){
536              viewDV[t.setter](offset, args[i], state.littleEndian);
537              offset += t.size;
538            }else{/*String*/
539              const s = textEncoder.encode(args[i]);
540              viewDV.setInt32(offset, s.byteLength, state.littleEndian);
541              offset += 4;
542              viewU8.set(s, offset);
543              offset += s.byteLength;
544            }
545          }
546          //log("serialize() result:",viewU8.slice(0,offset));
547        }else{
548          viewU8[0] = 0;
549        }
550        metrics.s11n.serialize.time += performance.now() - t;
551      };
552      return state.s11n;
553    }/*initS11n()*/;
554
555    /**
556       Generates a random ASCII string len characters long, intended for
557       use as a temporary file name.
558    */
559    const randomFilename = function f(len=16){
560      if(!f._chars){
561        f._chars = "abcdefghijklmnopqrstuvwxyz"+
562          "ABCDEFGHIJKLMNOPQRSTUVWXYZ"+
563          "012346789";
564        f._n = f._chars.length;
565      }
566      const a = [];
567      let i = 0;
568      for( ; i < len; ++i){
569        const ndx = Math.random() * (f._n * 64) % f._n | 0;
570        a[i] = f._chars[ndx];
571      }
572      return a.join('');
573    };
574
575    /**
576       Map of sqlite3_file pointers to objects constructed by xOpen().
577    */
578    const __openFiles = Object.create(null);
579
580    /**
581       Installs a StructBinder-bound function pointer member of the
582       given name and function in the given StructType target object.
583       It creates a WASM proxy for the given function and arranges for
584       that proxy to be cleaned up when tgt.dispose() is called.  Throws
585       on the slightest hint of error (e.g. tgt is-not-a StructType,
586       name does not map to a struct-bound member, etc.).
587
588       Returns a proxy for this function which is bound to tgt and takes
589       2 args (name,func). That function returns the same thing,
590       permitting calls to be chained.
591
592       If called with only 1 arg, it has no side effects but returns a
593       func with the same signature as described above.
594    */
595    const installMethod = function callee(tgt, name, func){
596      if(!(tgt instanceof sqlite3.StructBinder.StructType)){
597        toss("Usage error: target object is-not-a StructType.");
598      }
599      if(1===arguments.length){
600        return (n,f)=>callee(tgt,n,f);
601      }
602      if(!callee.argcProxy){
603        callee.argcProxy = function(func,sig){
604          return function(...args){
605            if(func.length!==arguments.length){
606              toss("Argument mismatch. Native signature is:",sig);
607            }
608            return func.apply(this, args);
609          }
610        };
611        callee.removeFuncList = function(){
612          if(this.ondispose.__removeFuncList){
613            this.ondispose.__removeFuncList.forEach(
614              (v,ndx)=>{
615                if('number'===typeof v){
616                  try{wasm.uninstallFunction(v)}
617                  catch(e){/*ignore*/}
618                }
619                /* else it's a descriptive label for the next number in
620                   the list. */
621              }
622            );
623            delete this.ondispose.__removeFuncList;
624          }
625        };
626      }/*static init*/
627      const sigN = tgt.memberSignature(name);
628      if(sigN.length<2){
629        toss("Member",name," is not a function pointer. Signature =",sigN);
630      }
631      const memKey = tgt.memberKey(name);
632      const fProxy = 0
633      /** This middle-man proxy is only for use during development, to
634          confirm that we always pass the proper number of
635          arguments. We know that the C-level code will always use the
636          correct argument count. */
637            ? callee.argcProxy(func, sigN)
638            : func;
639      const pFunc = wasm.installFunction(fProxy, tgt.memberSignature(name, true));
640      tgt[memKey] = pFunc;
641      if(!tgt.ondispose) tgt.ondispose = [];
642      if(!tgt.ondispose.__removeFuncList){
643        tgt.ondispose.push('ondispose.__removeFuncList handler',
644                           callee.removeFuncList);
645        tgt.ondispose.__removeFuncList = [];
646      }
647      tgt.ondispose.__removeFuncList.push(memKey, pFunc);
648      return (n,f)=>callee(tgt, n, f);
649    }/*installMethod*/;
650
651    const opTimer = Object.create(null);
652    opTimer.op = undefined;
653    opTimer.start = undefined;
654    const mTimeStart = (op)=>{
655      opTimer.start = performance.now();
656      opTimer.op = op;
657      ++metrics[op].count;
658    };
659    const mTimeEnd = ()=>(
660      metrics[opTimer.op].time += performance.now() - opTimer.start
661    );
662
663    /**
664       Impls for the sqlite3_io_methods methods. Maintenance reminder:
665       members are in alphabetical order to simplify finding them.
666    */
667    const ioSyncWrappers = {
668      xCheckReservedLock: function(pFile,pOut){
669        /**
670           As of late 2022, only a single lock can be held on an OPFS
671           file. We have no way of checking whether any _other_ db
672           connection has a lock except by trying to obtain and (on
673           success) release a sync-handle for it, but doing so would
674           involve an inherent race condition. For the time being,
675           pending a better solution, we simply report whether the
676           given pFile instance has a lock.
677        */
678        const f = __openFiles[pFile];
679        wasm.setMemValue(pOut, f.lockMode ? 1 : 0, 'i32');
680        return 0;
681      },
682      xClose: function(pFile){
683        mTimeStart('xClose');
684        let rc = 0;
685        const f = __openFiles[pFile];
686        if(f){
687          delete __openFiles[pFile];
688          rc = opRun('xClose', pFile);
689          if(f.sq3File) f.sq3File.dispose();
690        }
691        mTimeEnd();
692        return rc;
693      },
694      xDeviceCharacteristics: function(pFile){
695        //debug("xDeviceCharacteristics(",pFile,")");
696        return capi.SQLITE_IOCAP_UNDELETABLE_WHEN_OPEN;
697      },
698      xFileControl: function(pFile, opId, pArg){
699        mTimeStart('xFileControl');
700        const rc = (capi.SQLITE_FCNTL_SYNC===opId)
701              ? opRun('xSync', pFile, 0)
702              : capi.SQLITE_NOTFOUND;
703        mTimeEnd();
704        return rc;
705      },
706      xFileSize: function(pFile,pSz64){
707        mTimeStart('xFileSize');
708        const rc = opRun('xFileSize', pFile);
709        if(0==rc){
710          const sz = state.s11n.deserialize()[0];
711          wasm.setMemValue(pSz64, sz, 'i64');
712        }
713        mTimeEnd();
714        return rc;
715      },
716      xLock: function(pFile,lockType){
717        mTimeStart('xLock');
718        const f = __openFiles[pFile];
719        let rc = 0;
720        if( capi.SQLITE_LOCK_NONE === f.lockType ) {
721          rc = opRun('xLock', pFile, lockType);
722          if( 0===rc ) f.lockType = lockType;
723        }else{
724          f.lockType = lockType;
725        }
726        mTimeEnd();
727        return rc;
728      },
729      xRead: function(pFile,pDest,n,offset64){
730        mTimeStart('xRead');
731        const f = __openFiles[pFile];
732        let rc;
733        try {
734          rc = opRun('xRead',pFile, n, Number(offset64));
735          if(0===rc || capi.SQLITE_IOERR_SHORT_READ===rc){
736            /**
737               Results get written to the SharedArrayBuffer f.sabView.
738               Because the heap is _not_ a SharedArrayBuffer, we have
739               to copy the results. TypedArray.set() seems to be the
740               fastest way to copy this. */
741            wasm.heap8u().set(f.sabView.subarray(0, n), pDest);
742          }
743        }catch(e){
744          error("xRead(",arguments,") failed:",e,f);
745          rc = capi.SQLITE_IOERR_READ;
746        }
747        mTimeEnd();
748        return rc;
749      },
750      xSync: function(pFile,flags){
751        ++metrics.xSync.count;
752        return 0; // impl'd in xFileControl()
753      },
754      xTruncate: function(pFile,sz64){
755        mTimeStart('xTruncate');
756        const rc = opRun('xTruncate', pFile, Number(sz64));
757        mTimeEnd();
758        return rc;
759      },
760      xUnlock: function(pFile,lockType){
761        mTimeStart('xUnlock');
762        const f = __openFiles[pFile];
763        let rc = 0;
764        if( capi.SQLITE_LOCK_NONE === lockType
765          && f.lockType ){
766          rc = opRun('xUnlock', pFile, lockType);
767        }
768        if( 0===rc ) f.lockType = lockType;
769        mTimeEnd();
770        return rc;
771      },
772      xWrite: function(pFile,pSrc,n,offset64){
773        mTimeStart('xWrite');
774        const f = __openFiles[pFile];
775        let rc;
776        try {
777          f.sabView.set(wasm.heap8u().subarray(pSrc, pSrc+n));
778          rc = opRun('xWrite', pFile, n, Number(offset64));
779        }catch(e){
780          error("xWrite(",arguments,") failed:",e,f);
781          rc = capi.SQLITE_IOERR_WRITE;
782        }
783        mTimeEnd();
784        return rc;
785      }
786    }/*ioSyncWrappers*/;
787
788    /**
789       Impls for the sqlite3_vfs methods. Maintenance reminder: members
790       are in alphabetical order to simplify finding them.
791    */
792    const vfsSyncWrappers = {
793      xAccess: function(pVfs,zName,flags,pOut){
794        mTimeStart('xAccess');
795        const rc = opRun('xAccess', wasm.cstringToJs(zName));
796        wasm.setMemValue( pOut, (rc ? 0 : 1), 'i32' );
797        mTimeEnd();
798        return 0;
799      },
800      xCurrentTime: function(pVfs,pOut){
801        /* If it turns out that we need to adjust for timezone, see:
802           https://stackoverflow.com/a/11760121/1458521 */
803        wasm.setMemValue(pOut, 2440587.5 + (new Date().getTime()/86400000),
804                         'double');
805        return 0;
806      },
807      xCurrentTimeInt64: function(pVfs,pOut){
808        // TODO: confirm that this calculation is correct
809        wasm.setMemValue(pOut, (2440587.5 * 86400000) + new Date().getTime(),
810                         'i64');
811        return 0;
812      },
813      xDelete: function(pVfs, zName, doSyncDir){
814        mTimeStart('xDelete');
815        opRun('xDelete', wasm.cstringToJs(zName), doSyncDir, false);
816        /* We're ignoring errors because we cannot yet differentiate
817           between harmless and non-harmless failures. */
818        mTimeEnd();
819        return 0;
820      },
821      xFullPathname: function(pVfs,zName,nOut,pOut){
822        /* Until/unless we have some notion of "current dir"
823           in OPFS, simply copy zName to pOut... */
824        const i = wasm.cstrncpy(pOut, zName, nOut);
825        return i<nOut ? 0 : capi.SQLITE_CANTOPEN
826        /*CANTOPEN is required by the docs but SQLITE_RANGE would be a closer match*/;
827      },
828      xGetLastError: function(pVfs,nOut,pOut){
829        /* TODO: store exception.message values from the async
830           partner in a dedicated SharedArrayBuffer, noting that we'd have
831           to encode them... TextEncoder can do that for us. */
832        warn("OPFS xGetLastError() has nothing sensible to return.");
833        return 0;
834      },
835      //xSleep is optionally defined below
836      xOpen: function f(pVfs, zName, pFile, flags, pOutFlags){
837        mTimeStart('xOpen');
838        if(0===zName){
839          zName = randomFilename();
840        }else if('number'===typeof zName){
841          zName = wasm.cstringToJs(zName);
842        }
843        const fh = Object.create(null);
844        fh.fid = pFile;
845        fh.filename = zName;
846        fh.sab = new SharedArrayBuffer(state.fileBufferSize);
847        fh.flags = flags;
848        const rc = opRun('xOpen', pFile, zName, flags);
849        if(!rc){
850          /* Recall that sqlite3_vfs::xClose() will be called, even on
851             error, unless pFile->pMethods is NULL. */
852          if(fh.readOnly){
853            wasm.setMemValue(pOutFlags, capi.SQLITE_OPEN_READONLY, 'i32');
854          }
855          __openFiles[pFile] = fh;
856          fh.sabView = state.sabFileBufView;
857          fh.sq3File = new sqlite3_file(pFile);
858          fh.sq3File.$pMethods = opfsIoMethods.pointer;
859          fh.lockType = capi.SQLITE_LOCK_NONE;
860        }
861        mTimeEnd();
862        return rc;
863      }/*xOpen()*/
864    }/*vfsSyncWrappers*/;
865
866    if(dVfs){
867      opfsVfs.$xRandomness = dVfs.$xRandomness;
868      opfsVfs.$xSleep = dVfs.$xSleep;
869    }
870    if(!opfsVfs.$xRandomness){
871      /* If the default VFS has no xRandomness(), add a basic JS impl... */
872      vfsSyncWrappers.xRandomness = function(pVfs, nOut, pOut){
873        const heap = wasm.heap8u();
874        let i = 0;
875        for(; i < nOut; ++i) heap[pOut + i] = (Math.random()*255000) & 0xFF;
876        return i;
877      };
878    }
879    if(!opfsVfs.$xSleep){
880      /* If we can inherit an xSleep() impl from the default VFS then
881         assume it's sane and use it, otherwise install a JS-based
882         one. */
883      vfsSyncWrappers.xSleep = function(pVfs,ms){
884        Atomics.wait(state.sabOPView, state.opIds.xSleep, 0, ms);
885        return 0;
886      };
887    }
888
889    /* Install the vfs/io_methods into their C-level shared instances... */
890    for(let k of Object.keys(ioSyncWrappers)){
891      installMethod(opfsIoMethods, k, ioSyncWrappers[k]);
892    }
893    for(let k of Object.keys(vfsSyncWrappers)){
894      installMethod(opfsVfs, k, vfsSyncWrappers[k]);
895    }
896
897    /**
898       Expects an OPFS file path. It gets resolved, such that ".."
899       components are properly expanded, and returned. If the 2nd arg
900       is true, the result is returned as an array of path elements,
901       else an absolute path string is returned.
902    */
903    opfsUtil.getResolvedPath = function(filename,splitIt){
904      const p = new URL(filename, "file://irrelevant").pathname;
905      return splitIt ? p.split('/').filter((v)=>!!v) : p;
906    };
907
908    /**
909       Takes the absolute path to a filesystem element. Returns an
910       array of [handleOfContainingDir, filename]. If the 2nd argument
911       is truthy then each directory element leading to the file is
912       created along the way. Throws if any creation or resolution
913       fails.
914    */
915    opfsUtil.getDirForFilename = async function f(absFilename, createDirs = false){
916      const path = opfsUtil.getResolvedPath(absFilename, true);
917      const filename = path.pop();
918      let dh = opfsUtil.rootDirectory;
919      for(const dirName of path){
920        if(dirName){
921          dh = await dh.getDirectoryHandle(dirName, {create: !!createDirs});
922        }
923      }
924      return [dh, filename];
925    };
926
927    /**
928       Creates the given directory name, recursively, in
929       the OPFS filesystem. Returns true if it succeeds or the
930       directory already exists, else false.
931    */
932    opfsUtil.mkdir = async function(absDirName){
933      try {
934        await opfsUtil.getDirForFilename(absDirName+"/filepart", true);
935        return true;
936      }catch(e){
937        //console.warn("mkdir(",absDirName,") failed:",e);
938        return false;
939      }
940    };
941    /**
942       Checks whether the given OPFS filesystem entry exists,
943       returning true if it does, false if it doesn't.
944    */
945    opfsUtil.entryExists = async function(fsEntryName){
946      try {
947        const [dh, fn] = await opfsUtil.getDirForFilename(fsEntryName);
948        await dh.getFileHandle(fn);
949        return true;
950      }catch(e){
951        return false;
952      }
953    };
954
955    /**
956       Generates a random ASCII string, intended for use as a
957       temporary file name. Its argument is the length of the string,
958       defaulting to 16.
959    */
960    opfsUtil.randomFilename = randomFilename;
961
962    /**
963       Re-registers the OPFS VFS. This is intended only for odd use
964       cases which have to call sqlite3_shutdown() as part of their
965       initialization process, which will unregister the VFS
966       registered by installOpfsVfs(). If passed a truthy value, the
967       OPFS VFS is registered as the default VFS, else it is not made
968       the default. Returns the result of the the
969       sqlite3_vfs_register() call.
970
971       Design note: the problem of having to re-register things after
972       a shutdown/initialize pair is more general. How to best plug
973       that in to the library is unclear. In particular, we cannot
974       hook in to any C-side calls to sqlite3_initialize(), so we
975       cannot add an after-initialize callback mechanism.
976    */
977    opfsUtil.registerVfs = (asDefault=false)=>{
978      return wasm.exports.sqlite3_vfs_register(
979        opfsVfs.pointer, asDefault ? 1 : 0
980      );
981    };
982
983    /**
984       Returns a promise which resolves to an object which represents
985       all files and directories in the OPFS tree. The top-most object
986       has two properties: `dirs` is an array of directory entries
987       (described below) and `files` is a list of file names for all
988       files in that directory.
989
990       Traversal starts at sqlite3.opfs.rootDirectory.
991
992       Each `dirs` entry is an object in this form:
993
994       ```
995       { name: directoryName,
996         dirs: [...subdirs],
997         files: [...file names]
998       }
999       ```
1000
1001       The `files` and `subdirs` entries are always set but may be
1002       empty arrays.
1003
1004       The returned object has the same structure but its `name` is
1005       an empty string. All returned objects are created with
1006       Object.create(null), so have no prototype.
1007
1008       Design note: the entries do not contain more information,
1009       e.g. file sizes, because getting such info is not only
1010       expensive but is subject to locking-related errors.
1011    */
1012    opfsUtil.treeList = async function(){
1013      const doDir = async function callee(dirHandle,tgt){
1014        tgt.name = dirHandle.name;
1015        tgt.dirs = [];
1016        tgt.files = [];
1017        for await (const handle of dirHandle.values()){
1018          if('directory' === handle.kind){
1019            const subDir = Object.create(null);
1020            tgt.dirs.push(subDir);
1021            await callee(handle, subDir);
1022          }else{
1023            tgt.files.push(handle.name);
1024          }
1025        }
1026      };
1027      const root = Object.create(null);
1028      await doDir(opfsUtil.rootDirectory, root);
1029      return root;
1030    };
1031
1032    /**
1033       Irrevocably deletes _all_ files in the current origin's OPFS.
1034       Obviously, this must be used with great caution. It may throw
1035       an exception if removal of anything fails (e.g. a file is
1036       locked), but the precise conditions under which it will throw
1037       are not documented (so we cannot tell you what they are).
1038    */
1039    opfsUtil.rmfr = async function(){
1040      const dir = opfsUtil.rootDirectory, opt = {recurse: true};
1041      for await (const handle of dir.values()){
1042        dir.removeEntry(handle.name, opt);
1043      }
1044    };
1045
1046    /**
1047       Deletes the given OPFS filesystem entry.  As this environment
1048       has no notion of "current directory", the given name must be an
1049       absolute path. If the 2nd argument is truthy, deletion is
1050       recursive (use with caution!).
1051
1052       The returned Promise resolves to true if the deletion was
1053       successful, else false (but...). The OPFS API reports the
1054       reason for the failure only in human-readable form, not
1055       exceptions which can be type-checked to determine the
1056       failure. Because of that...
1057
1058       If the final argument is truthy then this function will
1059       propagate any exception on error, rather than returning false.
1060    */
1061    opfsUtil.unlink = async function(fsEntryName, recursive = false,
1062                                          throwOnError = false){
1063      try {
1064        const [hDir, filenamePart] =
1065              await opfsUtil.getDirForFilename(fsEntryName, false);
1066        await hDir.removeEntry(filenamePart, {recursive});
1067        return true;
1068      }catch(e){
1069        if(throwOnError){
1070          throw new Error("unlink(",arguments[0],") failed: "+e.message,{
1071            cause: e
1072          });
1073        }
1074        return false;
1075      }
1076    };
1077
1078    /**
1079       Traverses the OPFS filesystem, calling a callback for each one.
1080       The argument may be either a callback function or an options object
1081       with any of the following properties:
1082
1083       - `callback`: function which gets called for each filesystem
1084         entry.  It gets passed 3 arguments: 1) the
1085         FileSystemFileHandle or FileSystemDirectoryHandle of each
1086         entry (noting that both are instanceof FileSystemHandle). 2)
1087         the FileSystemDirectoryHandle of the parent directory. 3) the
1088         current depth level, with 0 being at the top of the tree
1089         relative to the starting directory. If the callback returns a
1090         literal false, as opposed to any other falsy value, traversal
1091         stops without an error. Any exceptions it throws are
1092         propagated. Results are undefined if the callback manipulate
1093         the filesystem (e.g. removing or adding entries) because the
1094         how OPFS iterators behave in the face of such changes is
1095         undocumented.
1096
1097       - `recursive` [bool=true]: specifies whether to recurse into
1098         subdirectories or not. Whether recursion is depth-first or
1099         breadth-first is unspecified!
1100
1101       - `directory` [FileSystemDirectoryEntry=sqlite3.opfs.rootDirectory]
1102         specifies the starting directory.
1103
1104       If this function is passed a function, it is assumed to be the
1105       callback.
1106
1107       Returns a promise because it has to (by virtue of being async)
1108       but that promise has no specific meaning: the traversal it
1109       performs is synchronous. The promise must be used to catch any
1110       exceptions propagated by the callback, however.
1111
1112       TODO: add an option which specifies whether to traverse
1113       depth-first or breadth-first. We currently do depth-first but
1114       an incremental file browsing widget would benefit more from
1115       breadth-first.
1116    */
1117    opfsUtil.traverse = async function(opt){
1118      const defaultOpt = {
1119        recursive: true,
1120        directory: opfsUtil.rootDirectory
1121      };
1122      if('function'===typeof opt){
1123        opt = {callback:opt};
1124      }
1125      opt = Object.assign(defaultOpt, opt||{});
1126      const doDir = async function callee(dirHandle, depth){
1127        for await (const handle of dirHandle.values()){
1128          if(false === opt.callback(handle, dirHandle, depth)) return false;
1129          else if(opt.recursive && 'directory' === handle.kind){
1130            if(false === await callee(handle, depth + 1)) break;
1131          }
1132        }
1133      };
1134      doDir(opt.directory, 0);
1135    };
1136
1137    //TODO to support fiddle and worker1 db upload:
1138    //opfsUtil.createFile = function(absName, content=undefined){...}
1139
1140    if(sqlite3.oo1){
1141      opfsUtil.OpfsDb = function(...args){
1142        const opt = sqlite3.oo1.DB.dbCtorHelper.normalizeArgs(...args);
1143        opt.vfs = opfsVfs.$zName;
1144        sqlite3.oo1.DB.dbCtorHelper.call(this, opt);
1145      };
1146      opfsUtil.OpfsDb.prototype = Object.create(sqlite3.oo1.DB.prototype);
1147      sqlite3.oo1.DB.dbCtorHelper.setVfsPostOpenSql(
1148        opfsVfs.pointer,
1149        [
1150          /* Truncate journal mode is faster than delete or wal for
1151             this vfs, per speedtest1. */
1152          "pragma journal_mode=truncate;"
1153          /*
1154            This vfs benefits hugely from cache on moderate/large
1155            speedtest1 --size 50 and --size 100 workloads. We currently
1156            rely on setting a non-default cache size when building
1157            sqlite3.wasm. If that policy changes, the cache can
1158            be set here.
1159          */
1160          //"pragma cache_size=-8388608;"
1161        ].join('')
1162      );
1163    }
1164
1165    /**
1166       Potential TODOs:
1167
1168       - Expose one or both of the Worker objects via opfsUtil and
1169         publish an interface for proxying the higher-level OPFS
1170         features like getting a directory listing.
1171    */
1172    const sanityCheck = function(){
1173      const scope = wasm.scopedAllocPush();
1174      const sq3File = new sqlite3_file();
1175      try{
1176        const fid = sq3File.pointer;
1177        const openFlags = capi.SQLITE_OPEN_CREATE
1178              | capi.SQLITE_OPEN_READWRITE
1179        //| capi.SQLITE_OPEN_DELETEONCLOSE
1180              | capi.SQLITE_OPEN_MAIN_DB;
1181        const pOut = wasm.scopedAlloc(8);
1182        const dbFile = "/sanity/check/file"+randomFilename(8);
1183        const zDbFile = wasm.scopedAllocCString(dbFile);
1184        let rc;
1185        state.s11n.serialize("This is ä string.");
1186        rc = state.s11n.deserialize();
1187        log("deserialize() says:",rc);
1188        if("This is ä string."!==rc[0]) toss("String d13n error.");
1189        vfsSyncWrappers.xAccess(opfsVfs.pointer, zDbFile, 0, pOut);
1190        rc = wasm.getMemValue(pOut,'i32');
1191        log("xAccess(",dbFile,") exists ?=",rc);
1192        rc = vfsSyncWrappers.xOpen(opfsVfs.pointer, zDbFile,
1193                                   fid, openFlags, pOut);
1194        log("open rc =",rc,"state.sabOPView[xOpen] =",
1195            state.sabOPView[state.opIds.xOpen]);
1196        if(0!==rc){
1197          error("open failed with code",rc);
1198          return;
1199        }
1200        vfsSyncWrappers.xAccess(opfsVfs.pointer, zDbFile, 0, pOut);
1201        rc = wasm.getMemValue(pOut,'i32');
1202        if(!rc) toss("xAccess() failed to detect file.");
1203        rc = ioSyncWrappers.xSync(sq3File.pointer, 0);
1204        if(rc) toss('sync failed w/ rc',rc);
1205        rc = ioSyncWrappers.xTruncate(sq3File.pointer, 1024);
1206        if(rc) toss('truncate failed w/ rc',rc);
1207        wasm.setMemValue(pOut,0,'i64');
1208        rc = ioSyncWrappers.xFileSize(sq3File.pointer, pOut);
1209        if(rc) toss('xFileSize failed w/ rc',rc);
1210        log("xFileSize says:",wasm.getMemValue(pOut, 'i64'));
1211        rc = ioSyncWrappers.xWrite(sq3File.pointer, zDbFile, 10, 1);
1212        if(rc) toss("xWrite() failed!");
1213        const readBuf = wasm.scopedAlloc(16);
1214        rc = ioSyncWrappers.xRead(sq3File.pointer, readBuf, 6, 2);
1215        wasm.setMemValue(readBuf+6,0);
1216        let jRead = wasm.cstringToJs(readBuf);
1217        log("xRead() got:",jRead);
1218        if("sanity"!==jRead) toss("Unexpected xRead() value.");
1219        if(vfsSyncWrappers.xSleep){
1220          log("xSleep()ing before close()ing...");
1221          vfsSyncWrappers.xSleep(opfsVfs.pointer,2000);
1222          log("waking up from xSleep()");
1223        }
1224        rc = ioSyncWrappers.xClose(fid);
1225        log("xClose rc =",rc,"sabOPView =",state.sabOPView);
1226        log("Deleting file:",dbFile);
1227        vfsSyncWrappers.xDelete(opfsVfs.pointer, zDbFile, 0x1234);
1228        vfsSyncWrappers.xAccess(opfsVfs.pointer, zDbFile, 0, pOut);
1229        rc = wasm.getMemValue(pOut,'i32');
1230        if(rc) toss("Expecting 0 from xAccess(",dbFile,") after xDelete().");
1231        warn("End of OPFS sanity checks.");
1232      }finally{
1233        sq3File.dispose();
1234        wasm.scopedAllocPop(scope);
1235      }
1236    }/*sanityCheck()*/;
1237
1238    W.onmessage = function({data}){
1239      //log("Worker.onmessage:",data);
1240      switch(data.type){
1241          case 'opfs-async-loaded':
1242            /*Arrives as soon as the asyc proxy finishes loading.
1243              Pass our config and shared state on to the async worker.*/
1244            W.postMessage({type: 'opfs-async-init',args: state});
1245            break;
1246          case 'opfs-async-inited':{
1247            /*Indicates that the async partner has received the 'init'
1248              and has finished initializing, so the real work can
1249              begin...*/
1250            try {
1251              const rc = capi.sqlite3_vfs_register(opfsVfs.pointer, 0);
1252              if(rc){
1253                toss("sqlite3_vfs_register(OPFS) failed with rc",rc);
1254              }
1255              if(opfsVfs.pointer !== capi.sqlite3_vfs_find("opfs")){
1256                toss("BUG: sqlite3_vfs_find() failed for just-installed OPFS VFS");
1257              }
1258              capi.sqlite3_vfs_register.addReference(opfsVfs, opfsIoMethods);
1259              state.sabOPView = new Int32Array(state.sabOP);
1260              state.sabFileBufView = new Uint8Array(state.sabIO, 0, state.fileBufferSize);
1261              state.sabS11nView = new Uint8Array(state.sabIO, state.sabS11nOffset, state.sabS11nSize);
1262              initS11n();
1263              if(options.sanityChecks){
1264                warn("Running sanity checks because of opfs-sanity-check URL arg...");
1265                sanityCheck();
1266              }
1267              navigator.storage.getDirectory().then((d)=>{
1268                W.onerror = W._originalOnError;
1269                delete W._originalOnError;
1270                sqlite3.opfs = opfsUtil;
1271                opfsUtil.rootDirectory = d;
1272                log("End of OPFS sqlite3_vfs setup.", opfsVfs);
1273                promiseResolve(sqlite3);
1274              });
1275            }catch(e){
1276              error(e);
1277              promiseReject(e);
1278            }
1279            break;
1280          }
1281          default:
1282            promiseReject(e);
1283            error("Unexpected message from the async worker:",data);
1284            break;
1285      }/*switch(data.type)*/
1286    }/*W.onmessage()*/;
1287  })/*thePromise*/;
1288  return thePromise;
1289}/*installOpfsVfs()*/;
1290installOpfsVfs.defaultProxyUri =
1291  "sqlite3-opfs-async-proxy.js";
1292self.sqlite3ApiBootstrap.initializersAsync.push(async (sqlite3)=>{
1293  if(sqlite3.scriptInfo && !sqlite3.scriptInfo.isWorker){
1294    return;
1295  }
1296  try{
1297    let proxyJs = installOpfsVfs.defaultProxyUri;
1298    if(sqlite3.scriptInfo.sqlite3Dir){
1299      installOpfsVfs.defaultProxyUri =
1300        sqlite3.scriptInfo.sqlite3Dir + proxyJs;
1301      //console.warn("installOpfsVfs.defaultProxyUri =",installOpfsVfs.defaultProxyUri);
1302    }
1303    return installOpfsVfs().catch((e)=>{
1304      console.warn("Ignoring inability to install OPFS sqlite3_vfs:",e.message);
1305    });
1306  }catch(e){
1307    console.error("installOpfsVfs() exception:",e);
1308    throw e;
1309  }
1310});
1311}/*sqlite3ApiBootstrap.initializers.push()*/);
1312