Snapshot of upstream SQLite 3.46.1
[sqlcipher.git] / ext / wasm / api / sqlite3-vfs-opfs.c-pp.js
blob5b74e7863f40a427c4ff648be23657fab5efec77
1 //#ifnot target=node
2 /*
3   2022-09-18
5   The author disclaims copyright to this source code.  In place of a
6   legal notice, here is a blessing:
8   *   May you do good and not evil.
9   *   May you find forgiveness for yourself and forgive others.
10   *   May you share freely, never taking more than you give.
12   ***********************************************************************
14   This file holds the synchronous half of an sqlite3_vfs
15   implementation which proxies, in a synchronous fashion, the
16   asynchronous Origin-Private FileSystem (OPFS) APIs using a second
17   Worker, implemented in sqlite3-opfs-async-proxy.js.  This file is
18   intended to be appended to the main sqlite3 JS deliverable somewhere
19   after sqlite3-api-oo1.js and before sqlite3-api-cleanup.js.
21 'use strict';
22 globalThis.sqlite3ApiBootstrap.initializers.push(function(sqlite3){
23 /**
24    installOpfsVfs() returns a Promise which, on success, installs an
25    sqlite3_vfs named "opfs", suitable for use with all sqlite3 APIs
26    which accept a VFS. It is intended to be called via
27    sqlite3ApiBootstrap.initializers or an equivalent mechanism.
29    The installed VFS uses the Origin-Private FileSystem API for
30    all file storage. On error it is rejected with an exception
31    explaining the problem. Reasons for rejection include, but are
32    not limited to:
34    - The counterpart Worker (see below) could not be loaded.
36    - The environment does not support OPFS. That includes when
37      this function is called from the main window thread.
39   Significant notes and limitations:
41   - As of this writing, OPFS is still very much in flux and only
42     available in bleeding-edge versions of Chrome (v102+, noting that
43     that number will increase as the OPFS API matures).
45   - The OPFS features used here are only available in dedicated Worker
46     threads. This file tries to detect that case, resulting in a
47     rejected Promise if those features do not seem to be available.
49   - It requires the SharedArrayBuffer and Atomics classes, and the
50     former is only available if the HTTP server emits the so-called
51     COOP and COEP response headers. These features are required for
52     proxying OPFS's synchronous API via the synchronous interface
53     required by the sqlite3_vfs API.
55   - This function may only be called a single time. When called, this
56     function removes itself from the sqlite3 object.
58   All arguments to this function are for internal/development purposes
59   only. They do not constitute a public API and may change at any
60   time.
62   The argument may optionally be a plain object with the following
63   configuration options:
65   - proxyUri: as described above
67   - verbose (=2): an integer 0-3. 0 disables all logging, 1 enables
68     logging of errors. 2 enables logging of warnings and errors. 3
69     additionally enables debugging info.
71   - sanityChecks (=false): if true, some basic sanity tests are
72     run on the OPFS VFS API after it's initialized, before the
73     returned Promise resolves.
75   On success, the Promise resolves to the top-most sqlite3 namespace
76   object and that object gets a new object installed in its
77   `opfs` property, containing several OPFS-specific utilities.
79 const installOpfsVfs = function callee(options){
80   if(!globalThis.SharedArrayBuffer
81     || !globalThis.Atomics){
82     return Promise.reject(
83       new Error("Cannot install OPFS: Missing SharedArrayBuffer and/or Atomics. "+
84                 "The server must emit the COOP/COEP response headers to enable those. "+
85                 "See https://sqlite.org/wasm/doc/trunk/persistence.md#coop-coep")
86     );
87   }else if('undefined'===typeof WorkerGlobalScope){
88     return Promise.reject(
89       new Error("The OPFS sqlite3_vfs cannot run in the main thread "+
90                 "because it requires Atomics.wait().")
91     );
92   }else if(!globalThis.FileSystemHandle ||
93            !globalThis.FileSystemDirectoryHandle ||
94            !globalThis.FileSystemFileHandle ||
95            !globalThis.FileSystemFileHandle.prototype.createSyncAccessHandle ||
96            !navigator?.storage?.getDirectory){
97     return Promise.reject(
98       new Error("Missing required OPFS APIs.")
99     );
100   }
101   if(!options || 'object'!==typeof options){
102     options = Object.create(null);
103   }
104   const urlParams = new URL(globalThis.location.href).searchParams;
105   if(urlParams.has('opfs-disable')){
106     //sqlite3.config.warn('Explicitly not installing "opfs" VFS due to opfs-disable flag.');
107     return Promise.resolve(sqlite3);
108   }
109   if(undefined===options.verbose){
110     options.verbose = urlParams.has('opfs-verbose')
111       ? (+urlParams.get('opfs-verbose') || 2) : 1;
112   }
113   if(undefined===options.sanityChecks){
114     options.sanityChecks = urlParams.has('opfs-sanity-check');
115   }
116   if(undefined===options.proxyUri){
117     options.proxyUri = callee.defaultProxyUri;
118   }
120   //sqlite3.config.warn("OPFS options =",options,globalThis.location);
122   if('function' === typeof options.proxyUri){
123     options.proxyUri = options.proxyUri();
124   }
125   const thePromise = new Promise(function(promiseResolve_, promiseReject_){
126     const loggers = [
127       sqlite3.config.error,
128       sqlite3.config.warn,
129       sqlite3.config.log
130     ];
131     const logImpl = (level,...args)=>{
132       if(options.verbose>level) loggers[level]("OPFS syncer:",...args);
133     };
134     const log =    (...args)=>logImpl(2, ...args);
135     const warn =   (...args)=>logImpl(1, ...args);
136     const error =  (...args)=>logImpl(0, ...args);
137     const toss = sqlite3.util.toss;
138     const capi = sqlite3.capi;
139     const util = sqlite3.util;
140     const wasm = sqlite3.wasm;
141     const sqlite3_vfs = capi.sqlite3_vfs;
142     const sqlite3_file = capi.sqlite3_file;
143     const sqlite3_io_methods = capi.sqlite3_io_methods;
144     /**
145        Generic utilities for working with OPFS. This will get filled out
146        by the Promise setup and, on success, installed as sqlite3.opfs.
148        ACHTUNG: do not rely on these APIs in client code. They are
149        experimental and subject to change or removal as the
150        OPFS-specific sqlite3_vfs evolves.
151     */
152     const opfsUtil = Object.create(null);
154     /**
155        Returns true if _this_ thread has access to the OPFS APIs.
156     */
157     const thisThreadHasOPFS = ()=>{
158       return globalThis.FileSystemHandle &&
159         globalThis.FileSystemDirectoryHandle &&
160         globalThis.FileSystemFileHandle &&
161         globalThis.FileSystemFileHandle.prototype.createSyncAccessHandle &&
162         navigator?.storage?.getDirectory;
163     };
165     /**
166        Not part of the public API. Solely for internal/development
167        use.
168     */
169     opfsUtil.metrics = {
170       dump: function(){
171         let k, n = 0, t = 0, w = 0;
172         for(k in state.opIds){
173           const m = metrics[k];
174           n += m.count;
175           t += m.time;
176           w += m.wait;
177           m.avgTime = (m.count && m.time) ? (m.time / m.count) : 0;
178           m.avgWait = (m.count && m.wait) ? (m.wait / m.count) : 0;
179         }
180         sqlite3.config.log(globalThis.location.href,
181                     "metrics for",globalThis.location.href,":",metrics,
182                     "\nTotal of",n,"op(s) for",t,
183                     "ms (incl. "+w+" ms of waiting on the async side)");
184         sqlite3.config.log("Serialization metrics:",metrics.s11n);
185         W.postMessage({type:'opfs-async-metrics'});
186       },
187       reset: function(){
188         let k;
189         const r = (m)=>(m.count = m.time = m.wait = 0);
190         for(k in state.opIds){
191           r(metrics[k] = Object.create(null));
192         }
193         let s = metrics.s11n = Object.create(null);
194         s = s.serialize = Object.create(null);
195         s.count = s.time = 0;
196         s = metrics.s11n.deserialize = Object.create(null);
197         s.count = s.time = 0;
198       }
199     }/*metrics*/;
200     const opfsIoMethods = new sqlite3_io_methods();
201     const opfsVfs = new sqlite3_vfs()
202           .addOnDispose( ()=>opfsIoMethods.dispose());
203     let promiseWasRejected = undefined;
204     const promiseReject = (err)=>{
205       promiseWasRejected = true;
206       opfsVfs.dispose();
207       return promiseReject_(err);
208     };
209     const promiseResolve = ()=>{
210       promiseWasRejected = false;
211       return promiseResolve_(sqlite3);
212     };
213     const W =
214 //#if target=es6-bundler-friendly
215     new Worker(new URL("sqlite3-opfs-async-proxy.js", import.meta.url));
216 //#elif target=es6-module
217     new Worker(new URL(options.proxyUri, import.meta.url));
218 //#else
219     new Worker(options.proxyUri);
220 //#endif
221     setTimeout(()=>{
222       /* At attempt to work around a browser-specific quirk in which
223          the Worker load is failing in such a way that we neither
224          resolve nor reject it. This workaround gives that resolve/reject
225          a time limit and rejects if that timer expires. Discussion:
226          https://sqlite.org/forum/forumpost/a708c98dcb3ef */
227       if(undefined===promiseWasRejected){
228         promiseReject(
229           new Error("Timeout while waiting for OPFS async proxy worker.")
230         );
231       }
232     }, 4000);
233     W._originalOnError = W.onerror /* will be restored later */;
234     W.onerror = function(err){
235       // The error object doesn't contain any useful info when the
236       // failure is, e.g., that the remote script is 404.
237       error("Error initializing OPFS asyncer:",err);
238       promiseReject(new Error("Loading OPFS async Worker failed for unknown reasons."));
239     };
240     const pDVfs = capi.sqlite3_vfs_find(null)/*pointer to default VFS*/;
241     const dVfs = pDVfs
242           ? new sqlite3_vfs(pDVfs)
243           : null /* dVfs will be null when sqlite3 is built with
244                     SQLITE_OS_OTHER. */;
245     opfsIoMethods.$iVersion = 1;
246     opfsVfs.$iVersion = 2/*yes, two*/;
247     opfsVfs.$szOsFile = capi.sqlite3_file.structInfo.sizeof;
248     opfsVfs.$mxPathname = 1024/* sure, why not? The OPFS name length limit
249                                  is undocumented/unspecified. */;
250     opfsVfs.$zName = wasm.allocCString("opfs");
251     // All C-side memory of opfsVfs is zeroed out, but just to be explicit:
252     opfsVfs.$xDlOpen = opfsVfs.$xDlError = opfsVfs.$xDlSym = opfsVfs.$xDlClose = null;
253     opfsVfs.addOnDispose(
254       '$zName', opfsVfs.$zName,
255       'cleanup default VFS wrapper', ()=>(dVfs ? dVfs.dispose() : null)
256     );
257     /**
258        Pedantic sidebar about opfsVfs.ondispose: the entries in that array
259        are items to clean up when opfsVfs.dispose() is called, but in this
260        environment it will never be called. The VFS instance simply
261        hangs around until the WASM module instance is cleaned up. We
262        "could" _hypothetically_ clean it up by "importing" an
263        sqlite3_os_end() impl into the wasm build, but the shutdown order
264        of the wasm engine and the JS one are undefined so there is no
265        guaranty that the opfsVfs instance would be available in one
266        environment or the other when sqlite3_os_end() is called (_if_ it
267        gets called at all in a wasm build, which is undefined).
268     */
269     /**
270        State which we send to the async-api Worker or share with it.
271        This object must initially contain only cloneable or sharable
272        objects. After the worker's "inited" message arrives, other types
273        of data may be added to it.
275        For purposes of Atomics.wait() and Atomics.notify(), we use a
276        SharedArrayBuffer with one slot reserved for each of the API
277        proxy's methods. The sync side of the API uses Atomics.wait()
278        on the corresponding slot and the async side uses
279        Atomics.notify() on that slot.
281        The approach of using a single SAB to serialize comms for all
282        instances might(?) lead to deadlock situations in multi-db
283        cases. We should probably have one SAB here with a single slot
284        for locking a per-file initialization step and then allocate a
285        separate SAB like the above one for each file. That will
286        require a bit of acrobatics but should be feasible. The most
287        problematic part is that xOpen() would have to use
288        postMessage() to communicate its SharedArrayBuffer, and mixing
289        that approach with Atomics.wait/notify() gets a bit messy.
290     */
291     const state = Object.create(null);
292     state.verbose = options.verbose;
293     state.littleEndian = (()=>{
294       const buffer = new ArrayBuffer(2);
295       new DataView(buffer).setInt16(0, 256, true /* ==>littleEndian */);
296       // Int16Array uses the platform's endianness.
297       return new Int16Array(buffer)[0] === 256;
298     })();
299     /**
300        asyncIdleWaitTime is how long (ms) to wait, in the async proxy,
301        for each Atomics.wait() when waiting on inbound VFS API calls.
302        We need to wake up periodically to give the thread a chance to
303        do other things. If this is too high (e.g. 500ms) then even two
304        workers/tabs can easily run into locking errors. Some multiple
305        of this value is also used for determining how long to wait on
306        lock contention to free up.
307     */
308     state.asyncIdleWaitTime = 150;
310     /**
311        Whether the async counterpart should log exceptions to
312        the serialization channel. That produces a great deal of
313        noise for seemingly innocuous things like xAccess() checks
314        for missing files, so this option may have one of 3 values:
316        0 = no exception logging.
318        1 = only log exceptions for "significant" ops like xOpen(),
319        xRead(), and xWrite().
321        2 = log all exceptions.
322     */
323     state.asyncS11nExceptions = 1;
324     /* Size of file I/O buffer block. 64k = max sqlite3 page size, and
325        xRead/xWrite() will never deal in blocks larger than that. */
326     state.fileBufferSize = 1024 * 64;
327     state.sabS11nOffset = state.fileBufferSize;
328     /**
329        The size of the block in our SAB for serializing arguments and
330        result values. Needs to be large enough to hold serialized
331        values of any of the proxied APIs. Filenames are the largest
332        part but are limited to opfsVfs.$mxPathname bytes. We also
333        store exceptions there, so it needs to be long enough to hold
334        a reasonably long exception string.
335     */
336     state.sabS11nSize = opfsVfs.$mxPathname * 2;
337     /**
338        The SAB used for all data I/O between the synchronous and
339        async halves (file i/o and arg/result s11n).
340     */
341     state.sabIO = new SharedArrayBuffer(
342       state.fileBufferSize/* file i/o block */
343       + state.sabS11nSize/* argument/result serialization block */
344     );
345     state.opIds = Object.create(null);
346     const metrics = Object.create(null);
347     {
348       /* Indexes for use in our SharedArrayBuffer... */
349       let i = 0;
350       /* SAB slot used to communicate which operation is desired
351          between both workers. This worker writes to it and the other
352          listens for changes. */
353       state.opIds.whichOp = i++;
354       /* Slot for storing return values. This worker listens to that
355          slot and the other worker writes to it. */
356       state.opIds.rc = i++;
357       /* Each function gets an ID which this worker writes to
358          the whichOp slot. The async-api worker uses Atomic.wait()
359          on the whichOp slot to figure out which operation to run
360          next. */
361       state.opIds.xAccess = i++;
362       state.opIds.xClose = i++;
363       state.opIds.xDelete = i++;
364       state.opIds.xDeleteNoWait = i++;
365       state.opIds.xFileSize = i++;
366       state.opIds.xLock = i++;
367       state.opIds.xOpen = i++;
368       state.opIds.xRead = i++;
369       state.opIds.xSleep = i++;
370       state.opIds.xSync = i++;
371       state.opIds.xTruncate = i++;
372       state.opIds.xUnlock = i++;
373       state.opIds.xWrite = i++;
374       state.opIds.mkdir = i++;
375       state.opIds['opfs-async-metrics'] = i++;
376       state.opIds['opfs-async-shutdown'] = i++;
377       /* The retry slot is used by the async part for wait-and-retry
378          semantics. Though we could hypothetically use the xSleep slot
379          for that, doing so might lead to undesired side effects. */
380       state.opIds.retry = i++;
381       state.sabOP = new SharedArrayBuffer(
382         i * 4/* ==sizeof int32, noting that Atomics.wait() and friends
383                 can only function on Int32Array views of an SAB. */);
384       opfsUtil.metrics.reset();
385     }
386     /**
387        SQLITE_xxx constants to export to the async worker
388        counterpart...
389     */
390     state.sq3Codes = Object.create(null);
391     [
392       'SQLITE_ACCESS_EXISTS',
393       'SQLITE_ACCESS_READWRITE',
394       'SQLITE_BUSY',
395       'SQLITE_CANTOPEN',
396       'SQLITE_ERROR',
397       'SQLITE_IOERR',
398       'SQLITE_IOERR_ACCESS',
399       'SQLITE_IOERR_CLOSE',
400       'SQLITE_IOERR_DELETE',
401       'SQLITE_IOERR_FSYNC',
402       'SQLITE_IOERR_LOCK',
403       'SQLITE_IOERR_READ',
404       'SQLITE_IOERR_SHORT_READ',
405       'SQLITE_IOERR_TRUNCATE',
406       'SQLITE_IOERR_UNLOCK',
407       'SQLITE_IOERR_WRITE',
408       'SQLITE_LOCK_EXCLUSIVE',
409       'SQLITE_LOCK_NONE',
410       'SQLITE_LOCK_PENDING',
411       'SQLITE_LOCK_RESERVED',
412       'SQLITE_LOCK_SHARED',
413       'SQLITE_LOCKED',
414       'SQLITE_MISUSE',
415       'SQLITE_NOTFOUND',
416       'SQLITE_OPEN_CREATE',
417       'SQLITE_OPEN_DELETEONCLOSE',
418       'SQLITE_OPEN_MAIN_DB',
419       'SQLITE_OPEN_READONLY'
420     ].forEach((k)=>{
421       if(undefined === (state.sq3Codes[k] = capi[k])){
422         toss("Maintenance required: not found:",k);
423       }
424     });
425     state.opfsFlags = Object.assign(Object.create(null),{
426       /**
427          Flag for use with xOpen(). URI flag "opfs-unlock-asap=1"
428          enables this. See defaultUnlockAsap, below.
429        */
430       OPFS_UNLOCK_ASAP: 0x01,
431       /**
432          Flag for use with xOpen(). URI flag "delete-before-open=1"
433          tells the VFS to delete the db file before attempting to open
434          it. This can be used, e.g., to replace a db which has been
435          corrupted (without forcing us to expose a delete/unlink()
436          function in the public API).
438          Failure to unlink the file is ignored but may lead to
439          downstream errors.  An unlink can fail if, e.g., another tab
440          has the handle open.
442          It goes without saying that deleting a file out from under another
443          instance results in Undefined Behavior.
444       */
445       OPFS_UNLINK_BEFORE_OPEN: 0x02,
446       /**
447          If true, any async routine which implicitly acquires a sync
448          access handle (i.e. an OPFS lock) will release that lock at
449          the end of the call which acquires it. If false, such
450          "autolocks" are not released until the VFS is idle for some
451          brief amount of time.
453          The benefit of enabling this is much higher concurrency. The
454          down-side is much-reduced performance (as much as a 4x decrease
455          in speedtest1).
456       */
457       defaultUnlockAsap: false
458     });
460     /**
461        Runs the given operation (by name) in the async worker
462        counterpart, waits for its response, and returns the result
463        which the async worker writes to SAB[state.opIds.rc]. The
464        2nd and subsequent arguments must be the aruguments for the
465        async op.
466     */
467     const opRun = (op,...args)=>{
468       const opNdx = state.opIds[op] || toss("Invalid op ID:",op);
469       state.s11n.serialize(...args);
470       Atomics.store(state.sabOPView, state.opIds.rc, -1);
471       Atomics.store(state.sabOPView, state.opIds.whichOp, opNdx);
472       Atomics.notify(state.sabOPView, state.opIds.whichOp)
473       /* async thread will take over here */;
474       const t = performance.now();
475       while('not-equal'!==Atomics.wait(state.sabOPView, state.opIds.rc, -1)){
476         /*
477           The reason for this loop is buried in the details of a long
478           discussion at:
480           https://github.com/sqlite/sqlite-wasm/issues/12
482           Summary: in at least one browser flavor, under high loads,
483           the wait()/notify() pairings can get out of sync. Calling
484           wait() here until it returns 'not-equal' gets them back in
485           sync.
486         */
487       }
488       /* When the above wait() call returns 'not-equal', the async
489          half will have completed the operation and reported its results
490          in the state.opIds.rc slot of the SAB. */
491       const rc = Atomics.load(state.sabOPView, state.opIds.rc);
492       metrics[op].wait += performance.now() - t;
493       if(rc && state.asyncS11nExceptions){
494         const err = state.s11n.deserialize();
495         if(err) error(op+"() async error:",...err);
496       }
497       return rc;
498     };
500     /**
501        Not part of the public API. Only for test/development use.
502     */
503     opfsUtil.debug = {
504       asyncShutdown: ()=>{
505         warn("Shutting down OPFS async listener. The OPFS VFS will no longer work.");
506         opRun('opfs-async-shutdown');
507       },
508       asyncRestart: ()=>{
509         warn("Attempting to restart OPFS VFS async listener. Might work, might not.");
510         W.postMessage({type: 'opfs-async-restart'});
511       }
512     };
514     const initS11n = ()=>{
515       /**
516          !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
517          ACHTUNG: this code is 100% duplicated in the other half of
518          this proxy! The documentation is maintained in the
519          "synchronous half".
520          !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
522          This proxy de/serializes cross-thread function arguments and
523          output-pointer values via the state.sabIO SharedArrayBuffer,
524          using the region defined by (state.sabS11nOffset,
525          state.sabS11nOffset + state.sabS11nSize]. Only one dataset is
526          recorded at a time.
528          This is not a general-purpose format. It only supports the
529          range of operations, and data sizes, needed by the
530          sqlite3_vfs and sqlite3_io_methods operations. Serialized
531          data are transient and this serialization algorithm may
532          change at any time.
534          The data format can be succinctly summarized as:
536          Nt...Td...D
538          Where:
540          - N = number of entries (1 byte)
542          - t = type ID of first argument (1 byte)
544          - ...T = type IDs of the 2nd and subsequent arguments (1 byte
545          each).
547          - d = raw bytes of first argument (per-type size).
549          - ...D = raw bytes of the 2nd and subsequent arguments (per-type
550          size).
552          All types except strings have fixed sizes. Strings are stored
553          using their TextEncoder/TextDecoder representations. It would
554          arguably make more sense to store them as Int16Arrays of
555          their JS character values, but how best/fastest to get that
556          in and out of string form is an open point. Initial
557          experimentation with that approach did not gain us any speed.
559          Historical note: this impl was initially about 1% this size by
560          using using JSON.stringify/parse(), but using fit-to-purpose
561          serialization saves considerable runtime.
562       */
563       if(state.s11n) return state.s11n;
564       const textDecoder = new TextDecoder(),
565             textEncoder = new TextEncoder('utf-8'),
566             viewU8 = new Uint8Array(state.sabIO, state.sabS11nOffset, state.sabS11nSize),
567             viewDV = new DataView(state.sabIO, state.sabS11nOffset, state.sabS11nSize);
568       state.s11n = Object.create(null);
569       /* Only arguments and return values of these types may be
570          serialized. This covers the whole range of types needed by the
571          sqlite3_vfs API. */
572       const TypeIds = Object.create(null);
573       TypeIds.number  = { id: 1, size: 8, getter: 'getFloat64', setter: 'setFloat64' };
574       TypeIds.bigint  = { id: 2, size: 8, getter: 'getBigInt64', setter: 'setBigInt64' };
575       TypeIds.boolean = { id: 3, size: 4, getter: 'getInt32', setter: 'setInt32' };
576       TypeIds.string =  { id: 4 };
578       const getTypeId = (v)=>(
579         TypeIds[typeof v]
580           || toss("Maintenance required: this value type cannot be serialized.",v)
581       );
582       const getTypeIdById = (tid)=>{
583         switch(tid){
584             case TypeIds.number.id: return TypeIds.number;
585             case TypeIds.bigint.id: return TypeIds.bigint;
586             case TypeIds.boolean.id: return TypeIds.boolean;
587             case TypeIds.string.id: return TypeIds.string;
588             default: toss("Invalid type ID:",tid);
589         }
590       };
592       /**
593          Returns an array of the deserialized state stored by the most
594          recent serialize() operation (from from this thread or the
595          counterpart thread), or null if the serialization buffer is
596          empty.  If passed a truthy argument, the serialization buffer
597          is cleared after deserialization.
598       */
599       state.s11n.deserialize = function(clear=false){
600         ++metrics.s11n.deserialize.count;
601         const t = performance.now();
602         const argc = viewU8[0];
603         const rc = argc ? [] : null;
604         if(argc){
605           const typeIds = [];
606           let offset = 1, i, n, v;
607           for(i = 0; i < argc; ++i, ++offset){
608             typeIds.push(getTypeIdById(viewU8[offset]));
609           }
610           for(i = 0; i < argc; ++i){
611             const t = typeIds[i];
612             if(t.getter){
613               v = viewDV[t.getter](offset, state.littleEndian);
614               offset += t.size;
615             }else{/*String*/
616               n = viewDV.getInt32(offset, state.littleEndian);
617               offset += 4;
618               v = textDecoder.decode(viewU8.slice(offset, offset+n));
619               offset += n;
620             }
621             rc.push(v);
622           }
623         }
624         if(clear) viewU8[0] = 0;
625         //log("deserialize:",argc, rc);
626         metrics.s11n.deserialize.time += performance.now() - t;
627         return rc;
628       };
630       /**
631          Serializes all arguments to the shared buffer for consumption
632          by the counterpart thread.
634          This routine is only intended for serializing OPFS VFS
635          arguments and (in at least one special case) result values,
636          and the buffer is sized to be able to comfortably handle
637          those.
639          If passed no arguments then it zeroes out the serialization
640          state.
641       */
642       state.s11n.serialize = function(...args){
643         const t = performance.now();
644         ++metrics.s11n.serialize.count;
645         if(args.length){
646           //log("serialize():",args);
647           const typeIds = [];
648           let i = 0, offset = 1;
649           viewU8[0] = args.length & 0xff /* header = # of args */;
650           for(; i < args.length; ++i, ++offset){
651             /* Write the TypeIds.id value into the next args.length
652                bytes. */
653             typeIds.push(getTypeId(args[i]));
654             viewU8[offset] = typeIds[i].id;
655           }
656           for(i = 0; i < args.length; ++i) {
657             /* Deserialize the following bytes based on their
658                corresponding TypeIds.id from the header. */
659             const t = typeIds[i];
660             if(t.setter){
661               viewDV[t.setter](offset, args[i], state.littleEndian);
662               offset += t.size;
663             }else{/*String*/
664               const s = textEncoder.encode(args[i]);
665               viewDV.setInt32(offset, s.byteLength, state.littleEndian);
666               offset += 4;
667               viewU8.set(s, offset);
668               offset += s.byteLength;
669             }
670           }
671           //log("serialize() result:",viewU8.slice(0,offset));
672         }else{
673           viewU8[0] = 0;
674         }
675         metrics.s11n.serialize.time += performance.now() - t;
676       };
677       return state.s11n;
678     }/*initS11n()*/;
680     /**
681        Generates a random ASCII string len characters long, intended for
682        use as a temporary file name.
683     */
684     const randomFilename = function f(len=16){
685       if(!f._chars){
686         f._chars = "abcdefghijklmnopqrstuvwxyz"+
687           "ABCDEFGHIJKLMNOPQRSTUVWXYZ"+
688           "012346789";
689         f._n = f._chars.length;
690       }
691       const a = [];
692       let i = 0;
693       for( ; i < len; ++i){
694         const ndx = Math.random() * (f._n * 64) % f._n | 0;
695         a[i] = f._chars[ndx];
696       }
697       return a.join("");
698       /*
699         An alternative impl. with an unpredictable length
700         but much simpler:
702         Math.floor(Math.random() * Number.MAX_SAFE_INTEGER).toString(36)
703       */
704     };
706     /**
707        Map of sqlite3_file pointers to objects constructed by xOpen().
708     */
709     const __openFiles = Object.create(null);
711     const opTimer = Object.create(null);
712     opTimer.op = undefined;
713     opTimer.start = undefined;
714     const mTimeStart = (op)=>{
715       opTimer.start = performance.now();
716       opTimer.op = op;
717       ++metrics[op].count;
718     };
719     const mTimeEnd = ()=>(
720       metrics[opTimer.op].time += performance.now() - opTimer.start
721     );
723     /**
724        Impls for the sqlite3_io_methods methods. Maintenance reminder:
725        members are in alphabetical order to simplify finding them.
726     */
727     const ioSyncWrappers = {
728       xCheckReservedLock: function(pFile,pOut){
729         /**
730            As of late 2022, only a single lock can be held on an OPFS
731            file. We have no way of checking whether any _other_ db
732            connection has a lock except by trying to obtain and (on
733            success) release a sync-handle for it, but doing so would
734            involve an inherent race condition. For the time being,
735            pending a better solution, we simply report whether the
736            given pFile is open.
738            Update 2024-06-12: based on forum discussions, this
739            function now always sets pOut to 0 (false):
741            https://sqlite.org/forum/forumpost/a2f573b00cda1372
742         */
743         if(1){
744           wasm.poke(pOut, 0, 'i32');
745         }else{
746           const f = __openFiles[pFile];
747           wasm.poke(pOut, f.lockType ? 1 : 0, 'i32');
748         }
749         return 0;
750       },
751       xClose: function(pFile){
752         mTimeStart('xClose');
753         let rc = 0;
754         const f = __openFiles[pFile];
755         if(f){
756           delete __openFiles[pFile];
757           rc = opRun('xClose', pFile);
758           if(f.sq3File) f.sq3File.dispose();
759         }
760         mTimeEnd();
761         return rc;
762       },
763       xDeviceCharacteristics: function(pFile){
764         return capi.SQLITE_IOCAP_UNDELETABLE_WHEN_OPEN;
765       },
766       xFileControl: function(pFile, opId, pArg){
767         /*mTimeStart('xFileControl');
768           mTimeEnd();*/
769         return capi.SQLITE_NOTFOUND;
770       },
771       xFileSize: function(pFile,pSz64){
772         mTimeStart('xFileSize');
773         let rc = opRun('xFileSize', pFile);
774         if(0==rc){
775           try {
776             const sz = state.s11n.deserialize()[0];
777             wasm.poke(pSz64, sz, 'i64');
778           }catch(e){
779             error("Unexpected error reading xFileSize() result:",e);
780             rc = state.sq3Codes.SQLITE_IOERR;
781           }
782         }
783         mTimeEnd();
784         return rc;
785       },
786       xLock: function(pFile,lockType){
787         mTimeStart('xLock');
788         const f = __openFiles[pFile];
789         let rc = 0;
790         /* All OPFS locks are exclusive locks. If xLock() has
791            previously succeeded, do nothing except record the lock
792            type. If no lock is active, have the async counterpart
793            lock the file. */
794         if( !f.lockType ) {
795           rc = opRun('xLock', pFile, lockType);
796           if( 0===rc ) f.lockType = lockType;
797         }else{
798           f.lockType = lockType;
799         }
800         mTimeEnd();
801         return rc;
802       },
803       xRead: function(pFile,pDest,n,offset64){
804         mTimeStart('xRead');
805         const f = __openFiles[pFile];
806         let rc;
807         try {
808           rc = opRun('xRead',pFile, n, Number(offset64));
809           if(0===rc || capi.SQLITE_IOERR_SHORT_READ===rc){
810             /**
811                Results get written to the SharedArrayBuffer f.sabView.
812                Because the heap is _not_ a SharedArrayBuffer, we have
813                to copy the results. TypedArray.set() seems to be the
814                fastest way to copy this. */
815             wasm.heap8u().set(f.sabView.subarray(0, n), pDest);
816           }
817         }catch(e){
818           error("xRead(",arguments,") failed:",e,f);
819           rc = capi.SQLITE_IOERR_READ;
820         }
821         mTimeEnd();
822         return rc;
823       },
824       xSync: function(pFile,flags){
825         mTimeStart('xSync');
826         ++metrics.xSync.count;
827         const rc = opRun('xSync', pFile, flags);
828         mTimeEnd();
829         return rc;
830       },
831       xTruncate: function(pFile,sz64){
832         mTimeStart('xTruncate');
833         const rc = opRun('xTruncate', pFile, Number(sz64));
834         mTimeEnd();
835         return rc;
836       },
837       xUnlock: function(pFile,lockType){
838         mTimeStart('xUnlock');
839         const f = __openFiles[pFile];
840         let rc = 0;
841         if( capi.SQLITE_LOCK_NONE === lockType
842           && f.lockType ){
843           rc = opRun('xUnlock', pFile, lockType);
844         }
845         if( 0===rc ) f.lockType = lockType;
846         mTimeEnd();
847         return rc;
848       },
849       xWrite: function(pFile,pSrc,n,offset64){
850         mTimeStart('xWrite');
851         const f = __openFiles[pFile];
852         let rc;
853         try {
854           f.sabView.set(wasm.heap8u().subarray(pSrc, pSrc+n));
855           rc = opRun('xWrite', pFile, n, Number(offset64));
856         }catch(e){
857           error("xWrite(",arguments,") failed:",e,f);
858           rc = capi.SQLITE_IOERR_WRITE;
859         }
860         mTimeEnd();
861         return rc;
862       }
863     }/*ioSyncWrappers*/;
865     /**
866        Impls for the sqlite3_vfs methods. Maintenance reminder: members
867        are in alphabetical order to simplify finding them.
868     */
869     const vfsSyncWrappers = {
870       xAccess: function(pVfs,zName,flags,pOut){
871         mTimeStart('xAccess');
872         const rc = opRun('xAccess', wasm.cstrToJs(zName));
873         wasm.poke( pOut, (rc ? 0 : 1), 'i32' );
874         mTimeEnd();
875         return 0;
876       },
877       xCurrentTime: function(pVfs,pOut){
878         /* If it turns out that we need to adjust for timezone, see:
879            https://stackoverflow.com/a/11760121/1458521 */
880         wasm.poke(pOut, 2440587.5 + (new Date().getTime()/86400000),
881                   'double');
882         return 0;
883       },
884       xCurrentTimeInt64: function(pVfs,pOut){
885         wasm.poke(pOut, (2440587.5 * 86400000) + new Date().getTime(),
886                   'i64');
887         return 0;
888       },
889       xDelete: function(pVfs, zName, doSyncDir){
890         mTimeStart('xDelete');
891         const rc = opRun('xDelete', wasm.cstrToJs(zName), doSyncDir, false);
892         mTimeEnd();
893         return rc;
894       },
895       xFullPathname: function(pVfs,zName,nOut,pOut){
896         /* Until/unless we have some notion of "current dir"
897            in OPFS, simply copy zName to pOut... */
898         const i = wasm.cstrncpy(pOut, zName, nOut);
899         return i<nOut ? 0 : capi.SQLITE_CANTOPEN
900         /*CANTOPEN is required by the docs but SQLITE_RANGE would be a closer match*/;
901       },
902       xGetLastError: function(pVfs,nOut,pOut){
903         /* TODO: store exception.message values from the async
904            partner in a dedicated SharedArrayBuffer, noting that we'd have
905            to encode them... TextEncoder can do that for us. */
906         warn("OPFS xGetLastError() has nothing sensible to return.");
907         return 0;
908       },
909       //xSleep is optionally defined below
910       xOpen: function f(pVfs, zName, pFile, flags, pOutFlags){
911         mTimeStart('xOpen');
912         let opfsFlags = 0;
913         if(0===zName){
914           zName = randomFilename();
915         }else if(wasm.isPtr(zName)){
916           if(capi.sqlite3_uri_boolean(zName, "opfs-unlock-asap", 0)){
917             /* -----------------------^^^^^ MUST pass the untranslated
918                C-string here. */
919             opfsFlags |= state.opfsFlags.OPFS_UNLOCK_ASAP;
920           }
921           if(capi.sqlite3_uri_boolean(zName, "delete-before-open", 0)){
922             opfsFlags |= state.opfsFlags.OPFS_UNLINK_BEFORE_OPEN;
923           }
924           zName = wasm.cstrToJs(zName);
925           //warn("xOpen zName =",zName, "opfsFlags =",opfsFlags);
926         }
927         const fh = Object.create(null);
928         fh.fid = pFile;
929         fh.filename = zName;
930         fh.sab = new SharedArrayBuffer(state.fileBufferSize);
931         fh.flags = flags;
932         const rc = opRun('xOpen', pFile, zName, flags, opfsFlags);
933         if(!rc){
934           /* Recall that sqlite3_vfs::xClose() will be called, even on
935              error, unless pFile->pMethods is NULL. */
936           if(fh.readOnly){
937             wasm.poke(pOutFlags, capi.SQLITE_OPEN_READONLY, 'i32');
938           }
939           __openFiles[pFile] = fh;
940           fh.sabView = state.sabFileBufView;
941           fh.sq3File = new sqlite3_file(pFile);
942           fh.sq3File.$pMethods = opfsIoMethods.pointer;
943           fh.lockType = capi.SQLITE_LOCK_NONE;
944         }
945         mTimeEnd();
946         return rc;
947       }/*xOpen()*/
948     }/*vfsSyncWrappers*/;
950     if(dVfs){
951       opfsVfs.$xRandomness = dVfs.$xRandomness;
952       opfsVfs.$xSleep = dVfs.$xSleep;
953     }
954     if(!opfsVfs.$xRandomness){
955       /* If the default VFS has no xRandomness(), add a basic JS impl... */
956       vfsSyncWrappers.xRandomness = function(pVfs, nOut, pOut){
957         const heap = wasm.heap8u();
958         let i = 0;
959         for(; i < nOut; ++i) heap[pOut + i] = (Math.random()*255000) & 0xFF;
960         return i;
961       };
962     }
963     if(!opfsVfs.$xSleep){
964       /* If we can inherit an xSleep() impl from the default VFS then
965          assume it's sane and use it, otherwise install a JS-based
966          one. */
967       vfsSyncWrappers.xSleep = function(pVfs,ms){
968         Atomics.wait(state.sabOPView, state.opIds.xSleep, 0, ms);
969         return 0;
970       };
971     }
973     /**
974        Expects an OPFS file path. It gets resolved, such that ".."
975        components are properly expanded, and returned. If the 2nd arg
976        is true, the result is returned as an array of path elements,
977        else an absolute path string is returned.
978     */
979     opfsUtil.getResolvedPath = function(filename,splitIt){
980       const p = new URL(filename, "file://irrelevant").pathname;
981       return splitIt ? p.split('/').filter((v)=>!!v) : p;
982     };
984     /**
985        Takes the absolute path to a filesystem element. Returns an
986        array of [handleOfContainingDir, filename]. If the 2nd argument
987        is truthy then each directory element leading to the file is
988        created along the way. Throws if any creation or resolution
989        fails.
990     */
991     opfsUtil.getDirForFilename = async function f(absFilename, createDirs = false){
992       const path = opfsUtil.getResolvedPath(absFilename, true);
993       const filename = path.pop();
994       let dh = opfsUtil.rootDirectory;
995       for(const dirName of path){
996         if(dirName){
997           dh = await dh.getDirectoryHandle(dirName, {create: !!createDirs});
998         }
999       }
1000       return [dh, filename];
1001     };
1003     /**
1004        Creates the given directory name, recursively, in
1005        the OPFS filesystem. Returns true if it succeeds or the
1006        directory already exists, else false.
1007     */
1008     opfsUtil.mkdir = async function(absDirName){
1009       try {
1010         await opfsUtil.getDirForFilename(absDirName+"/filepart", true);
1011         return true;
1012       }catch(e){
1013         //sqlite3.config.warn("mkdir(",absDirName,") failed:",e);
1014         return false;
1015       }
1016     };
1017     /**
1018        Checks whether the given OPFS filesystem entry exists,
1019        returning true if it does, false if it doesn't.
1020     */
1021     opfsUtil.entryExists = async function(fsEntryName){
1022       try {
1023         const [dh, fn] = await opfsUtil.getDirForFilename(fsEntryName);
1024         await dh.getFileHandle(fn);
1025         return true;
1026       }catch(e){
1027         return false;
1028       }
1029     };
1031     /**
1032        Generates a random ASCII string, intended for use as a
1033        temporary file name. Its argument is the length of the string,
1034        defaulting to 16.
1035     */
1036     opfsUtil.randomFilename = randomFilename;
1038     /**
1039        Returns a promise which resolves to an object which represents
1040        all files and directories in the OPFS tree. The top-most object
1041        has two properties: `dirs` is an array of directory entries
1042        (described below) and `files` is a list of file names for all
1043        files in that directory.
1045        Traversal starts at sqlite3.opfs.rootDirectory.
1047        Each `dirs` entry is an object in this form:
1049        ```
1050        { name: directoryName,
1051          dirs: [...subdirs],
1052          files: [...file names]
1053        }
1054        ```
1056        The `files` and `subdirs` entries are always set but may be
1057        empty arrays.
1059        The returned object has the same structure but its `name` is
1060        an empty string. All returned objects are created with
1061        Object.create(null), so have no prototype.
1063        Design note: the entries do not contain more information,
1064        e.g. file sizes, because getting such info is not only
1065        expensive but is subject to locking-related errors.
1066     */
1067     opfsUtil.treeList = async function(){
1068       const doDir = async function callee(dirHandle,tgt){
1069         tgt.name = dirHandle.name;
1070         tgt.dirs = [];
1071         tgt.files = [];
1072         for await (const handle of dirHandle.values()){
1073           if('directory' === handle.kind){
1074             const subDir = Object.create(null);
1075             tgt.dirs.push(subDir);
1076             await callee(handle, subDir);
1077           }else{
1078             tgt.files.push(handle.name);
1079           }
1080         }
1081       };
1082       const root = Object.create(null);
1083       await doDir(opfsUtil.rootDirectory, root);
1084       return root;
1085     };
1087     /**
1088        Irrevocably deletes _all_ files in the current origin's OPFS.
1089        Obviously, this must be used with great caution. It may throw
1090        an exception if removal of anything fails (e.g. a file is
1091        locked), but the precise conditions under which the underlying
1092        APIs will throw are not documented (so we cannot tell you what
1093        they are).
1094     */
1095     opfsUtil.rmfr = async function(){
1096       const dir = opfsUtil.rootDirectory, opt = {recurse: true};
1097       for await (const handle of dir.values()){
1098         dir.removeEntry(handle.name, opt);
1099       }
1100     };
1102     /**
1103        Deletes the given OPFS filesystem entry.  As this environment
1104        has no notion of "current directory", the given name must be an
1105        absolute path. If the 2nd argument is truthy, deletion is
1106        recursive (use with caution!).
1108        The returned Promise resolves to true if the deletion was
1109        successful, else false (but...). The OPFS API reports the
1110        reason for the failure only in human-readable form, not
1111        exceptions which can be type-checked to determine the
1112        failure. Because of that...
1114        If the final argument is truthy then this function will
1115        propagate any exception on error, rather than returning false.
1116     */
1117     opfsUtil.unlink = async function(fsEntryName, recursive = false,
1118                                      throwOnError = false){
1119       try {
1120         const [hDir, filenamePart] =
1121               await opfsUtil.getDirForFilename(fsEntryName, false);
1122         await hDir.removeEntry(filenamePart, {recursive});
1123         return true;
1124       }catch(e){
1125         if(throwOnError){
1126           throw new Error("unlink(",arguments[0],") failed: "+e.message,{
1127             cause: e
1128           });
1129         }
1130         return false;
1131       }
1132     };
1134     /**
1135        Traverses the OPFS filesystem, calling a callback for each one.
1136        The argument may be either a callback function or an options object
1137        with any of the following properties:
1139        - `callback`: function which gets called for each filesystem
1140          entry.  It gets passed 3 arguments: 1) the
1141          FileSystemFileHandle or FileSystemDirectoryHandle of each
1142          entry (noting that both are instanceof FileSystemHandle). 2)
1143          the FileSystemDirectoryHandle of the parent directory. 3) the
1144          current depth level, with 0 being at the top of the tree
1145          relative to the starting directory. If the callback returns a
1146          literal false, as opposed to any other falsy value, traversal
1147          stops without an error. Any exceptions it throws are
1148          propagated. Results are undefined if the callback manipulate
1149          the filesystem (e.g. removing or adding entries) because the
1150          how OPFS iterators behave in the face of such changes is
1151          undocumented.
1153        - `recursive` [bool=true]: specifies whether to recurse into
1154          subdirectories or not. Whether recursion is depth-first or
1155          breadth-first is unspecified!
1157        - `directory` [FileSystemDirectoryEntry=sqlite3.opfs.rootDirectory]
1158          specifies the starting directory.
1160        If this function is passed a function, it is assumed to be the
1161        callback.
1163        Returns a promise because it has to (by virtue of being async)
1164        but that promise has no specific meaning: the traversal it
1165        performs is synchronous. The promise must be used to catch any
1166        exceptions propagated by the callback, however.
1168        TODO: add an option which specifies whether to traverse
1169        depth-first or breadth-first. We currently do depth-first but
1170        an incremental file browsing widget would benefit more from
1171        breadth-first.
1172     */
1173     opfsUtil.traverse = async function(opt){
1174       const defaultOpt = {
1175         recursive: true,
1176         directory: opfsUtil.rootDirectory
1177       };
1178       if('function'===typeof opt){
1179         opt = {callback:opt};
1180       }
1181       opt = Object.assign(defaultOpt, opt||{});
1182       const doDir = async function callee(dirHandle, depth){
1183         for await (const handle of dirHandle.values()){
1184           if(false === opt.callback(handle, dirHandle, depth)) return false;
1185           else if(opt.recursive && 'directory' === handle.kind){
1186             if(false === await callee(handle, depth + 1)) break;
1187           }
1188         }
1189       };
1190       doDir(opt.directory, 0);
1191     };
1193     /**
1194        impl of importDb() when it's given a function as its second
1195        argument.
1196     */
1197     const importDbChunked = async function(filename, callback){
1198       const [hDir, fnamePart] = await opfsUtil.getDirForFilename(filename, true);
1199       const hFile = await hDir.getFileHandle(fnamePart, {create:true});
1200       let sah = await hFile.createSyncAccessHandle();
1201       let nWrote = 0, chunk, checkedHeader = false, err = false;
1202       try{
1203         sah.truncate(0);
1204         while( undefined !== (chunk = await callback()) ){
1205           if(chunk instanceof ArrayBuffer) chunk = new Uint8Array(chunk);
1206           if( 0===nWrote && chunk.byteLength>=15 ){
1207             util.affirmDbHeader(chunk);
1208             checkedHeader = true;
1209           }
1210           sah.write(chunk, {at: nWrote});
1211           nWrote += chunk.byteLength;
1212         }
1213         if( nWrote < 512 || 0!==nWrote % 512 ){
1214           toss("Input size",nWrote,"is not correct for an SQLite database.");
1215         }
1216         if( !checkedHeader ){
1217           const header = new Uint8Array(20);
1218           sah.read( header, {at: 0} );
1219           util.affirmDbHeader( header );
1220         }
1221         sah.write(new Uint8Array([1,1]), {at: 18}/*force db out of WAL mode*/);
1222         return nWrote;
1223       }catch(e){
1224         await sah.close();
1225         sah = undefined;
1226         await hDir.removeEntry( fnamePart ).catch(()=>{});
1227         throw e;
1228       }finally {
1229         if( sah ) await sah.close();
1230       }
1231     };
1233     /**
1234        Asynchronously imports the given bytes (a byte array or
1235        ArrayBuffer) into the given database file.
1237        Results are undefined if the given db name refers to an opened
1238        db.
1240        If passed a function for its second argument, its behaviour
1241        changes: imports its data in chunks fed to it by the given
1242        callback function. It calls the callback (which may be async)
1243        repeatedly, expecting either a Uint8Array or ArrayBuffer (to
1244        denote new input) or undefined (to denote EOF). For so long as
1245        the callback continues to return non-undefined, it will append
1246        incoming data to the given VFS-hosted database file. When
1247        called this way, the resolved value of the returned Promise is
1248        the number of bytes written to the target file.
1250        It very specifically requires the input to be an SQLite3
1251        database and throws if that's not the case.  It does so in
1252        order to prevent this function from taking on a larger scope
1253        than it is specifically intended to. i.e. we do not want it to
1254        become a convenience for importing arbitrary files into OPFS.
1256        This routine rewrites the database header bytes in the output
1257        file (not the input array) to force disabling of WAL mode.
1259        On error this throws and the state of the input file is
1260        undefined (it depends on where the exception was triggered).
1262        On success, resolves to the number of bytes written.
1263     */
1264     opfsUtil.importDb = async function(filename, bytes){
1265       if( bytes instanceof Function ){
1266         return importDbChunked(filename, bytes);
1267       }
1268       if(bytes instanceof ArrayBuffer) bytes = new Uint8Array(bytes);
1269       util.affirmIsDb(bytes);
1270       const n = bytes.byteLength;
1271       const [hDir, fnamePart] = await opfsUtil.getDirForFilename(filename, true);
1272       let sah, err, nWrote = 0;
1273       try {
1274         const hFile = await hDir.getFileHandle(fnamePart, {create:true});
1275         sah = await hFile.createSyncAccessHandle();
1276         sah.truncate(0);
1277         nWrote = sah.write(bytes, {at: 0});
1278         if(nWrote != n){
1279           toss("Expected to write "+n+" bytes but wrote "+nWrote+".");
1280         }
1281         sah.write(new Uint8Array([1,1]), {at: 18}) /* force db out of WAL mode */;
1282         return nWrote;
1283       }catch(e){
1284         if( sah ){ await sah.close(); sah = undefined; }
1285         await hDir.removeEntry( fnamePart ).catch(()=>{});
1286         throw e;
1287       }finally{
1288         if( sah ) await sah.close();
1289       }
1290     };
1292     if(sqlite3.oo1){
1293       const OpfsDb = function(...args){
1294         const opt = sqlite3.oo1.DB.dbCtorHelper.normalizeArgs(...args);
1295         opt.vfs = opfsVfs.$zName;
1296         sqlite3.oo1.DB.dbCtorHelper.call(this, opt);
1297       };
1298       OpfsDb.prototype = Object.create(sqlite3.oo1.DB.prototype);
1299       sqlite3.oo1.OpfsDb = OpfsDb;
1300       OpfsDb.importDb = opfsUtil.importDb;
1301       sqlite3.oo1.DB.dbCtorHelper.setVfsPostOpenSql(
1302         opfsVfs.pointer,
1303         function(oo1Db, sqlite3){
1304           /* Set a relatively high default busy-timeout handler to
1305              help OPFS dbs deal with multi-tab/multi-worker
1306              contention. */
1307           sqlite3.capi.sqlite3_busy_timeout(oo1Db, 10000);
1308           sqlite3.capi.sqlite3_exec(oo1Db, [
1309             /* As of July 2023, the PERSIST journal mode on OPFS is
1310                somewhat slower than DELETE or TRUNCATE (it was faster
1311                before Chrome version 108 or 109). TRUNCATE and DELETE
1312                have very similar performance on OPFS.
1314                Roy Hashimoto notes that TRUNCATE and PERSIST modes may
1315                decrease OPFS concurrency because multiple connections
1316                can open the journal file in those modes:
1318                https://github.com/rhashimoto/wa-sqlite/issues/68
1320                Given that, and the fact that testing has not revealed
1321                any appreciable difference between performance of
1322                TRUNCATE and DELETE modes on OPFS, we currently (as of
1323                2023-07-13) default to DELETE mode.
1324             */
1325             "pragma journal_mode=DELETE;",
1326             /*
1327               This vfs benefits hugely from cache on moderate/large
1328               speedtest1 --size 50 and --size 100 workloads. We
1329               currently rely on setting a non-default cache size when
1330               building sqlite3.wasm. If that policy changes, the cache
1331               can be set here.
1332             */
1333             "pragma cache_size=-16384;"
1334           ], 0, 0, 0);
1335         }
1336       );
1337     }/*extend sqlite3.oo1*/
1339     const sanityCheck = function(){
1340       const scope = wasm.scopedAllocPush();
1341       const sq3File = new sqlite3_file();
1342       try{
1343         const fid = sq3File.pointer;
1344         const openFlags = capi.SQLITE_OPEN_CREATE
1345               | capi.SQLITE_OPEN_READWRITE
1346         //| capi.SQLITE_OPEN_DELETEONCLOSE
1347               | capi.SQLITE_OPEN_MAIN_DB;
1348         const pOut = wasm.scopedAlloc(8);
1349         const dbFile = "/sanity/check/file"+randomFilename(8);
1350         const zDbFile = wasm.scopedAllocCString(dbFile);
1351         let rc;
1352         state.s11n.serialize("This is ä string.");
1353         rc = state.s11n.deserialize();
1354         log("deserialize() says:",rc);
1355         if("This is ä string."!==rc[0]) toss("String d13n error.");
1356         vfsSyncWrappers.xAccess(opfsVfs.pointer, zDbFile, 0, pOut);
1357         rc = wasm.peek(pOut,'i32');
1358         log("xAccess(",dbFile,") exists ?=",rc);
1359         rc = vfsSyncWrappers.xOpen(opfsVfs.pointer, zDbFile,
1360                                    fid, openFlags, pOut);
1361         log("open rc =",rc,"state.sabOPView[xOpen] =",
1362             state.sabOPView[state.opIds.xOpen]);
1363         if(0!==rc){
1364           error("open failed with code",rc);
1365           return;
1366         }
1367         vfsSyncWrappers.xAccess(opfsVfs.pointer, zDbFile, 0, pOut);
1368         rc = wasm.peek(pOut,'i32');
1369         if(!rc) toss("xAccess() failed to detect file.");
1370         rc = ioSyncWrappers.xSync(sq3File.pointer, 0);
1371         if(rc) toss('sync failed w/ rc',rc);
1372         rc = ioSyncWrappers.xTruncate(sq3File.pointer, 1024);
1373         if(rc) toss('truncate failed w/ rc',rc);
1374         wasm.poke(pOut,0,'i64');
1375         rc = ioSyncWrappers.xFileSize(sq3File.pointer, pOut);
1376         if(rc) toss('xFileSize failed w/ rc',rc);
1377         log("xFileSize says:",wasm.peek(pOut, 'i64'));
1378         rc = ioSyncWrappers.xWrite(sq3File.pointer, zDbFile, 10, 1);
1379         if(rc) toss("xWrite() failed!");
1380         const readBuf = wasm.scopedAlloc(16);
1381         rc = ioSyncWrappers.xRead(sq3File.pointer, readBuf, 6, 2);
1382         wasm.poke(readBuf+6,0);
1383         let jRead = wasm.cstrToJs(readBuf);
1384         log("xRead() got:",jRead);
1385         if("sanity"!==jRead) toss("Unexpected xRead() value.");
1386         if(vfsSyncWrappers.xSleep){
1387           log("xSleep()ing before close()ing...");
1388           vfsSyncWrappers.xSleep(opfsVfs.pointer,2000);
1389           log("waking up from xSleep()");
1390         }
1391         rc = ioSyncWrappers.xClose(fid);
1392         log("xClose rc =",rc,"sabOPView =",state.sabOPView);
1393         log("Deleting file:",dbFile);
1394         vfsSyncWrappers.xDelete(opfsVfs.pointer, zDbFile, 0x1234);
1395         vfsSyncWrappers.xAccess(opfsVfs.pointer, zDbFile, 0, pOut);
1396         rc = wasm.peek(pOut,'i32');
1397         if(rc) toss("Expecting 0 from xAccess(",dbFile,") after xDelete().");
1398         warn("End of OPFS sanity checks.");
1399       }finally{
1400         sq3File.dispose();
1401         wasm.scopedAllocPop(scope);
1402       }
1403     }/*sanityCheck()*/;
1405     W.onmessage = function({data}){
1406       //log("Worker.onmessage:",data);
1407       switch(data.type){
1408           case 'opfs-unavailable':
1409             /* Async proxy has determined that OPFS is unavailable. There's
1410                nothing more for us to do here. */
1411             promiseReject(new Error(data.payload.join(' ')));
1412             break;
1413           case 'opfs-async-loaded':
1414             /* Arrives as soon as the asyc proxy finishes loading.
1415                Pass our config and shared state on to the async
1416                worker. */
1417             W.postMessage({type: 'opfs-async-init',args: state});
1418             break;
1419           case 'opfs-async-inited': {
1420             /* Indicates that the async partner has received the 'init'
1421                and has finished initializing, so the real work can
1422                begin... */
1423             if(true===promiseWasRejected){
1424               break /* promise was already rejected via timer */;
1425             }
1426             try {
1427               sqlite3.vfs.installVfs({
1428                 io: {struct: opfsIoMethods, methods: ioSyncWrappers},
1429                 vfs: {struct: opfsVfs, methods: vfsSyncWrappers}
1430               });
1431               state.sabOPView = new Int32Array(state.sabOP);
1432               state.sabFileBufView = new Uint8Array(state.sabIO, 0, state.fileBufferSize);
1433               state.sabS11nView = new Uint8Array(state.sabIO, state.sabS11nOffset, state.sabS11nSize);
1434               initS11n();
1435               if(options.sanityChecks){
1436                 warn("Running sanity checks because of opfs-sanity-check URL arg...");
1437                 sanityCheck();
1438               }
1439               if(thisThreadHasOPFS()){
1440                 navigator.storage.getDirectory().then((d)=>{
1441                   W.onerror = W._originalOnError;
1442                   delete W._originalOnError;
1443                   sqlite3.opfs = opfsUtil;
1444                   opfsUtil.rootDirectory = d;
1445                   log("End of OPFS sqlite3_vfs setup.", opfsVfs);
1446                   promiseResolve();
1447                 }).catch(promiseReject);
1448               }else{
1449                 promiseResolve();
1450               }
1451             }catch(e){
1452               error(e);
1453               promiseReject(e);
1454             }
1455             break;
1456           }
1457           default: {
1458             const errMsg = (
1459               "Unexpected message from the OPFS async worker: " +
1460               JSON.stringify(data)
1461             );
1462             error(errMsg);
1463             promiseReject(new Error(errMsg));
1464             break;
1465           }
1466       }/*switch(data.type)*/
1467     }/*W.onmessage()*/;
1468   })/*thePromise*/;
1469   return thePromise;
1470 }/*installOpfsVfs()*/;
1471 installOpfsVfs.defaultProxyUri =
1472   "sqlite3-opfs-async-proxy.js";
1473 globalThis.sqlite3ApiBootstrap.initializersAsync.push(async (sqlite3)=>{
1474   try{
1475     let proxyJs = installOpfsVfs.defaultProxyUri;
1476     if(sqlite3.scriptInfo.sqlite3Dir){
1477       installOpfsVfs.defaultProxyUri =
1478         sqlite3.scriptInfo.sqlite3Dir + proxyJs;
1479       //sqlite3.config.warn("installOpfsVfs.defaultProxyUri =",installOpfsVfs.defaultProxyUri);
1480     }
1481     return installOpfsVfs().catch((e)=>{
1482       sqlite3.config.warn("Ignoring inability to install OPFS sqlite3_vfs:",e.message);
1483     });
1484   }catch(e){
1485     sqlite3.config.error("installOpfsVfs() exception:",e);
1486     return Promise.reject(e);
1487   }
1489 }/*sqlite3ApiBootstrap.initializers.push()*/);
1490 //#else
1491 /* The OPFS VFS parts are elided from builds targeting node.js. */
1492 //#endif target=node