blob: 4e9b7328cad51c0c21e1a8bea7bccecc88dfe774 [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
stephanf3860122022-09-18 17:32:35 +000045 threads. This file tries to detect that case, resulting in a
46 rejected Promise if those features do not seem to be available.
stephanc5313af2022-09-18 02:35:30 +000047
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
stephanf3860122022-09-18 17:32:35 +000075 object and that object gets a new object installed in its
76 `opfs` property, containing several OPFS-specific utilities.
stephan3961b262022-08-10 11:26:08 +000077*/
stephanc5313af2022-09-18 02:35:30 +000078sqlite3.installOpfsVfs = function callee(asyncProxyUri = callee.defaultProxyUri){
stephan509f4052022-09-19 09:58:01 +000079 delete sqlite3.installOpfsVfs;
stephan4cffb642022-09-20 16:20:35 +000080 if(!self.SharedArrayBuffer ||
stephan509f4052022-09-19 09:58:01 +000081 !self.FileSystemHandle ||
82 !self.FileSystemDirectoryHandle ||
83 !self.FileSystemFileHandle ||
84 !self.FileSystemFileHandle.prototype.createSyncAccessHandle ||
85 !navigator.storage.getDirectory){
86 return Promise.reject(
87 new Error("This environment does not have OPFS support.")
88 );
89 }
stephanc5313af2022-09-18 02:35:30 +000090 const options = (asyncProxyUri && 'object'===asyncProxyUri) ? asyncProxyUri : {
91 proxyUri: asyncProxyUri
stephan3961b262022-08-10 11:26:08 +000092 };
stephan509f4052022-09-19 09:58:01 +000093 const urlParams = new URL(self.location.href).searchParams;
stephanc5313af2022-09-18 02:35:30 +000094 if(undefined===options.verbose){
stephan509f4052022-09-19 09:58:01 +000095 options.verbose = urlParams.has('opfs-verbose') ? 3 : 2;
stephan3961b262022-08-10 11:26:08 +000096 }
stephanc5313af2022-09-18 02:35:30 +000097 if(undefined===options.sanityChecks){
stephan509f4052022-09-19 09:58:01 +000098 options.sanityChecks = urlParams.has('opfs-sanity-check');
stephanc5313af2022-09-18 02:35:30 +000099 }
100 if(undefined===options.proxyUri){
101 options.proxyUri = callee.defaultProxyUri;
102 }
stephanf3860122022-09-18 17:32:35 +0000103
stephanc5313af2022-09-18 02:35:30 +0000104 const thePromise = new Promise(function(promiseResolve, promiseReject){
stephan509f4052022-09-19 09:58:01 +0000105 const loggers = {
106 0:console.error.bind(console),
107 1:console.warn.bind(console),
108 2:console.log.bind(console)
stephanc5313af2022-09-18 02:35:30 +0000109 };
stephan509f4052022-09-19 09:58:01 +0000110 const logImpl = (level,...args)=>{
111 if(options.verbose>level) loggers[level]("OPFS syncer:",...args);
112 };
113 const log = (...args)=>logImpl(2, ...args);
114 const warn = (...args)=>logImpl(1, ...args);
115 const error = (...args)=>logImpl(0, ...args);
stephanc5313af2022-09-18 02:35:30 +0000116 warn("The OPFS VFS feature is very much experimental and under construction.");
117 const toss = function(...args){throw new Error(args.join(' '))};
stephanc5313af2022-09-18 02:35:30 +0000118 const capi = sqlite3.capi;
119 const wasm = capi.wasm;
120 const sqlite3_vfs = capi.sqlite3_vfs;
121 const sqlite3_file = capi.sqlite3_file;
122 const sqlite3_io_methods = capi.sqlite3_io_methods;
stephanc5313af2022-09-18 02:35:30 +0000123 const W = new Worker(options.proxyUri);
stephanf3860122022-09-18 17:32:35 +0000124 W._originalOnError = W.onerror /* will be restored later */;
stephanc5313af2022-09-18 02:35:30 +0000125 W.onerror = function(err){
stephan509f4052022-09-19 09:58:01 +0000126 // The error object doesn't contain any useful info when the
127 // failure is, e.g., that the remote script is 404.
stephanc5313af2022-09-18 02:35:30 +0000128 promiseReject(new Error("Loading OPFS async Worker failed for unknown reasons."));
129 };
stephan509f4052022-09-19 09:58:01 +0000130 /**
131 Generic utilities for working with OPFS. This will get filled out
132 by the Promise setup and, on success, installed as sqlite3.opfs.
133 */
134 const opfsUtil = Object.create(null);
stephanf8150112022-09-19 17:09:09 +0000135 /**
136 Not part of the public API. Solely for internal/development
137 use.
138 */
139 opfsUtil.metrics = {
140 dump: function(){
stephanaec046a2022-09-19 18:22:29 +0000141 let k, n = 0, t = 0, w = 0;
142 for(k in state.opIds){
stephanf8150112022-09-19 17:09:09 +0000143 const m = metrics[k];
144 n += m.count;
145 t += m.time;
stephanaec046a2022-09-19 18:22:29 +0000146 w += m.wait;
stephanf8150112022-09-19 17:09:09 +0000147 m.avgTime = (m.count && m.time) ? (m.time / m.count) : 0;
148 m.avgWait = (m.count && m.wait) ? (m.wait / m.count) : 0;
149 }
stephanaec046a2022-09-19 18:22:29 +0000150 console.log(self.location.href,
151 "metrics for",self.location.href,":",metrics,
152 "\nTotal of",n,"op(s) for",t,
153 "ms (incl. "+w+" ms of waiting on the async side)");
stephanb8c8d4e2022-09-20 13:25:39 +0000154 console.log("Serialization metrics:",JSON.stringify(metrics.s11n,0,2));
stephanf8150112022-09-19 17:09:09 +0000155 },
156 reset: function(){
157 let k;
158 const r = (m)=>(m.count = m.time = m.wait = 0);
159 for(k in state.opIds){
160 r(metrics[k] = Object.create(null));
161 }
stephanb8c8d4e2022-09-20 13:25:39 +0000162 let s = metrics.s11n = Object.create(null);
163 s = s.serialize = Object.create(null);
164 s.count = s.time = 0;
165 s = metrics.s11n.deserialize = Object.create(null);
166 s.count = s.time = 0;
stephanaec046a2022-09-19 18:22:29 +0000167 //[ // timed routines which are not in state.opIds
168 // 'xFileControl'
169 //].forEach((k)=>r(metrics[k] = Object.create(null)));
stephanf8150112022-09-19 17:09:09 +0000170 }
171 }/*metrics*/;
stephanc5313af2022-09-18 02:35:30 +0000172
stephanc9e26022022-09-20 10:11:52 +0000173 const pDVfs = capi.sqlite3_vfs_find(null)/*pointer to default VFS*/;
174 const dVfs = pDVfs
175 ? new sqlite3_vfs(pDVfs)
176 : null /* dVfs will be null when sqlite3 is built with
177 SQLITE_OS_OTHER. Though we cannot currently handle
178 that case, the hope is to eventually be able to. */;
179 const opfsVfs = new sqlite3_vfs();
180 const opfsIoMethods = new sqlite3_io_methods();
181 opfsVfs.$iVersion = 2/*yes, two*/;
182 opfsVfs.$szOsFile = capi.sqlite3_file.structInfo.sizeof;
183 opfsVfs.$mxPathname = 1024/*sure, why not?*/;
184 opfsVfs.$zName = wasm.allocCString("opfs");
185 // All C-side memory of opfsVfs is zeroed out, but just to be explicit:
186 opfsVfs.$xDlOpen = opfsVfs.$xDlError = opfsVfs.$xDlSym = opfsVfs.$xDlClose = null;
187 opfsVfs.ondispose = [
188 '$zName', opfsVfs.$zName,
189 'cleanup default VFS wrapper', ()=>(dVfs ? dVfs.dispose() : null),
190 'cleanup opfsIoMethods', ()=>opfsIoMethods.dispose()
191 ];
192 /**
193 Pedantic sidebar about opfsVfs.ondispose: the entries in that array
194 are items to clean up when opfsVfs.dispose() is called, but in this
195 environment it will never be called. The VFS instance simply
196 hangs around until the WASM module instance is cleaned up. We
197 "could" _hypothetically_ clean it up by "importing" an
198 sqlite3_os_end() impl into the wasm build, but the shutdown order
199 of the wasm engine and the JS one are undefined so there is no
200 guaranty that the opfsVfs instance would be available in one
201 environment or the other when sqlite3_os_end() is called (_if_ it
202 gets called at all in a wasm build, which is undefined).
203 */
204
stephanc5313af2022-09-18 02:35:30 +0000205 /**
206 State which we send to the async-api Worker or share with it.
207 This object must initially contain only cloneable or sharable
208 objects. After the worker's "inited" message arrives, other types
209 of data may be added to it.
stephanf3860122022-09-18 17:32:35 +0000210
211 For purposes of Atomics.wait() and Atomics.notify(), we use a
212 SharedArrayBuffer with one slot reserved for each of the API
213 proxy's methods. The sync side of the API uses Atomics.wait()
214 on the corresponding slot and the async side uses
215 Atomics.notify() on that slot.
216
217 The approach of using a single SAB to serialize comms for all
218 instances might(?) lead to deadlock situations in multi-db
219 cases. We should probably have one SAB here with a single slot
220 for locking a per-file initialization step and then allocate a
221 separate SAB like the above one for each file. That will
222 require a bit of acrobatics but should be feasible.
stephanc5313af2022-09-18 02:35:30 +0000223 */
224 const state = Object.create(null);
stephan138647a2022-09-20 03:31:02 +0000225 state.littleEndian = true;
stephanc5313af2022-09-18 02:35:30 +0000226 state.verbose = options.verbose;
stephanc9e26022022-09-20 10:11:52 +0000227 /* Size of file I/O buffer block. 64k = max sqlite3 page size. */
stephanf3860122022-09-18 17:32:35 +0000228 state.fileBufferSize =
stephanc9e26022022-09-20 10:11:52 +0000229 1024 * 64;
stephan138647a2022-09-20 03:31:02 +0000230 state.sabS11nOffset = state.fileBufferSize;
stephanc9e26022022-09-20 10:11:52 +0000231 /**
232 The size of the block in our SAB for serializing arguments and
233 result values. Need to be large enough to hold serialized
234 values of any of the proxied APIs. Filenames are the largest
235 part but are limited to opfsVfs.$mxPathname bytes.
236 */
237 state.sabS11nSize = opfsVfs.$mxPathname * 2;
238 /**
239 The SAB used for all data I/O (files and arg/result s11n).
240 */
stephanc4b87be2022-09-20 01:28:47 +0000241 state.sabIO = new SharedArrayBuffer(
stephanc9e26022022-09-20 10:11:52 +0000242 state.fileBufferSize/* file i/o block */
243 + state.sabS11nSize/* argument/result serialization block */
stephanc4b87be2022-09-20 01:28:47 +0000244 );
stephanc5313af2022-09-18 02:35:30 +0000245 state.opIds = Object.create(null);
stephanf8150112022-09-19 17:09:09 +0000246 const metrics = Object.create(null);
stephanc5313af2022-09-18 02:35:30 +0000247 {
stephanc9e26022022-09-20 10:11:52 +0000248 /* Indexes for use in our SharedArrayBuffer... */
stephan3961b262022-08-10 11:26:08 +0000249 let i = 0;
stephanc9e26022022-09-20 10:11:52 +0000250 /* SAB slot used to communicate which operation is desired
251 between both workers. This worker writes to it and the other
252 listens for changes. */
stephan138647a2022-09-20 03:31:02 +0000253 state.opIds.whichOp = i++;
stephanc9e26022022-09-20 10:11:52 +0000254 /* Slot for storing return values. This work listens to that
255 slot and the other worker writes to it. */
256 state.opIds.rc = i++;
257 /* Each function gets an ID which this worker writes to
258 the whichOp slot. The async-api worker uses Atomic.wait()
259 on the whichOp slot to figure out which operation to run
260 next. */
stephanc5313af2022-09-18 02:35:30 +0000261 state.opIds.xAccess = i++;
262 state.opIds.xClose = i++;
263 state.opIds.xDelete = i++;
stephanf3860122022-09-18 17:32:35 +0000264 state.opIds.xDeleteNoWait = i++;
stephanc5313af2022-09-18 02:35:30 +0000265 state.opIds.xFileSize = i++;
266 state.opIds.xOpen = i++;
267 state.opIds.xRead = i++;
268 state.opIds.xSleep = i++;
269 state.opIds.xSync = i++;
270 state.opIds.xTruncate = i++;
271 state.opIds.xWrite = i++;
stephanf3860122022-09-18 17:32:35 +0000272 state.opIds.mkdir = i++;
stephan5e8bb0a2022-09-20 08:27:57 +0000273 state.opIds.xFileControl = i++;
stephanc4b87be2022-09-20 01:28:47 +0000274 state.sabOP = new SharedArrayBuffer(i * 4/*sizeof int32*/);
stephanf8150112022-09-19 17:09:09 +0000275 opfsUtil.metrics.reset();
stephanc5313af2022-09-18 02:35:30 +0000276 }
277
stephanc9e26022022-09-20 10:11:52 +0000278 /**
279 SQLITE_xxx constants to export to the async worker
280 counterpart...
281 */
stephanc5313af2022-09-18 02:35:30 +0000282 state.sq3Codes = Object.create(null);
283 state.sq3Codes._reverse = Object.create(null);
stephanc9e26022022-09-20 10:11:52 +0000284 [
stephanc5313af2022-09-18 02:35:30 +0000285 'SQLITE_ERROR', 'SQLITE_IOERR',
286 'SQLITE_NOTFOUND', 'SQLITE_MISUSE',
287 'SQLITE_IOERR_READ', 'SQLITE_IOERR_SHORT_READ',
288 'SQLITE_IOERR_WRITE', 'SQLITE_IOERR_FSYNC',
289 'SQLITE_IOERR_TRUNCATE', 'SQLITE_IOERR_DELETE',
stephanf3860122022-09-18 17:32:35 +0000290 'SQLITE_IOERR_ACCESS', 'SQLITE_IOERR_CLOSE',
stephanc4b87be2022-09-20 01:28:47 +0000291 'SQLITE_IOERR_DELETE',
292 'SQLITE_OPEN_CREATE', 'SQLITE_OPEN_DELETEONCLOSE',
293 'SQLITE_OPEN_READONLY'
stephanc5313af2022-09-18 02:35:30 +0000294 ].forEach(function(k){
295 state.sq3Codes[k] = capi[k] || toss("Maintenance required: not found:",k);
296 state.sq3Codes._reverse[capi[k]] = k;
stephan3961b262022-08-10 11:26:08 +0000297 });
stephan3961b262022-08-10 11:26:08 +0000298
stephanc5313af2022-09-18 02:35:30 +0000299 const isWorkerErrCode = (n)=>!!state.sq3Codes._reverse[n];
stephan3961b262022-08-10 11:26:08 +0000300
stephanc5313af2022-09-18 02:35:30 +0000301 /**
stephanc9e26022022-09-20 10:11:52 +0000302 Runs the given operation (by name) in the async worker
303 counterpart, waits for its response, and returns the result
304 which the async worker writes to SAB[state.opIds.rc]. The
305 2nd and subsequent arguments must be the aruguments for the
306 async op.
stephanc5313af2022-09-18 02:35:30 +0000307 */
stephan138647a2022-09-20 03:31:02 +0000308 const opRun = (op,...args)=>{
stephan5e8bb0a2022-09-20 08:27:57 +0000309 const opNdx = state.opIds[op] || toss("Invalid op ID:",op);
310 state.s11n.serialize(...args);
stephanc9e26022022-09-20 10:11:52 +0000311 Atomics.store(state.sabOPView, state.opIds.rc, -1);
stephan5e8bb0a2022-09-20 08:27:57 +0000312 Atomics.store(state.sabOPView, state.opIds.whichOp, opNdx);
313 Atomics.notify(state.sabOPView, state.opIds.whichOp) /* async thread will take over here */;
stephanf8150112022-09-19 17:09:09 +0000314 const t = performance.now();
stephanc9e26022022-09-20 10:11:52 +0000315 Atomics.wait(state.sabOPView, state.opIds.rc, -1);
316 const rc = Atomics.load(state.sabOPView, state.opIds.rc);
stephanf8150112022-09-19 17:09:09 +0000317 metrics[op].wait += performance.now() - t;
stephan5e8bb0a2022-09-20 08:27:57 +0000318 return rc;
stephanc5313af2022-09-18 02:35:30 +0000319 };
320
stephan138647a2022-09-20 03:31:02 +0000321 const initS11n = ()=>{
stephanb8c8d4e2022-09-20 13:25:39 +0000322 /**
323 ACHTUNG: this code is 100% duplicated in the other half of
324 this proxy!
325
326 Historical note: this impl was initially about 5% this size by using
327 using JSON.stringify/parse(), but using fit-to-purpose serialization
328 saves considerable runtime.
329 */
stephan138647a2022-09-20 03:31:02 +0000330 if(state.s11n) return state.s11n;
stephanb8c8d4e2022-09-20 13:25:39 +0000331 const textDecoder = new TextDecoder(),
332 textEncoder = new TextEncoder('utf-8'),
333 viewU8 = new Uint8Array(state.sabIO, state.sabS11nOffset, state.sabS11nSize),
334 viewDV = new DataView(state.sabIO, state.sabS11nOffset, state.sabS11nSize);
stephan138647a2022-09-20 03:31:02 +0000335 state.s11n = Object.create(null);
stephanb8c8d4e2022-09-20 13:25:39 +0000336 const TypeIds = Object.create(null);
337 TypeIds.number = { id: 1, size: 8, getter: 'getFloat64', setter: 'setFloat64' };
338 TypeIds.bigint = { id: 2, size: 8, getter: 'getBigInt64', setter: 'setBigInt64' };
339 TypeIds.boolean = { id: 3, size: 4, getter: 'getInt32', setter: 'setInt32' };
340 TypeIds.string = { id: 4 };
341 const getTypeId = (v)=>{
342 return TypeIds[typeof v] || toss("This value type cannot be serialized.",v);
343 };
344 const getTypeIdById = (tid)=>{
345 switch(tid){
346 case TypeIds.number.id: return TypeIds.number;
347 case TypeIds.bigint.id: return TypeIds.bigint;
348 case TypeIds.boolean.id: return TypeIds.boolean;
349 case TypeIds.string.id: return TypeIds.string;
350 default: toss("Invalid type ID:",tid);
351 }
352 };
stephan138647a2022-09-20 03:31:02 +0000353 /**
354 Returns an array of the state serialized by the most recent
355 serialize() operation (here or in the counterpart thread), or
356 null if the serialization buffer is empty.
357 */
358 state.s11n.deserialize = function(){
stephanb8c8d4e2022-09-20 13:25:39 +0000359 ++metrics.s11n.deserialize.count;
360 const t = performance.now();
361 let rc = null;
362 const argc = viewU8[0];
363 if(argc){
364 rc = [];
365 let offset = 1, i, n, v, typeIds = [];
366 for(i = 0; i < argc; ++i, ++offset){
367 typeIds.push(getTypeIdById(viewU8[offset]));
368 }
369 for(i = 0; i < argc; ++i){
370 const t = typeIds[i];
371 if(t.getter){
372 v = viewDV[t.getter](offset, state.littleEndian);
373 offset += t.size;
374 }else{
375 n = viewDV.getInt32(offset, state.littleEndian);
376 offset += 4;
377 v = textDecoder.decode(viewU8.slice(offset, offset+n));
378 offset += n;
379 }
380 rc.push(v);
381 }
382 }
383 //log("deserialize:",argc, rc);
384 metrics.s11n.deserialize.time += performance.now() - t;
385 return rc;
386 };
stephan138647a2022-09-20 03:31:02 +0000387 /**
388 Serializes all arguments to the shared buffer for consumption
stephanb8c8d4e2022-09-20 13:25:39 +0000389 by the counterpart thread.
stephan5e8bb0a2022-09-20 08:27:57 +0000390
stephanb8c8d4e2022-09-20 13:25:39 +0000391 This routine is only intended for serializing OPFS VFS
392 arguments and (in at least one special case) result values,
393 and the buffer is sized to be able to comfortably handle
394 those.
stephan5e8bb0a2022-09-20 08:27:57 +0000395
396 If passed no arguments then it zeroes out the serialization
397 state.
stephan138647a2022-09-20 03:31:02 +0000398 */
399 state.s11n.serialize = function(...args){
stephanb8c8d4e2022-09-20 13:25:39 +0000400 ++metrics.s11n.serialize.count;
401 const t = performance.now();
stephan5e8bb0a2022-09-20 08:27:57 +0000402 if(args.length){
stephanb8c8d4e2022-09-20 13:25:39 +0000403 //log("serialize():",args);
404 let i = 0, offset = 1, typeIds = [];
405 viewU8[0] = args.length & 0xff;
406 for(; i < args.length; ++i, ++offset){
407 typeIds.push(getTypeId(args[i]));
408 viewU8[offset] = typeIds[i].id;
409 }
410 for(i = 0; i < args.length; ++i) {
411 const t = typeIds[i];
412 if(t.setter){
413 viewDV[t.setter](offset, args[i], state.littleEndian);
414 offset += t.size;
415 }else{
416 const s = textEncoder.encode(args[i]);
417 viewDV.setInt32(offset, s.byteLength, state.littleEndian);
418 offset += 4;
419 viewU8.set(s, offset);
420 offset += s.byteLength;
421 }
422 }
423 //log("serialize() result:",viewU8.slice(0,offset));
stephan5e8bb0a2022-09-20 08:27:57 +0000424 }else{
stephanb8c8d4e2022-09-20 13:25:39 +0000425 viewU8[0] = 0;
stephan5e8bb0a2022-09-20 08:27:57 +0000426 }
stephanb8c8d4e2022-09-20 13:25:39 +0000427 metrics.s11n.serialize.time += performance.now() - t;
stephan138647a2022-09-20 03:31:02 +0000428 };
429 return state.s11n;
stephanb8c8d4e2022-09-20 13:25:39 +0000430 }/*initS11n()*/;
stephan138647a2022-09-20 03:31:02 +0000431
stephanc5313af2022-09-18 02:35:30 +0000432 /**
433 Generates a random ASCII string len characters long, intended for
434 use as a temporary file name.
435 */
436 const randomFilename = function f(len=16){
437 if(!f._chars){
438 f._chars = "abcdefghijklmnopqrstuvwxyz"+
439 "ABCDEFGHIJKLMNOPQRSTUVWXYZ"+
440 "012346789";
441 f._n = f._chars.length;
442 }
443 const a = [];
444 let i = 0;
445 for( ; i < len; ++i){
446 const ndx = Math.random() * (f._n * 64) % f._n | 0;
447 a[i] = f._chars[ndx];
448 }
449 return a.join('');
450 };
451
452 /**
453 Map of sqlite3_file pointers to objects constructed by xOpen().
454 */
455 const __openFiles = Object.create(null);
stephanc5313af2022-09-18 02:35:30 +0000456
457 /**
458 Installs a StructBinder-bound function pointer member of the
459 given name and function in the given StructType target object.
460 It creates a WASM proxy for the given function and arranges for
461 that proxy to be cleaned up when tgt.dispose() is called. Throws
462 on the slightest hint of error (e.g. tgt is-not-a StructType,
463 name does not map to a struct-bound member, etc.).
464
465 Returns a proxy for this function which is bound to tgt and takes
466 2 args (name,func). That function returns the same thing,
467 permitting calls to be chained.
468
469 If called with only 1 arg, it has no side effects but returns a
470 func with the same signature as described above.
471 */
472 const installMethod = function callee(tgt, name, func){
stephanf3860122022-09-18 17:32:35 +0000473 if(!(tgt instanceof sqlite3.StructBinder.StructType)){
stephanc5313af2022-09-18 02:35:30 +0000474 toss("Usage error: target object is-not-a StructType.");
475 }
476 if(1===arguments.length){
477 return (n,f)=>callee(tgt,n,f);
478 }
479 if(!callee.argcProxy){
480 callee.argcProxy = function(func,sig){
481 return function(...args){
482 if(func.length!==arguments.length){
483 toss("Argument mismatch. Native signature is:",sig);
484 }
485 return func.apply(this, args);
486 }
487 };
488 callee.removeFuncList = function(){
489 if(this.ondispose.__removeFuncList){
490 this.ondispose.__removeFuncList.forEach(
491 (v,ndx)=>{
492 if('number'===typeof v){
493 try{wasm.uninstallFunction(v)}
494 catch(e){/*ignore*/}
495 }
496 /* else it's a descriptive label for the next number in
497 the list. */
498 }
499 );
500 delete this.ondispose.__removeFuncList;
501 }
502 };
503 }/*static init*/
504 const sigN = tgt.memberSignature(name);
505 if(sigN.length<2){
506 toss("Member",name," is not a function pointer. Signature =",sigN);
507 }
508 const memKey = tgt.memberKey(name);
509 //log("installMethod",tgt, name, sigN);
stephanc2ccd672022-09-20 10:47:36 +0000510 const fProxy = 0
stephanc5313af2022-09-18 02:35:30 +0000511 // We can remove this proxy middle-man once the VFS is working
512 ? callee.argcProxy(func, sigN)
513 : func;
514 const pFunc = wasm.installFunction(fProxy, tgt.memberSignature(name, true));
515 tgt[memKey] = pFunc;
516 if(!tgt.ondispose) tgt.ondispose = [];
517 if(!tgt.ondispose.__removeFuncList){
518 tgt.ondispose.push('ondispose.__removeFuncList handler',
519 callee.removeFuncList);
520 tgt.ondispose.__removeFuncList = [];
521 }
522 tgt.ondispose.__removeFuncList.push(memKey, pFunc);
523 return (n,f)=>callee(tgt, n, f);
524 }/*installMethod*/;
stephanf8150112022-09-19 17:09:09 +0000525
526 const opTimer = Object.create(null);
527 opTimer.op = undefined;
528 opTimer.start = undefined;
529 const mTimeStart = (op)=>{
530 opTimer.start = performance.now();
531 opTimer.op = op;
532 //metrics[op] || toss("Maintenance required: missing metrics for",op);
533 ++metrics[op].count;
534 };
535 const mTimeEnd = ()=>(
536 metrics[opTimer.op].time += performance.now() - opTimer.start
537 );
538
stephanc5313af2022-09-18 02:35:30 +0000539 /**
540 Impls for the sqlite3_io_methods methods. Maintenance reminder:
541 members are in alphabetical order to simplify finding them.
542 */
543 const ioSyncWrappers = {
544 xCheckReservedLock: function(pFile,pOut){
545 // Exclusive lock is automatically acquired when opened
546 //warn("xCheckReservedLock(",arguments,") is a no-op");
547 wasm.setMemValue(pOut,1,'i32');
548 return 0;
549 },
550 xClose: function(pFile){
stephanf8150112022-09-19 17:09:09 +0000551 mTimeStart('xClose');
stephanc5313af2022-09-18 02:35:30 +0000552 let rc = 0;
553 const f = __openFiles[pFile];
554 if(f){
555 delete __openFiles[pFile];
556 rc = opRun('xClose', pFile);
557 if(f.sq3File) f.sq3File.dispose();
558 }
stephanf8150112022-09-19 17:09:09 +0000559 mTimeEnd();
stephanc5313af2022-09-18 02:35:30 +0000560 return rc;
561 },
562 xDeviceCharacteristics: function(pFile){
563 //debug("xDeviceCharacteristics(",pFile,")");
564 return capi.SQLITE_IOCAP_UNDELETABLE_WHEN_OPEN;
565 },
stephanf8150112022-09-19 17:09:09 +0000566 xFileControl: function(pFile, opId, pArg){
567 mTimeStart('xFileControl');
stephanaec046a2022-09-19 18:22:29 +0000568 const rc = (capi.SQLITE_FCNTL_SYNC===opId)
stephan138647a2022-09-20 03:31:02 +0000569 ? opRun('xSync', pFile, 0)
stephanaec046a2022-09-19 18:22:29 +0000570 : capi.SQLITE_NOTFOUND;
stephanf8150112022-09-19 17:09:09 +0000571 mTimeEnd();
stephanaec046a2022-09-19 18:22:29 +0000572 return rc;
stephanc5313af2022-09-18 02:35:30 +0000573 },
574 xFileSize: function(pFile,pSz64){
stephanf8150112022-09-19 17:09:09 +0000575 mTimeStart('xFileSize');
stephanc5313af2022-09-18 02:35:30 +0000576 const rc = opRun('xFileSize', pFile);
577 if(!isWorkerErrCode(rc)){
stephan138647a2022-09-20 03:31:02 +0000578 const sz = state.s11n.deserialize()[0];
579 wasm.setMemValue(pSz64, BigInt(sz), 'i64');
stephanc5313af2022-09-18 02:35:30 +0000580 }
stephanf8150112022-09-19 17:09:09 +0000581 mTimeEnd();
stephanc5313af2022-09-18 02:35:30 +0000582 return rc;
583 },
584 xLock: function(pFile,lockType){
585 //2022-09: OPFS handles lock when opened
586 //warn("xLock(",arguments,") is a no-op");
587 return 0;
588 },
stephan138647a2022-09-20 03:31:02 +0000589 xRead: function(pFile,pDest,n,offset64){
stephanc5313af2022-09-18 02:35:30 +0000590 /* int (*xRead)(sqlite3_file*, void*, int iAmt, sqlite3_int64 iOfst) */
stephanf8150112022-09-19 17:09:09 +0000591 mTimeStart('xRead');
stephanc5313af2022-09-18 02:35:30 +0000592 const f = __openFiles[pFile];
593 let rc;
594 try {
stephan138647a2022-09-20 03:31:02 +0000595 rc = opRun('xRead',pFile, n, Number(offset64));
stephan862281f2022-09-19 09:25:25 +0000596 if(0===rc || capi.SQLITE_IOERR_SHORT_READ===rc){
stephanf8150112022-09-19 17:09:09 +0000597 // set() seems to be the fastest way to copy this...
598 wasm.heap8u().set(f.sabView.subarray(0, n), pDest);
stephan862281f2022-09-19 09:25:25 +0000599 }
stephanc5313af2022-09-18 02:35:30 +0000600 }catch(e){
601 error("xRead(",arguments,") failed:",e,f);
602 rc = capi.SQLITE_IOERR_READ;
603 }
stephanf8150112022-09-19 17:09:09 +0000604 mTimeEnd();
stephanc5313af2022-09-18 02:35:30 +0000605 return rc;
606 },
607 xSync: function(pFile,flags){
stephanaec046a2022-09-19 18:22:29 +0000608 ++metrics.xSync.count;
stephan138647a2022-09-20 03:31:02 +0000609 return 0; // impl'd in xFileControl()
stephanc5313af2022-09-18 02:35:30 +0000610 },
611 xTruncate: function(pFile,sz64){
stephanf8150112022-09-19 17:09:09 +0000612 mTimeStart('xTruncate');
stephan138647a2022-09-20 03:31:02 +0000613 const rc = opRun('xTruncate', pFile, Number(sz64));
stephanf8150112022-09-19 17:09:09 +0000614 mTimeEnd();
615 return rc;
stephanc5313af2022-09-18 02:35:30 +0000616 },
617 xUnlock: function(pFile,lockType){
618 //2022-09: OPFS handles lock when opened
619 //warn("xUnlock(",arguments,") is a no-op");
620 return 0;
621 },
stephan138647a2022-09-20 03:31:02 +0000622 xWrite: function(pFile,pSrc,n,offset64){
stephanc5313af2022-09-18 02:35:30 +0000623 /* int (*xWrite)(sqlite3_file*, const void*, int iAmt, sqlite3_int64 iOfst) */
stephanf8150112022-09-19 17:09:09 +0000624 mTimeStart('xWrite');
stephanc5313af2022-09-18 02:35:30 +0000625 const f = __openFiles[pFile];
stephanf8150112022-09-19 17:09:09 +0000626 let rc;
stephanc5313af2022-09-18 02:35:30 +0000627 try {
stephanf8150112022-09-19 17:09:09 +0000628 f.sabView.set(wasm.heap8u().subarray(pSrc, pSrc+n));
stephan138647a2022-09-20 03:31:02 +0000629 rc = opRun('xWrite', pFile, n, Number(offset64));
stephanc5313af2022-09-18 02:35:30 +0000630 }catch(e){
631 error("xWrite(",arguments,") failed:",e,f);
stephanf8150112022-09-19 17:09:09 +0000632 rc = capi.SQLITE_IOERR_WRITE;
stephanc5313af2022-09-18 02:35:30 +0000633 }
stephanf8150112022-09-19 17:09:09 +0000634 mTimeEnd();
635 return rc;
stephanc5313af2022-09-18 02:35:30 +0000636 }
637 }/*ioSyncWrappers*/;
638
639 /**
640 Impls for the sqlite3_vfs methods. Maintenance reminder: members
641 are in alphabetical order to simplify finding them.
642 */
643 const vfsSyncWrappers = {
644 xAccess: function(pVfs,zName,flags,pOut){
stephanf8150112022-09-19 17:09:09 +0000645 mTimeStart('xAccess');
stephan5e8bb0a2022-09-20 08:27:57 +0000646 const rc = opRun('xAccess', wasm.cstringToJs(zName));
647 wasm.setMemValue( pOut, (rc ? 0 : 1), 'i32' );
stephanf8150112022-09-19 17:09:09 +0000648 mTimeEnd();
stephanc5313af2022-09-18 02:35:30 +0000649 return 0;
650 },
651 xCurrentTime: function(pVfs,pOut){
652 /* If it turns out that we need to adjust for timezone, see:
653 https://stackoverflow.com/a/11760121/1458521 */
654 wasm.setMemValue(pOut, 2440587.5 + (new Date().getTime()/86400000),
655 'double');
656 return 0;
657 },
658 xCurrentTimeInt64: function(pVfs,pOut){
659 // TODO: confirm that this calculation is correct
660 wasm.setMemValue(pOut, (2440587.5 * 86400000) + new Date().getTime(),
661 'i64');
662 return 0;
663 },
664 xDelete: function(pVfs, zName, doSyncDir){
stephanf8150112022-09-19 17:09:09 +0000665 mTimeStart('xDelete');
stephan138647a2022-09-20 03:31:02 +0000666 opRun('xDelete', wasm.cstringToJs(zName), doSyncDir, false);
stephanf3860122022-09-18 17:32:35 +0000667 /* We're ignoring errors because we cannot yet differentiate
668 between harmless and non-harmless failures. */
stephanf8150112022-09-19 17:09:09 +0000669 mTimeEnd();
stephanf3860122022-09-18 17:32:35 +0000670 return 0;
stephanc5313af2022-09-18 02:35:30 +0000671 },
672 xFullPathname: function(pVfs,zName,nOut,pOut){
673 /* Until/unless we have some notion of "current dir"
674 in OPFS, simply copy zName to pOut... */
675 const i = wasm.cstrncpy(pOut, zName, nOut);
676 return i<nOut ? 0 : capi.SQLITE_CANTOPEN
677 /*CANTOPEN is required by the docs but SQLITE_RANGE would be a closer match*/;
678 },
679 xGetLastError: function(pVfs,nOut,pOut){
680 /* TODO: store exception.message values from the async
681 partner in a dedicated SharedArrayBuffer, noting that we'd have
682 to encode them... TextEncoder can do that for us. */
683 warn("OPFS xGetLastError() has nothing sensible to return.");
684 return 0;
685 },
stephan8766fd22022-09-19 05:19:04 +0000686 //xSleep is optionally defined below
stephanc5313af2022-09-18 02:35:30 +0000687 xOpen: function f(pVfs, zName, pFile, flags, pOutFlags){
stephanf8150112022-09-19 17:09:09 +0000688 mTimeStart('xOpen');
stephanc5313af2022-09-18 02:35:30 +0000689 if(!f._){
690 f._ = {
691 fileTypes: {
692 SQLITE_OPEN_MAIN_DB: 'mainDb',
693 SQLITE_OPEN_MAIN_JOURNAL: 'mainJournal',
694 SQLITE_OPEN_TEMP_DB: 'tempDb',
695 SQLITE_OPEN_TEMP_JOURNAL: 'tempJournal',
696 SQLITE_OPEN_TRANSIENT_DB: 'transientDb',
697 SQLITE_OPEN_SUBJOURNAL: 'subjournal',
698 SQLITE_OPEN_SUPER_JOURNAL: 'superJournal',
699 SQLITE_OPEN_WAL: 'wal'
700 },
701 getFileType: function(filename,oflags){
702 const ft = f._.fileTypes;
703 for(let k of Object.keys(ft)){
704 if(oflags & capi[k]) return ft[k];
705 }
706 warn("Cannot determine fileType based on xOpen() flags for file",filename);
707 return '???';
708 }
709 };
710 }
711 if(0===zName){
712 zName = randomFilename();
713 }else if('number'===typeof zName){
714 zName = wasm.cstringToJs(zName);
715 }
stephan138647a2022-09-20 03:31:02 +0000716 const fh = Object.create(null);
717 fh.fid = pFile;
718 fh.filename = zName;
719 fh.sab = new SharedArrayBuffer(state.fileBufferSize);
720 fh.flags = flags;
721 const rc = opRun('xOpen', pFile, zName, flags);
stephanc5313af2022-09-18 02:35:30 +0000722 if(!rc){
723 /* Recall that sqlite3_vfs::xClose() will be called, even on
724 error, unless pFile->pMethods is NULL. */
stephan138647a2022-09-20 03:31:02 +0000725 if(fh.readOnly){
stephanc5313af2022-09-18 02:35:30 +0000726 wasm.setMemValue(pOutFlags, capi.SQLITE_OPEN_READONLY, 'i32');
727 }
stephan138647a2022-09-20 03:31:02 +0000728 __openFiles[pFile] = fh;
729 fh.sabView = state.sabFileBufView;
730 fh.sq3File = new sqlite3_file(pFile);
731 fh.sq3File.$pMethods = opfsIoMethods.pointer;
stephanc5313af2022-09-18 02:35:30 +0000732 }
stephanf8150112022-09-19 17:09:09 +0000733 mTimeEnd();
stephanc5313af2022-09-18 02:35:30 +0000734 return rc;
735 }/*xOpen()*/
736 }/*vfsSyncWrappers*/;
737
stephan8766fd22022-09-19 05:19:04 +0000738 if(dVfs){
739 opfsVfs.$xRandomness = dVfs.$xRandomness;
740 opfsVfs.$xSleep = dVfs.$xSleep;
741 }
stephanc5313af2022-09-18 02:35:30 +0000742 if(!opfsVfs.$xRandomness){
743 /* If the default VFS has no xRandomness(), add a basic JS impl... */
744 vfsSyncWrappers.xRandomness = function(pVfs, nOut, pOut){
745 const heap = wasm.heap8u();
746 let i = 0;
747 for(; i < nOut; ++i) heap[pOut + i] = (Math.random()*255000) & 0xFF;
748 return i;
749 };
750 }
751 if(!opfsVfs.$xSleep){
752 /* If we can inherit an xSleep() impl from the default VFS then
stephan8766fd22022-09-19 05:19:04 +0000753 assume it's sane and use it, otherwise install a JS-based
754 one. */
755 vfsSyncWrappers.xSleep = function(pVfs,ms){
stephanc4b87be2022-09-20 01:28:47 +0000756 Atomics.wait(state.sabOPView, state.opIds.xSleep, 0, ms);
stephan8766fd22022-09-19 05:19:04 +0000757 return 0;
758 };
stephanc5313af2022-09-18 02:35:30 +0000759 }
760
761 /* Install the vfs/io_methods into their C-level shared instances... */
762 let inst = installMethod(opfsIoMethods);
763 for(let k of Object.keys(ioSyncWrappers)) inst(k, ioSyncWrappers[k]);
764 inst = installMethod(opfsVfs);
765 for(let k of Object.keys(vfsSyncWrappers)) inst(k, vfsSyncWrappers[k]);
stephanf3860122022-09-18 17:32:35 +0000766
stephanf3860122022-09-18 17:32:35 +0000767 /**
768 Syncronously deletes the given OPFS filesystem entry, ignoring
769 any errors. As this environment has no notion of "current
770 directory", the given name must be an absolute path. If the 2nd
771 argument is truthy, deletion is recursive (use with caution!).
772
773 Returns true if the deletion succeeded and fails if it fails,
774 but cannot report the nature of the failure.
775 */
stephan0e0687c2022-09-19 13:44:23 +0000776 opfsUtil.deleteEntry = function(fsEntryName,recursive=false){
stephan138647a2022-09-20 03:31:02 +0000777 return 0===opRun('xDelete', fsEntryName, 0, recursive);
stephanf3860122022-09-18 17:32:35 +0000778 };
779 /**
stephanf3860122022-09-18 17:32:35 +0000780 Synchronously creates the given directory name, recursively, in
781 the OPFS filesystem. Returns true if it succeeds or the
782 directory already exists, else false.
783 */
stephan5e8bb0a2022-09-20 08:27:57 +0000784 opfsUtil.mkdir = function(absDirName){
stephanf3860122022-09-18 17:32:35 +0000785 return 0===opRun('mkdir', absDirName);
786 };
787 /**
788 Synchronously checks whether the given OPFS filesystem exists,
789 returning true if it does, false if it doesn't.
790 */
791 opfsUtil.entryExists = function(fsEntryName){
792 return 0===opRun('xAccess', fsEntryName);
793 };
794
795 /**
796 Generates a random ASCII string, intended for use as a
797 temporary file name. Its argument is the length of the string,
798 defaulting to 16.
799 */
800 opfsUtil.randomFilename = randomFilename;
801
802 if(sqlite3.oo1){
803 opfsUtil.OpfsDb = function(...args){
804 const opt = sqlite3.oo1.dbCtorHelper.normalizeArgs(...args);
805 opt.vfs = opfsVfs.$zName;
806 sqlite3.oo1.dbCtorHelper.call(this, opt);
807 };
808 opfsUtil.OpfsDb.prototype = Object.create(sqlite3.oo1.DB.prototype);
809 }
stephanf8150112022-09-19 17:09:09 +0000810
stephanf3860122022-09-18 17:32:35 +0000811 /**
812 Potential TODOs:
813
814 - Expose one or both of the Worker objects via opfsUtil and
815 publish an interface for proxying the higher-level OPFS
816 features like getting a directory listing.
817 */
stephanc5313af2022-09-18 02:35:30 +0000818
stephan5e8bb0a2022-09-20 08:27:57 +0000819 const sanityCheck = function(){
stephanc5313af2022-09-18 02:35:30 +0000820 const scope = wasm.scopedAllocPush();
821 const sq3File = new sqlite3_file();
822 try{
823 const fid = sq3File.pointer;
824 const openFlags = capi.SQLITE_OPEN_CREATE
825 | capi.SQLITE_OPEN_READWRITE
826 //| capi.SQLITE_OPEN_DELETEONCLOSE
827 | capi.SQLITE_OPEN_MAIN_DB;
828 const pOut = wasm.scopedAlloc(8);
stephanb8c8d4e2022-09-20 13:25:39 +0000829 const dbFile = "/sanity/check/file"+randomFilename(8);
stephanc5313af2022-09-18 02:35:30 +0000830 const zDbFile = wasm.scopedAllocCString(dbFile);
831 let rc;
832 vfsSyncWrappers.xAccess(opfsVfs.pointer, zDbFile, 0, pOut);
833 rc = wasm.getMemValue(pOut,'i32');
834 log("xAccess(",dbFile,") exists ?=",rc);
835 rc = vfsSyncWrappers.xOpen(opfsVfs.pointer, zDbFile,
836 fid, openFlags, pOut);
stephanc4b87be2022-09-20 01:28:47 +0000837 log("open rc =",rc,"state.sabOPView[xOpen] =",
838 state.sabOPView[state.opIds.xOpen]);
stephanc5313af2022-09-18 02:35:30 +0000839 if(isWorkerErrCode(rc)){
840 error("open failed with code",rc);
841 return;
842 }
843 vfsSyncWrappers.xAccess(opfsVfs.pointer, zDbFile, 0, pOut);
844 rc = wasm.getMemValue(pOut,'i32');
845 if(!rc) toss("xAccess() failed to detect file.");
846 rc = ioSyncWrappers.xSync(sq3File.pointer, 0);
847 if(rc) toss('sync failed w/ rc',rc);
848 rc = ioSyncWrappers.xTruncate(sq3File.pointer, 1024);
849 if(rc) toss('truncate failed w/ rc',rc);
850 wasm.setMemValue(pOut,0,'i64');
851 rc = ioSyncWrappers.xFileSize(sq3File.pointer, pOut);
852 if(rc) toss('xFileSize failed w/ rc',rc);
853 log("xFileSize says:",wasm.getMemValue(pOut, 'i64'));
854 rc = ioSyncWrappers.xWrite(sq3File.pointer, zDbFile, 10, 1);
855 if(rc) toss("xWrite() failed!");
856 const readBuf = wasm.scopedAlloc(16);
857 rc = ioSyncWrappers.xRead(sq3File.pointer, readBuf, 6, 2);
858 wasm.setMemValue(readBuf+6,0);
859 let jRead = wasm.cstringToJs(readBuf);
860 log("xRead() got:",jRead);
861 if("sanity"!==jRead) toss("Unexpected xRead() value.");
stephan8766fd22022-09-19 05:19:04 +0000862 if(vfsSyncWrappers.xSleep){
863 log("xSleep()ing before close()ing...");
864 vfsSyncWrappers.xSleep(opfsVfs.pointer,2000);
865 log("waking up from xSleep()");
866 }
stephanc5313af2022-09-18 02:35:30 +0000867 rc = ioSyncWrappers.xClose(fid);
stephanc4b87be2022-09-20 01:28:47 +0000868 log("xClose rc =",rc,"sabOPView =",state.sabOPView);
stephanc5313af2022-09-18 02:35:30 +0000869 log("Deleting file:",dbFile);
870 vfsSyncWrappers.xDelete(opfsVfs.pointer, zDbFile, 0x1234);
871 vfsSyncWrappers.xAccess(opfsVfs.pointer, zDbFile, 0, pOut);
872 rc = wasm.getMemValue(pOut,'i32');
873 if(rc) toss("Expecting 0 from xAccess(",dbFile,") after xDelete().");
stephanc9e26022022-09-20 10:11:52 +0000874 warn("End of OPFS sanity checks.");
stephanc5313af2022-09-18 02:35:30 +0000875 }finally{
876 sq3File.dispose();
877 wasm.scopedAllocPop(scope);
878 }
879 }/*sanityCheck()*/;
880
stephanf8150112022-09-19 17:09:09 +0000881
stephanc5313af2022-09-18 02:35:30 +0000882 W.onmessage = function({data}){
883 //log("Worker.onmessage:",data);
884 switch(data.type){
stephan138647a2022-09-20 03:31:02 +0000885 case 'opfs-async-loaded':
stephanc5313af2022-09-18 02:35:30 +0000886 /*Pass our config and shared state on to the async worker.*/
stephan5e8bb0a2022-09-20 08:27:57 +0000887 W.postMessage({type: 'opfs-async-init',args: state});
stephanc5313af2022-09-18 02:35:30 +0000888 break;
stephan138647a2022-09-20 03:31:02 +0000889 case 'opfs-async-inited':{
stephanc5313af2022-09-18 02:35:30 +0000890 /*Indicates that the async partner has received the 'init',
891 so we now know that the state object is no longer subject to
892 being copied by a pending postMessage() call.*/
893 try {
stephan0e0687c2022-09-19 13:44:23 +0000894 const rc = capi.sqlite3_vfs_register(opfsVfs.pointer, 0);
stephanc5313af2022-09-18 02:35:30 +0000895 if(rc){
896 opfsVfs.dispose();
897 toss("sqlite3_vfs_register(OPFS) failed with rc",rc);
898 }
899 if(opfsVfs.pointer !== capi.sqlite3_vfs_find("opfs")){
900 toss("BUG: sqlite3_vfs_find() failed for just-installed OPFS VFS");
901 }
902 capi.sqlite3_vfs_register.addReference(opfsVfs, opfsIoMethods);
stephanc4b87be2022-09-20 01:28:47 +0000903 state.sabOPView = new Int32Array(state.sabOP);
stephan138647a2022-09-20 03:31:02 +0000904 state.sabFileBufView = new Uint8Array(state.sabIO, 0, state.fileBufferSize);
905 state.sabS11nView = new Uint8Array(state.sabIO, state.sabS11nOffset, state.sabS11nSize);
906 initS11n();
stephanc5313af2022-09-18 02:35:30 +0000907 if(options.sanityChecks){
908 warn("Running sanity checks because of opfs-sanity-check URL arg...");
909 sanityCheck();
910 }
stephanf3860122022-09-18 17:32:35 +0000911 W.onerror = W._originalOnError;
912 delete W._originalOnError;
913 sqlite3.opfs = opfsUtil;
stephanc5313af2022-09-18 02:35:30 +0000914 log("End of OPFS sqlite3_vfs setup.", opfsVfs);
stephan509f4052022-09-19 09:58:01 +0000915 promiseResolve(sqlite3);
stephanc5313af2022-09-18 02:35:30 +0000916 }catch(e){
917 error(e);
918 promiseReject(e);
919 }
920 break;
921 }
922 default:
923 promiseReject(e);
924 error("Unexpected message from the async worker:",data);
925 break;
926 }
927 };
928 })/*thePromise*/;
929 return thePromise;
930}/*installOpfsVfs()*/;
931sqlite3.installOpfsVfs.defaultProxyUri = "sqlite3-opfs-async-proxy.js";
932}/*sqlite3ApiBootstrap.initializers.push()*/);