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.
22 globalThis.sqlite3ApiBootstrap.initializers.push(function(sqlite3){
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
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
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")
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().")
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.")
101 if(!options || 'object'!==typeof options){
102 options = Object.create(null);
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);
109 if(undefined===options.verbose){
110 options.verbose = urlParams.has('opfs-verbose')
111 ? (+urlParams.get('opfs-verbose') || 2) : 1;
113 if(undefined===options.sanityChecks){
114 options.sanityChecks = urlParams.has('opfs-sanity-check');
116 if(undefined===options.proxyUri){
117 options.proxyUri = callee.defaultProxyUri;
120 //sqlite3.config.warn("OPFS options =",options,globalThis.location);
122 if('function' === typeof options.proxyUri){
123 options.proxyUri = options.proxyUri();
125 const thePromise = new Promise(function(promiseResolve_, promiseReject_){
127 sqlite3.config.error,
131 const logImpl = (level,...args)=>{
132 if(options.verbose>level) loggers[level]("OPFS syncer:",...args);
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;
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.
152 const opfsUtil = Object.create(null);
155 Returns true if _this_ thread has access to the OPFS APIs.
157 const thisThreadHasOPFS = ()=>{
158 return globalThis.FileSystemHandle &&
159 globalThis.FileSystemDirectoryHandle &&
160 globalThis.FileSystemFileHandle &&
161 globalThis.FileSystemFileHandle.prototype.createSyncAccessHandle &&
162 navigator?.storage?.getDirectory;
166 Not part of the public API. Solely for internal/development
171 let k, n = 0, t = 0, w = 0;
172 for(k in state.opIds){
173 const m = metrics[k];
177 m.avgTime = (m.count && m.time) ? (m.time / m.count) : 0;
178 m.avgWait = (m.count && m.wait) ? (m.wait / m.count) : 0;
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'});
189 const r = (m)=>(m.count = m.time = m.wait = 0);
190 for(k in state.opIds){
191 r(metrics[k] = Object.create(null));
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;
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;
207 return promiseReject_(err);
209 const promiseResolve = ()=>{
210 promiseWasRejected = false;
211 return promiseResolve_(sqlite3);
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));
219 new Worker(options.proxyUri);
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){
229 new Error("Timeout while waiting for OPFS async proxy worker.")
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."));
240 const pDVfs = capi.sqlite3_vfs_find(null)/*pointer to default VFS*/;
242 ? new sqlite3_vfs(pDVfs)
243 : null /* dVfs will be null when sqlite3 is built with
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)
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).
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.
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;
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.
308 state.asyncIdleWaitTime = 150;
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.
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;
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.
336 state.sabS11nSize = opfsVfs.$mxPathname * 2;
338 The SAB used for all data I/O between the synchronous and
339 async halves (file i/o and arg/result s11n).
341 state.sabIO = new SharedArrayBuffer(
342 state.fileBufferSize/* file i/o block */
343 + state.sabS11nSize/* argument/result serialization block */
345 state.opIds = Object.create(null);
346 const metrics = Object.create(null);
348 /* Indexes for use in our SharedArrayBuffer... */
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
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();
387 SQLITE_xxx constants to export to the async worker
390 state.sq3Codes = Object.create(null);
392 'SQLITE_ACCESS_EXISTS',
393 'SQLITE_ACCESS_READWRITE',
398 'SQLITE_IOERR_ACCESS',
399 'SQLITE_IOERR_CLOSE',
400 'SQLITE_IOERR_DELETE',
401 'SQLITE_IOERR_FSYNC',
404 'SQLITE_IOERR_SHORT_READ',
405 'SQLITE_IOERR_TRUNCATE',
406 'SQLITE_IOERR_UNLOCK',
407 'SQLITE_IOERR_WRITE',
408 'SQLITE_LOCK_EXCLUSIVE',
410 'SQLITE_LOCK_PENDING',
411 'SQLITE_LOCK_RESERVED',
412 'SQLITE_LOCK_SHARED',
416 'SQLITE_OPEN_CREATE',
417 'SQLITE_OPEN_DELETEONCLOSE',
418 'SQLITE_OPEN_MAIN_DB',
419 'SQLITE_OPEN_READONLY'
421 if(undefined === (state.sq3Codes[k] = capi[k])){
422 toss("Maintenance required: not found:",k);
425 state.opfsFlags = Object.assign(Object.create(null),{
427 Flag for use with xOpen(). URI flag "opfs-unlock-asap=1"
428 enables this. See defaultUnlockAsap, below.
430 OPFS_UNLOCK_ASAP: 0x01,
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
442 It goes without saying that deleting a file out from under another
443 instance results in Undefined Behavior.
445 OPFS_UNLINK_BEFORE_OPEN: 0x02,
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
457 defaultUnlockAsap: false
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
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)){
477 The reason for this loop is buried in the details of a long
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
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);
501 Not part of the public API. Only for test/development use.
505 warn("Shutting down OPFS async listener. The OPFS VFS will no longer work.");
506 opRun('opfs-async-shutdown');
509 warn("Attempting to restart OPFS VFS async listener. Might work, might not.");
510 W.postMessage({type: 'opfs-async-restart'});
514 const initS11n = ()=>{
516 !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
517 ACHTUNG: this code is 100% duplicated in the other half of
518 this proxy! The documentation is maintained in the
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
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
534 The data format can be succinctly summarized as:
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
547 - d = raw bytes of first argument (per-type size).
549 - ...D = raw bytes of the 2nd and subsequent arguments (per-type
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.
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
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)=>(
580 || toss("Maintenance required: this value type cannot be serialized.",v)
582 const getTypeIdById = (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);
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.
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;
606 let offset = 1, i, n, v;
607 for(i = 0; i < argc; ++i, ++offset){
608 typeIds.push(getTypeIdById(viewU8[offset]));
610 for(i = 0; i < argc; ++i){
611 const t = typeIds[i];
613 v = viewDV[t.getter](offset, state.littleEndian);
616 n = viewDV.getInt32(offset, state.littleEndian);
618 v = textDecoder.decode(viewU8.slice(offset, offset+n));
624 if(clear) viewU8[0] = 0;
625 //log("deserialize:",argc, rc);
626 metrics.s11n.deserialize.time += performance.now() - t;
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
639 If passed no arguments then it zeroes out the serialization
642 state.s11n.serialize = function(...args){
643 const t = performance.now();
644 ++metrics.s11n.serialize.count;
646 //log("serialize():",args);
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
653 typeIds.push(getTypeId(args[i]));
654 viewU8[offset] = typeIds[i].id;
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];
661 viewDV[t.setter](offset, args[i], state.littleEndian);
664 const s = textEncoder.encode(args[i]);
665 viewDV.setInt32(offset, s.byteLength, state.littleEndian);
667 viewU8.set(s, offset);
668 offset += s.byteLength;
671 //log("serialize() result:",viewU8.slice(0,offset));
675 metrics.s11n.serialize.time += performance.now() - t;
681 Generates a random ASCII string len characters long, intended for
682 use as a temporary file name.
684 const randomFilename = function f(len=16){
686 f._chars = "abcdefghijklmnopqrstuvwxyz"+
687 "ABCDEFGHIJKLMNOPQRSTUVWXYZ"+
689 f._n = f._chars.length;
693 for( ; i < len; ++i){
694 const ndx = Math.random() * (f._n * 64) % f._n | 0;
695 a[i] = f._chars[ndx];
699 An alternative impl. with an unpredictable length
702 Math.floor(Math.random() * Number.MAX_SAFE_INTEGER).toString(36)
707 Map of sqlite3_file pointers to objects constructed by xOpen().
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();
719 const mTimeEnd = ()=>(
720 metrics[opTimer.op].time += performance.now() - opTimer.start
724 Impls for the sqlite3_io_methods methods. Maintenance reminder:
725 members are in alphabetical order to simplify finding them.
727 const ioSyncWrappers = {
728 xCheckReservedLock: function(pFile,pOut){
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
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
744 wasm.poke(pOut, 0, 'i32');
746 const f = __openFiles[pFile];
747 wasm.poke(pOut, f.lockType ? 1 : 0, 'i32');
751 xClose: function(pFile){
752 mTimeStart('xClose');
754 const f = __openFiles[pFile];
756 delete __openFiles[pFile];
757 rc = opRun('xClose', pFile);
758 if(f.sq3File) f.sq3File.dispose();
763 xDeviceCharacteristics: function(pFile){
764 return capi.SQLITE_IOCAP_UNDELETABLE_WHEN_OPEN;
766 xFileControl: function(pFile, opId, pArg){
767 /*mTimeStart('xFileControl');
769 return capi.SQLITE_NOTFOUND;
771 xFileSize: function(pFile,pSz64){
772 mTimeStart('xFileSize');
773 let rc = opRun('xFileSize', pFile);
776 const sz = state.s11n.deserialize()[0];
777 wasm.poke(pSz64, sz, 'i64');
779 error("Unexpected error reading xFileSize() result:",e);
780 rc = state.sq3Codes.SQLITE_IOERR;
786 xLock: function(pFile,lockType){
788 const f = __openFiles[pFile];
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
795 rc = opRun('xLock', pFile, lockType);
796 if( 0===rc ) f.lockType = lockType;
798 f.lockType = lockType;
803 xRead: function(pFile,pDest,n,offset64){
805 const f = __openFiles[pFile];
808 rc = opRun('xRead',pFile, n, Number(offset64));
809 if(0===rc || capi.SQLITE_IOERR_SHORT_READ===rc){
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);
818 error("xRead(",arguments,") failed:",e,f);
819 rc = capi.SQLITE_IOERR_READ;
824 xSync: function(pFile,flags){
826 ++metrics.xSync.count;
827 const rc = opRun('xSync', pFile, flags);
831 xTruncate: function(pFile,sz64){
832 mTimeStart('xTruncate');
833 const rc = opRun('xTruncate', pFile, Number(sz64));
837 xUnlock: function(pFile,lockType){
838 mTimeStart('xUnlock');
839 const f = __openFiles[pFile];
841 if( capi.SQLITE_LOCK_NONE === lockType
843 rc = opRun('xUnlock', pFile, lockType);
845 if( 0===rc ) f.lockType = lockType;
849 xWrite: function(pFile,pSrc,n,offset64){
850 mTimeStart('xWrite');
851 const f = __openFiles[pFile];
854 f.sabView.set(wasm.heap8u().subarray(pSrc, pSrc+n));
855 rc = opRun('xWrite', pFile, n, Number(offset64));
857 error("xWrite(",arguments,") failed:",e,f);
858 rc = capi.SQLITE_IOERR_WRITE;
866 Impls for the sqlite3_vfs methods. Maintenance reminder: members
867 are in alphabetical order to simplify finding them.
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' );
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),
884 xCurrentTimeInt64: function(pVfs,pOut){
885 wasm.poke(pOut, (2440587.5 * 86400000) + new Date().getTime(),
889 xDelete: function(pVfs, zName, doSyncDir){
890 mTimeStart('xDelete');
891 const rc = opRun('xDelete', wasm.cstrToJs(zName), doSyncDir, false);
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*/;
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.");
909 //xSleep is optionally defined below
910 xOpen: function f(pVfs, zName, pFile, flags, pOutFlags){
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
919 opfsFlags |= state.opfsFlags.OPFS_UNLOCK_ASAP;
921 if(capi.sqlite3_uri_boolean(zName, "delete-before-open", 0)){
922 opfsFlags |= state.opfsFlags.OPFS_UNLINK_BEFORE_OPEN;
924 zName = wasm.cstrToJs(zName);
925 //warn("xOpen zName =",zName, "opfsFlags =",opfsFlags);
927 const fh = Object.create(null);
930 fh.sab = new SharedArrayBuffer(state.fileBufferSize);
932 const rc = opRun('xOpen', pFile, zName, flags, opfsFlags);
934 /* Recall that sqlite3_vfs::xClose() will be called, even on
935 error, unless pFile->pMethods is NULL. */
937 wasm.poke(pOutFlags, capi.SQLITE_OPEN_READONLY, 'i32');
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;
948 }/*vfsSyncWrappers*/;
951 opfsVfs.$xRandomness = dVfs.$xRandomness;
952 opfsVfs.$xSleep = dVfs.$xSleep;
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();
959 for(; i < nOut; ++i) heap[pOut + i] = (Math.random()*255000) & 0xFF;
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
967 vfsSyncWrappers.xSleep = function(pVfs,ms){
968 Atomics.wait(state.sabOPView, state.opIds.xSleep, 0, ms);
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.
979 opfsUtil.getResolvedPath = function(filename,splitIt){
980 const p = new URL(filename, "file://irrelevant").pathname;
981 return splitIt ? p.split('/').filter((v)=>!!v) : p;
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
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){
997 dh = await dh.getDirectoryHandle(dirName, {create: !!createDirs});
1000 return [dh, filename];
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.
1008 opfsUtil.mkdir = async function(absDirName){
1010 await opfsUtil.getDirForFilename(absDirName+"/filepart", true);
1013 //sqlite3.config.warn("mkdir(",absDirName,") failed:",e);
1018 Checks whether the given OPFS filesystem entry exists,
1019 returning true if it does, false if it doesn't.
1021 opfsUtil.entryExists = async function(fsEntryName){
1023 const [dh, fn] = await opfsUtil.getDirForFilename(fsEntryName);
1024 await dh.getFileHandle(fn);
1032 Generates a random ASCII string, intended for use as a
1033 temporary file name. Its argument is the length of the string,
1036 opfsUtil.randomFilename = randomFilename;
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:
1050 { name: directoryName,
1052 files: [...file names]
1056 The `files` and `subdirs` entries are always set but may be
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.
1067 opfsUtil.treeList = async function(){
1068 const doDir = async function callee(dirHandle,tgt){
1069 tgt.name = dirHandle.name;
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);
1078 tgt.files.push(handle.name);
1082 const root = Object.create(null);
1083 await doDir(opfsUtil.rootDirectory, root);
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
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);
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.
1117 opfsUtil.unlink = async function(fsEntryName, recursive = false,
1118 throwOnError = false){
1120 const [hDir, filenamePart] =
1121 await opfsUtil.getDirForFilename(fsEntryName, false);
1122 await hDir.removeEntry(filenamePart, {recursive});
1126 throw new Error("unlink(",arguments[0],") failed: "+e.message,{
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
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
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
1173 opfsUtil.traverse = async function(opt){
1174 const defaultOpt = {
1176 directory: opfsUtil.rootDirectory
1178 if('function'===typeof opt){
1179 opt = {callback:opt};
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;
1190 doDir(opt.directory, 0);
1194 impl of importDb() when it's given a function as its second
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;
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;
1210 sah.write(chunk, {at: nWrote});
1211 nWrote += chunk.byteLength;
1213 if( nWrote < 512 || 0!==nWrote % 512 ){
1214 toss("Input size",nWrote,"is not correct for an SQLite database.");
1216 if( !checkedHeader ){
1217 const header = new Uint8Array(20);
1218 sah.read( header, {at: 0} );
1219 util.affirmDbHeader( header );
1221 sah.write(new Uint8Array([1,1]), {at: 18}/*force db out of WAL mode*/);
1226 await hDir.removeEntry( fnamePart ).catch(()=>{});
1229 if( sah ) await sah.close();
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
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.
1264 opfsUtil.importDb = async function(filename, bytes){
1265 if( bytes instanceof Function ){
1266 return importDbChunked(filename, bytes);
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;
1274 const hFile = await hDir.getFileHandle(fnamePart, {create:true});
1275 sah = await hFile.createSyncAccessHandle();
1277 nWrote = sah.write(bytes, {at: 0});
1279 toss("Expected to write "+n+" bytes but wrote "+nWrote+".");
1281 sah.write(new Uint8Array([1,1]), {at: 18}) /* force db out of WAL mode */;
1284 if( sah ){ await sah.close(); sah = undefined; }
1285 await hDir.removeEntry( fnamePart ).catch(()=>{});
1288 if( sah ) await sah.close();
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);
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(
1303 function(oo1Db, sqlite3){
1304 /* Set a relatively high default busy-timeout handler to
1305 help OPFS dbs deal with multi-tab/multi-worker
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.
1325 "pragma journal_mode=DELETE;",
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
1333 "pragma cache_size=-16384;"
1337 }/*extend sqlite3.oo1*/
1339 const sanityCheck = function(){
1340 const scope = wasm.scopedAllocPush();
1341 const sq3File = new sqlite3_file();
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);
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]);
1364 error("open failed with code",rc);
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()");
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.");
1401 wasm.scopedAllocPop(scope);
1405 W.onmessage = function({data}){
1406 //log("Worker.onmessage:",data);
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(' ')));
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
1417 W.postMessage({type: 'opfs-async-init',args: state});
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
1423 if(true===promiseWasRejected){
1424 break /* promise was already rejected via timer */;
1427 sqlite3.vfs.installVfs({
1428 io: {struct: opfsIoMethods, methods: ioSyncWrappers},
1429 vfs: {struct: opfsVfs, methods: vfsSyncWrappers}
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);
1435 if(options.sanityChecks){
1436 warn("Running sanity checks because of opfs-sanity-check URL arg...");
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);
1447 }).catch(promiseReject);
1459 "Unexpected message from the OPFS async worker: " +
1460 JSON.stringify(data)
1463 promiseReject(new Error(errMsg));
1466 }/*switch(data.type)*/
1470 }/*installOpfsVfs()*/;
1471 installOpfsVfs.defaultProxyUri =
1472 "sqlite3-opfs-async-proxy.js";
1473 globalThis.sqlite3ApiBootstrap.initializersAsync.push(async (sqlite3)=>{
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);
1481 return installOpfsVfs().catch((e)=>{
1482 sqlite3.config.warn("Ignoring inability to install OPFS sqlite3_vfs:",e.message);
1485 sqlite3.config.error("installOpfsVfs() exception:",e);
1486 return Promise.reject(e);
1489 }/*sqlite3ApiBootstrap.initializers.push()*/);
1491 /* The OPFS VFS parts are elided from builds targeting node.js. */
1492 //#endif target=node