blob: af1c6f76088fa21b981cdb7ce030ee63bd00808c [file] [log] [blame]
stephan3961b262022-08-10 11:26:08 +00001/*
stephanc5313af2022-09-18 02:35:30 +00002 2022-09-18
stephan3961b262022-08-10 11:26:08 +00003
4 The author disclaims copyright to this source code. In place of a
5 legal notice, here is a blessing:
6
7 * May you do good and not evil.
8 * May you find forgiveness for yourself and forgive others.
9 * May you share freely, never taking more than you give.
10
11 ***********************************************************************
12
stephanc5313af2022-09-18 02:35:30 +000013 This file holds the synchronous half of an sqlite3_vfs
14 implementation which proxies, in a synchronous fashion, the
15 asynchronous Origin-Private FileSystem (OPFS) APIs using a second
16 Worker, implemented in sqlite3-opfs-async-proxy.js. This file is
17 intended to be appended to the main sqlite3 JS deliverable somewhere
18 after sqlite3-api-glue.js and before sqlite3-api-cleanup.js.
19
20*/
21
22'use strict';
23self.sqlite3ApiBootstrap.initializers.push(function(sqlite3){
24/**
25 sqlite3.installOpfsVfs() returns a Promise which, on success, installs
26 an sqlite3_vfs named "opfs", suitable for use with all sqlite3 APIs
27 which accept a VFS. It uses the Origin-Private FileSystem API for
28 all file storage. On error it is rejected with an exception
29 explaining the problem. Reasons for rejection include, but are
30 not limited to:
31
32 - The counterpart Worker (see below) could not be loaded.
33
34 - The environment does not support OPFS. That includes when
35 this function is called from the main window thread.
36
stephan3961b262022-08-10 11:26:08 +000037
38 Significant notes and limitations:
39
40 - As of this writing, OPFS is still very much in flux and only
41 available in bleeding-edge versions of Chrome (v102+, noting that
42 that number will increase as the OPFS API matures).
43
stephanc5313af2022-09-18 02:35:30 +000044 - The OPFS features used here are only available in dedicated Worker
45 threads. This file tries to detect that case and becomes a no-op
46 if those features do not seem to be available.
47
48 - It requires the SharedArrayBuffer and Atomics classes, and the
49 former is only available if the HTTP server emits the so-called
50 COOP and COEP response headers. These features are required for
51 proxying OPFS's synchronous API via the synchronous interface
52 required by the sqlite3_vfs API.
53
54 - This function may only be called a single time and it must be
55 called from the client, as opposed to the library initialization,
56 in case the client requires a custom path for this API's
57 "counterpart": this function's argument is the relative URI to
58 this module's "asynchronous half". When called, this function removes
59 itself from the sqlite3 object.
60
61 The argument may optionally be a plain object with the following
62 configuration options:
63
64 - proxyUri: as described above
65
66 - verbose (=2): an integer 0-3. 0 disables all logging, 1 enables
67 logging of errors. 2 enables logging of warnings and errors. 3
68 additionally enables debugging info.
69
70 - sanityChecks (=false): if true, some basic sanity tests are
71 run on the OPFS VFS API after it's initialized, before the
72 returned Promise resolves.
73
74 On success, the Promise resolves to the top-most sqlite3 namespace
75 object.
stephan3961b262022-08-10 11:26:08 +000076*/
stephanc5313af2022-09-18 02:35:30 +000077sqlite3.installOpfsVfs = function callee(asyncProxyUri = callee.defaultProxyUri){
78 const options = (asyncProxyUri && 'object'===asyncProxyUri) ? asyncProxyUri : {
79 proxyUri: asyncProxyUri
stephan3961b262022-08-10 11:26:08 +000080 };
stephanc5313af2022-09-18 02:35:30 +000081 const thisUrl = new URL(self.location.href);
82 if(undefined===options.verbose){
83 options.verbose = thisUrl.searchParams.has('opfs-verbose') ? 3 : 2;
stephan3961b262022-08-10 11:26:08 +000084 }
stephanc5313af2022-09-18 02:35:30 +000085 if(undefined===options.sanityChecks){
86 options.sanityChecks = thisUrl.searchParams.has('opfs-sanity-check');
87 }
88 if(undefined===options.proxyUri){
89 options.proxyUri = callee.defaultProxyUri;
90 }
91 delete sqlite3.installOpfsVfs;
92 const thePromise = new Promise(function(promiseResolve, promiseReject){
93 const logPrefix = "OPFS syncer:";
94 const warn = (...args)=>{
95 if(options.verbose>1) console.warn(logPrefix,...args);
96 };
97 if(self.window===self ||
98 !self.SharedArrayBuffer ||
99 !self.FileSystemHandle ||
100 !self.FileSystemDirectoryHandle ||
101 !self.FileSystemFileHandle ||
102 !self.FileSystemFileHandle.prototype.createSyncAccessHandle ||
103 !navigator.storage.getDirectory){
104 warn("This environment does not have OPFS support.");
105 promiseReject(new Error("This environment does not have OPFS support."));
106 return;
107 }
108 warn("The OPFS VFS feature is very much experimental and under construction.");
109 const toss = function(...args){throw new Error(args.join(' '))};
110 const log = (...args)=>{
111 if(options.verbose>2) console.log(logPrefix,...args);
112 };
113 const error = (...args)=>{
114 if(options.verbose>0) console.error(logPrefix,...args);
115 };
116 const capi = sqlite3.capi;
117 const wasm = capi.wasm;
118 const sqlite3_vfs = capi.sqlite3_vfs;
119 const sqlite3_file = capi.sqlite3_file;
120 const sqlite3_io_methods = capi.sqlite3_io_methods;
121 const StructBinder = sqlite3.StructBinder;
122 const W = new Worker(options.proxyUri);
123 const workerOrigOnError = W.onrror;
124 W.onerror = function(err){
125 promiseReject(new Error("Loading OPFS async Worker failed for unknown reasons."));
126 };
127 const wMsg = (type,payload)=>W.postMessage({type,payload});
128
129 /**
130 State which we send to the async-api Worker or share with it.
131 This object must initially contain only cloneable or sharable
132 objects. After the worker's "inited" message arrives, other types
133 of data may be added to it.
134 */
135 const state = Object.create(null);
136 state.verbose = options.verbose;
137 state.fileBufferSize = 1024 * 64 + 8 /* size of fileHandle.sab. 64k = max sqlite3 page size */;
138 state.fbInt64Offset = state.fileBufferSize - 8 /*spot in fileHandle.sab to store an int64*/;
139 state.opIds = Object.create(null);
140 {
stephan3961b262022-08-10 11:26:08 +0000141 let i = 0;
stephanc5313af2022-09-18 02:35:30 +0000142 state.opIds.xAccess = i++;
143 state.opIds.xClose = i++;
144 state.opIds.xDelete = i++;
145 state.opIds.xFileSize = i++;
146 state.opIds.xOpen = i++;
147 state.opIds.xRead = i++;
148 state.opIds.xSleep = i++;
149 state.opIds.xSync = i++;
150 state.opIds.xTruncate = i++;
151 state.opIds.xWrite = i++;
152 state.opSAB = new SharedArrayBuffer(i * 4/*sizeof int32*/);
153 /* The approach of using a single SAB to serialize comms for all
154 instances may(?) lead to deadlock situations in multi-db
155 cases. We should probably have one SAB here with a single slot
156 for locking a per-file initialization step and then allocate a
157 separate SAB like the above one for each file. That will
158 require a bit of acrobatics but should be feasible.
159 */
160 }
161
162 state.sq3Codes = Object.create(null);
163 state.sq3Codes._reverse = Object.create(null);
164 [ // SQLITE_xxx constants to export to the async worker counterpart...
165 'SQLITE_ERROR', 'SQLITE_IOERR',
166 'SQLITE_NOTFOUND', 'SQLITE_MISUSE',
167 'SQLITE_IOERR_READ', 'SQLITE_IOERR_SHORT_READ',
168 'SQLITE_IOERR_WRITE', 'SQLITE_IOERR_FSYNC',
169 'SQLITE_IOERR_TRUNCATE', 'SQLITE_IOERR_DELETE',
170 'SQLITE_IOERR_ACCESS', 'SQLITE_IOERR_CLOSE'
171 ].forEach(function(k){
172 state.sq3Codes[k] = capi[k] || toss("Maintenance required: not found:",k);
173 state.sq3Codes._reverse[capi[k]] = k;
stephan3961b262022-08-10 11:26:08 +0000174 });
stephan3961b262022-08-10 11:26:08 +0000175
stephanc5313af2022-09-18 02:35:30 +0000176 const isWorkerErrCode = (n)=>!!state.sq3Codes._reverse[n];
177 const opStore = (op,val=-1)=>Atomics.store(state.opSABView, state.opIds[op], val);
178 const opWait = (op,val=-1)=>Atomics.wait(state.opSABView, state.opIds[op], val);
stephan3961b262022-08-10 11:26:08 +0000179
stephanc5313af2022-09-18 02:35:30 +0000180 /**
181 Runs the given operation in the async worker counterpart, waits
182 for its response, and returns the result which the async worker
183 writes to the given op's index in state.opSABView. The 2nd argument
184 must be a single object or primitive value, depending on the
185 given operation's signature in the async API counterpart.
186 */
187 const opRun = (op,args)=>{
188 opStore(op);
189 wMsg(op, args);
190 opWait(op);
191 return Atomics.load(state.opSABView, state.opIds[op]);
192 };
193
194 /**
195 Generates a random ASCII string len characters long, intended for
196 use as a temporary file name.
197 */
198 const randomFilename = function f(len=16){
199 if(!f._chars){
200 f._chars = "abcdefghijklmnopqrstuvwxyz"+
201 "ABCDEFGHIJKLMNOPQRSTUVWXYZ"+
202 "012346789";
203 f._n = f._chars.length;
204 }
205 const a = [];
206 let i = 0;
207 for( ; i < len; ++i){
208 const ndx = Math.random() * (f._n * 64) % f._n | 0;
209 a[i] = f._chars[ndx];
210 }
211 return a.join('');
212 };
213
214 /**
215 Map of sqlite3_file pointers to objects constructed by xOpen().
216 */
217 const __openFiles = Object.create(null);
218
219 const pDVfs = capi.sqlite3_vfs_find(null)/*pointer to default VFS*/;
220 const dVfs = pDVfs
221 ? new sqlite3_vfs(pDVfs)
222 : null /* dVfs will be null when sqlite3 is built with
223 SQLITE_OS_OTHER. Though we cannot currently handle
224 that case, the hope is to eventually be able to. */;
225 const opfsVfs = new sqlite3_vfs();
226 const opfsIoMethods = new sqlite3_io_methods();
227 opfsVfs.$iVersion = 2/*yes, two*/;
228 opfsVfs.$szOsFile = capi.sqlite3_file.structInfo.sizeof;
229 opfsVfs.$mxPathname = 1024/*sure, why not?*/;
230 opfsVfs.$zName = wasm.allocCString("opfs");
231 // All C-side memory of opfsVfs is zeroed out, but just to be explicit:
232 opfsVfs.$xDlOpen = opfsVfs.$xDlError = opfsVfs.$xDlSym = opfsVfs.$xDlClose = null;
233 opfsVfs.ondispose = [
234 '$zName', opfsVfs.$zName,
235 'cleanup default VFS wrapper', ()=>(dVfs ? dVfs.dispose() : null),
236 'cleanup opfsIoMethods', ()=>opfsIoMethods.dispose()
237 ];
238 if(dVfs){
239 opfsVfs.$xSleep = dVfs.$xSleep;
240 opfsVfs.$xRandomness = dVfs.$xRandomness;
241 }
242 /**
243 Pedantic sidebar about opfsVfs.ondispose: the entries in that array
244 are items to clean up when opfsVfs.dispose() is called, but in this
245 environment it will never be called. The VFS instance simply
246 hangs around until the WASM module instance is cleaned up. We
247 "could" _hypothetically_ clean it up by "importing" an
248 sqlite3_os_end() impl into the wasm build, but the shutdown order
249 of the wasm engine and the JS one are undefined so there is no
250 guaranty that the opfsVfs instance would be available in one
251 environment or the other when sqlite3_os_end() is called (_if_ it
252 gets called at all in a wasm build, which is undefined).
253 */
254
255 /**
256 Installs a StructBinder-bound function pointer member of the
257 given name and function in the given StructType target object.
258 It creates a WASM proxy for the given function and arranges for
259 that proxy to be cleaned up when tgt.dispose() is called. Throws
260 on the slightest hint of error (e.g. tgt is-not-a StructType,
261 name does not map to a struct-bound member, etc.).
262
263 Returns a proxy for this function which is bound to tgt and takes
264 2 args (name,func). That function returns the same thing,
265 permitting calls to be chained.
266
267 If called with only 1 arg, it has no side effects but returns a
268 func with the same signature as described above.
269 */
270 const installMethod = function callee(tgt, name, func){
271 if(!(tgt instanceof StructBinder.StructType)){
272 toss("Usage error: target object is-not-a StructType.");
273 }
274 if(1===arguments.length){
275 return (n,f)=>callee(tgt,n,f);
276 }
277 if(!callee.argcProxy){
278 callee.argcProxy = function(func,sig){
279 return function(...args){
280 if(func.length!==arguments.length){
281 toss("Argument mismatch. Native signature is:",sig);
282 }
283 return func.apply(this, args);
284 }
285 };
286 callee.removeFuncList = function(){
287 if(this.ondispose.__removeFuncList){
288 this.ondispose.__removeFuncList.forEach(
289 (v,ndx)=>{
290 if('number'===typeof v){
291 try{wasm.uninstallFunction(v)}
292 catch(e){/*ignore*/}
293 }
294 /* else it's a descriptive label for the next number in
295 the list. */
296 }
297 );
298 delete this.ondispose.__removeFuncList;
299 }
300 };
301 }/*static init*/
302 const sigN = tgt.memberSignature(name);
303 if(sigN.length<2){
304 toss("Member",name," is not a function pointer. Signature =",sigN);
305 }
306 const memKey = tgt.memberKey(name);
307 //log("installMethod",tgt, name, sigN);
308 const fProxy = 1
309 // We can remove this proxy middle-man once the VFS is working
310 ? callee.argcProxy(func, sigN)
311 : func;
312 const pFunc = wasm.installFunction(fProxy, tgt.memberSignature(name, true));
313 tgt[memKey] = pFunc;
314 if(!tgt.ondispose) tgt.ondispose = [];
315 if(!tgt.ondispose.__removeFuncList){
316 tgt.ondispose.push('ondispose.__removeFuncList handler',
317 callee.removeFuncList);
318 tgt.ondispose.__removeFuncList = [];
319 }
320 tgt.ondispose.__removeFuncList.push(memKey, pFunc);
321 return (n,f)=>callee(tgt, n, f);
322 }/*installMethod*/;
323
324 /**
325 Impls for the sqlite3_io_methods methods. Maintenance reminder:
326 members are in alphabetical order to simplify finding them.
327 */
328 const ioSyncWrappers = {
329 xCheckReservedLock: function(pFile,pOut){
330 // Exclusive lock is automatically acquired when opened
331 //warn("xCheckReservedLock(",arguments,") is a no-op");
332 wasm.setMemValue(pOut,1,'i32');
333 return 0;
334 },
335 xClose: function(pFile){
336 let rc = 0;
337 const f = __openFiles[pFile];
338 if(f){
339 delete __openFiles[pFile];
340 rc = opRun('xClose', pFile);
341 if(f.sq3File) f.sq3File.dispose();
342 }
343 return rc;
344 },
345 xDeviceCharacteristics: function(pFile){
346 //debug("xDeviceCharacteristics(",pFile,")");
347 return capi.SQLITE_IOCAP_UNDELETABLE_WHEN_OPEN;
348 },
349 xFileControl: function(pFile,op,pArg){
350 //debug("xFileControl(",arguments,") is a no-op");
351 return capi.SQLITE_NOTFOUND;
352 },
353 xFileSize: function(pFile,pSz64){
354 const rc = opRun('xFileSize', pFile);
355 if(!isWorkerErrCode(rc)){
356 const f = __openFiles[pFile];
357 wasm.setMemValue(pSz64, f.sabViewFileSize.getBigInt64(0) ,'i64');
358 }
359 return rc;
360 },
361 xLock: function(pFile,lockType){
362 //2022-09: OPFS handles lock when opened
363 //warn("xLock(",arguments,") is a no-op");
364 return 0;
365 },
366 xRead: function(pFile,pDest,n,offset){
367 /* int (*xRead)(sqlite3_file*, void*, int iAmt, sqlite3_int64 iOfst) */
368 const f = __openFiles[pFile];
369 let rc;
370 try {
371 // FIXME(?): block until we finish copying the xRead result buffer. How?
372 rc = opRun('xRead',{fid:pFile, n, offset});
373 if(0!==rc) return rc;
374 let i = 0;
375 for(; i < n; ++i) wasm.setMemValue(pDest + i, f.sabView[i]);
376 }catch(e){
377 error("xRead(",arguments,") failed:",e,f);
378 rc = capi.SQLITE_IOERR_READ;
379 }
380 return rc;
381 },
382 xSync: function(pFile,flags){
383 return opRun('xSync', {fid:pFile, flags});
384 },
385 xTruncate: function(pFile,sz64){
386 return opRun('xTruncate', {fid:pFile, size: sz64});
387 },
388 xUnlock: function(pFile,lockType){
389 //2022-09: OPFS handles lock when opened
390 //warn("xUnlock(",arguments,") is a no-op");
391 return 0;
392 },
393 xWrite: function(pFile,pSrc,n,offset){
394 /* int (*xWrite)(sqlite3_file*, const void*, int iAmt, sqlite3_int64 iOfst) */
395 const f = __openFiles[pFile];
396 try {
397 let i = 0;
398 // FIXME(?): block from here until we finish the xWrite. How?
399 for(; i < n; ++i) f.sabView[i] = wasm.getMemValue(pSrc+i);
400 return opRun('xWrite',{fid:pFile, n, offset});
401 }catch(e){
402 error("xWrite(",arguments,") failed:",e,f);
403 return capi.SQLITE_IOERR_WRITE;
404 }
405 }
406 }/*ioSyncWrappers*/;
407
408 /**
409 Impls for the sqlite3_vfs methods. Maintenance reminder: members
410 are in alphabetical order to simplify finding them.
411 */
412 const vfsSyncWrappers = {
413 xAccess: function(pVfs,zName,flags,pOut){
414 const rc = opRun('xAccess', wasm.cstringToJs(zName));
415 wasm.setMemValue(pOut, rc ? 0 : 1, 'i32');
416 return 0;
417 },
418 xCurrentTime: function(pVfs,pOut){
419 /* If it turns out that we need to adjust for timezone, see:
420 https://stackoverflow.com/a/11760121/1458521 */
421 wasm.setMemValue(pOut, 2440587.5 + (new Date().getTime()/86400000),
422 'double');
423 return 0;
424 },
425 xCurrentTimeInt64: function(pVfs,pOut){
426 // TODO: confirm that this calculation is correct
427 wasm.setMemValue(pOut, (2440587.5 * 86400000) + new Date().getTime(),
428 'i64');
429 return 0;
430 },
431 xDelete: function(pVfs, zName, doSyncDir){
432 return opRun('xDelete', {filename: wasm.cstringToJs(zName), syncDir: doSyncDir});
433 },
434 xFullPathname: function(pVfs,zName,nOut,pOut){
435 /* Until/unless we have some notion of "current dir"
436 in OPFS, simply copy zName to pOut... */
437 const i = wasm.cstrncpy(pOut, zName, nOut);
438 return i<nOut ? 0 : capi.SQLITE_CANTOPEN
439 /*CANTOPEN is required by the docs but SQLITE_RANGE would be a closer match*/;
440 },
441 xGetLastError: function(pVfs,nOut,pOut){
442 /* TODO: store exception.message values from the async
443 partner in a dedicated SharedArrayBuffer, noting that we'd have
444 to encode them... TextEncoder can do that for us. */
445 warn("OPFS xGetLastError() has nothing sensible to return.");
446 return 0;
447 },
448 xOpen: function f(pVfs, zName, pFile, flags, pOutFlags){
449 if(!f._){
450 f._ = {
451 fileTypes: {
452 SQLITE_OPEN_MAIN_DB: 'mainDb',
453 SQLITE_OPEN_MAIN_JOURNAL: 'mainJournal',
454 SQLITE_OPEN_TEMP_DB: 'tempDb',
455 SQLITE_OPEN_TEMP_JOURNAL: 'tempJournal',
456 SQLITE_OPEN_TRANSIENT_DB: 'transientDb',
457 SQLITE_OPEN_SUBJOURNAL: 'subjournal',
458 SQLITE_OPEN_SUPER_JOURNAL: 'superJournal',
459 SQLITE_OPEN_WAL: 'wal'
460 },
461 getFileType: function(filename,oflags){
462 const ft = f._.fileTypes;
463 for(let k of Object.keys(ft)){
464 if(oflags & capi[k]) return ft[k];
465 }
466 warn("Cannot determine fileType based on xOpen() flags for file",filename);
467 return '???';
468 }
469 };
470 }
471 if(0===zName){
472 zName = randomFilename();
473 }else if('number'===typeof zName){
474 zName = wasm.cstringToJs(zName);
475 }
476 const args = Object.create(null);
477 args.fid = pFile;
478 args.filename = zName;
479 args.sab = new SharedArrayBuffer(state.fileBufferSize);
480 args.fileType = f._.getFileType(args.filename, flags);
481 args.create = !!(flags & capi.SQLITE_OPEN_CREATE);
482 args.deleteOnClose = !!(flags & capi.SQLITE_OPEN_DELETEONCLOSE);
483 args.readOnly = !!(flags & capi.SQLITE_OPEN_READONLY);
484 const rc = opRun('xOpen', args);
485 if(!rc){
486 /* Recall that sqlite3_vfs::xClose() will be called, even on
487 error, unless pFile->pMethods is NULL. */
488 if(args.readOnly){
489 wasm.setMemValue(pOutFlags, capi.SQLITE_OPEN_READONLY, 'i32');
490 }
491 __openFiles[pFile] = args;
492 args.sabView = new Uint8Array(args.sab);
493 args.sabViewFileSize = new DataView(args.sab, state.fbInt64Offset, 8);
494 args.sq3File = new sqlite3_file(pFile);
495 args.sq3File.$pMethods = opfsIoMethods.pointer;
496 args.ba = new Uint8Array(args.sab);
497 }
498 return rc;
499 }/*xOpen()*/
500 }/*vfsSyncWrappers*/;
501
502 if(!opfsVfs.$xRandomness){
503 /* If the default VFS has no xRandomness(), add a basic JS impl... */
504 vfsSyncWrappers.xRandomness = function(pVfs, nOut, pOut){
505 const heap = wasm.heap8u();
506 let i = 0;
507 for(; i < nOut; ++i) heap[pOut + i] = (Math.random()*255000) & 0xFF;
508 return i;
509 };
510 }
511 if(!opfsVfs.$xSleep){
512 /* If we can inherit an xSleep() impl from the default VFS then
513 use it, otherwise install one which is certainly less accurate
514 because it has to go round-trip through the async worker, but
515 provides the only option for a synchronous sleep() in JS. */
516 vfsSyncWrappers.xSleep = (pVfs,ms)=>opRun('xSleep',ms);
517 }
518
519 /* Install the vfs/io_methods into their C-level shared instances... */
520 let inst = installMethod(opfsIoMethods);
521 for(let k of Object.keys(ioSyncWrappers)) inst(k, ioSyncWrappers[k]);
522 inst = installMethod(opfsVfs);
523 for(let k of Object.keys(vfsSyncWrappers)) inst(k, vfsSyncWrappers[k]);
524
525 const sanityCheck = async function(){
526 const scope = wasm.scopedAllocPush();
527 const sq3File = new sqlite3_file();
528 try{
529 const fid = sq3File.pointer;
530 const openFlags = capi.SQLITE_OPEN_CREATE
531 | capi.SQLITE_OPEN_READWRITE
532 //| capi.SQLITE_OPEN_DELETEONCLOSE
533 | capi.SQLITE_OPEN_MAIN_DB;
534 const pOut = wasm.scopedAlloc(8);
535 const dbFile = "/sanity/check/file";
536 const zDbFile = wasm.scopedAllocCString(dbFile);
537 let rc;
538 vfsSyncWrappers.xAccess(opfsVfs.pointer, zDbFile, 0, pOut);
539 rc = wasm.getMemValue(pOut,'i32');
540 log("xAccess(",dbFile,") exists ?=",rc);
541 rc = vfsSyncWrappers.xOpen(opfsVfs.pointer, zDbFile,
542 fid, openFlags, pOut);
543 log("open rc =",rc,"state.opSABView[xOpen] =",state.opSABView[state.opIds.xOpen]);
544 if(isWorkerErrCode(rc)){
545 error("open failed with code",rc);
546 return;
547 }
548 vfsSyncWrappers.xAccess(opfsVfs.pointer, zDbFile, 0, pOut);
549 rc = wasm.getMemValue(pOut,'i32');
550 if(!rc) toss("xAccess() failed to detect file.");
551 rc = ioSyncWrappers.xSync(sq3File.pointer, 0);
552 if(rc) toss('sync failed w/ rc',rc);
553 rc = ioSyncWrappers.xTruncate(sq3File.pointer, 1024);
554 if(rc) toss('truncate failed w/ rc',rc);
555 wasm.setMemValue(pOut,0,'i64');
556 rc = ioSyncWrappers.xFileSize(sq3File.pointer, pOut);
557 if(rc) toss('xFileSize failed w/ rc',rc);
558 log("xFileSize says:",wasm.getMemValue(pOut, 'i64'));
559 rc = ioSyncWrappers.xWrite(sq3File.pointer, zDbFile, 10, 1);
560 if(rc) toss("xWrite() failed!");
561 const readBuf = wasm.scopedAlloc(16);
562 rc = ioSyncWrappers.xRead(sq3File.pointer, readBuf, 6, 2);
563 wasm.setMemValue(readBuf+6,0);
564 let jRead = wasm.cstringToJs(readBuf);
565 log("xRead() got:",jRead);
566 if("sanity"!==jRead) toss("Unexpected xRead() value.");
567 log("xSleep()ing before close()ing...");
568 opRun('xSleep',1000);
569 rc = ioSyncWrappers.xClose(fid);
570 log("xClose rc =",rc,"opSABView =",state.opSABView);
571 log("Deleting file:",dbFile);
572 vfsSyncWrappers.xDelete(opfsVfs.pointer, zDbFile, 0x1234);
573 vfsSyncWrappers.xAccess(opfsVfs.pointer, zDbFile, 0, pOut);
574 rc = wasm.getMemValue(pOut,'i32');
575 if(rc) toss("Expecting 0 from xAccess(",dbFile,") after xDelete().");
576 }finally{
577 sq3File.dispose();
578 wasm.scopedAllocPop(scope);
579 }
580 }/*sanityCheck()*/;
581
582 W.onmessage = function({data}){
583 //log("Worker.onmessage:",data);
584 switch(data.type){
585 case 'loaded':
586 /*Pass our config and shared state on to the async worker.*/
587 wMsg('init',state);
588 break;
589 case 'inited':{
590 /*Indicates that the async partner has received the 'init',
591 so we now know that the state object is no longer subject to
592 being copied by a pending postMessage() call.*/
593 try {
594 const rc = capi.sqlite3_vfs_register(opfsVfs.pointer, opfsVfs.$zName);
595 if(rc){
596 opfsVfs.dispose();
597 toss("sqlite3_vfs_register(OPFS) failed with rc",rc);
598 }
599 if(opfsVfs.pointer !== capi.sqlite3_vfs_find("opfs")){
600 toss("BUG: sqlite3_vfs_find() failed for just-installed OPFS VFS");
601 }
602 capi.sqlite3_vfs_register.addReference(opfsVfs, opfsIoMethods);
603 state.opSABView = new Int32Array(state.opSAB);
604 if(options.sanityChecks){
605 warn("Running sanity checks because of opfs-sanity-check URL arg...");
606 sanityCheck();
607 }
608 W.onerror = workerOrigOnError;
609 promiseResolve(sqlite3);
610 log("End of OPFS sqlite3_vfs setup.", opfsVfs);
611 }catch(e){
612 error(e);
613 promiseReject(e);
614 }
615 break;
616 }
617 default:
618 promiseReject(e);
619 error("Unexpected message from the async worker:",data);
620 break;
621 }
622 };
623 })/*thePromise*/;
624 return thePromise;
625}/*installOpfsVfs()*/;
626sqlite3.installOpfsVfs.defaultProxyUri = "sqlite3-opfs-async-proxy.js";
627}/*sqlite3ApiBootstrap.initializers.push()*/);