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