blob: 1f50b4631aef2fadb84ee2d54152e557b5da1d85 [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;
80 if(self.window===self ||
81 !self.SharedArrayBuffer ||
82 !self.FileSystemHandle ||
83 !self.FileSystemDirectoryHandle ||
84 !self.FileSystemFileHandle ||
85 !self.FileSystemFileHandle.prototype.createSyncAccessHandle ||
86 !navigator.storage.getDirectory){
87 return Promise.reject(
88 new Error("This environment does not have OPFS support.")
89 );
90 }
stephanc5313af2022-09-18 02:35:30 +000091 const options = (asyncProxyUri && 'object'===asyncProxyUri) ? asyncProxyUri : {
92 proxyUri: asyncProxyUri
stephan3961b262022-08-10 11:26:08 +000093 };
stephan509f4052022-09-19 09:58:01 +000094 const urlParams = new URL(self.location.href).searchParams;
stephanc5313af2022-09-18 02:35:30 +000095 if(undefined===options.verbose){
stephan509f4052022-09-19 09:58:01 +000096 options.verbose = urlParams.has('opfs-verbose') ? 3 : 2;
stephan3961b262022-08-10 11:26:08 +000097 }
stephanc5313af2022-09-18 02:35:30 +000098 if(undefined===options.sanityChecks){
stephan509f4052022-09-19 09:58:01 +000099 options.sanityChecks = urlParams.has('opfs-sanity-check');
stephanc5313af2022-09-18 02:35:30 +0000100 }
101 if(undefined===options.proxyUri){
102 options.proxyUri = callee.defaultProxyUri;
103 }
stephanf3860122022-09-18 17:32:35 +0000104
stephanc5313af2022-09-18 02:35:30 +0000105 const thePromise = new Promise(function(promiseResolve, promiseReject){
stephan509f4052022-09-19 09:58:01 +0000106 const loggers = {
107 0:console.error.bind(console),
108 1:console.warn.bind(console),
109 2:console.log.bind(console)
stephanc5313af2022-09-18 02:35:30 +0000110 };
stephan509f4052022-09-19 09:58:01 +0000111 const logImpl = (level,...args)=>{
112 if(options.verbose>level) loggers[level]("OPFS syncer:",...args);
113 };
114 const log = (...args)=>logImpl(2, ...args);
115 const warn = (...args)=>logImpl(1, ...args);
116 const error = (...args)=>logImpl(0, ...args);
stephanc5313af2022-09-18 02:35:30 +0000117 warn("The OPFS VFS feature is very much experimental and under construction.");
118 const toss = function(...args){throw new Error(args.join(' '))};
stephanc5313af2022-09-18 02:35:30 +0000119 const capi = sqlite3.capi;
120 const wasm = capi.wasm;
121 const sqlite3_vfs = capi.sqlite3_vfs;
122 const sqlite3_file = capi.sqlite3_file;
123 const sqlite3_io_methods = capi.sqlite3_io_methods;
stephanc5313af2022-09-18 02:35:30 +0000124 const W = new Worker(options.proxyUri);
stephanf3860122022-09-18 17:32:35 +0000125 W._originalOnError = W.onerror /* will be restored later */;
stephanc5313af2022-09-18 02:35:30 +0000126 W.onerror = function(err){
stephan509f4052022-09-19 09:58:01 +0000127 // The error object doesn't contain any useful info when the
128 // failure is, e.g., that the remote script is 404.
stephanc5313af2022-09-18 02:35:30 +0000129 promiseReject(new Error("Loading OPFS async Worker failed for unknown reasons."));
130 };
stephan509f4052022-09-19 09:58:01 +0000131 /**
132 Generic utilities for working with OPFS. This will get filled out
133 by the Promise setup and, on success, installed as sqlite3.opfs.
134 */
135 const opfsUtil = Object.create(null);
stephanf8150112022-09-19 17:09:09 +0000136 /**
137 Not part of the public API. Solely for internal/development
138 use.
139 */
140 opfsUtil.metrics = {
141 dump: function(){
stephanaec046a2022-09-19 18:22:29 +0000142 let k, n = 0, t = 0, w = 0;
143 for(k in state.opIds){
stephanf8150112022-09-19 17:09:09 +0000144 const m = metrics[k];
145 n += m.count;
146 t += m.time;
stephanaec046a2022-09-19 18:22:29 +0000147 w += m.wait;
stephanf8150112022-09-19 17:09:09 +0000148 m.avgTime = (m.count && m.time) ? (m.time / m.count) : 0;
149 m.avgWait = (m.count && m.wait) ? (m.wait / m.count) : 0;
150 }
stephanaec046a2022-09-19 18:22:29 +0000151 console.log(self.location.href,
152 "metrics for",self.location.href,":",metrics,
153 "\nTotal of",n,"op(s) for",t,
154 "ms (incl. "+w+" ms of waiting on the async side)");
stephanb8c8d4e2022-09-20 13:25:39 +0000155 console.log("Serialization metrics:",JSON.stringify(metrics.s11n,0,2));
stephanf8150112022-09-19 17:09:09 +0000156 },
157 reset: function(){
158 let k;
159 const r = (m)=>(m.count = m.time = m.wait = 0);
160 for(k in state.opIds){
161 r(metrics[k] = Object.create(null));
162 }
stephanb8c8d4e2022-09-20 13:25:39 +0000163 let s = metrics.s11n = Object.create(null);
164 s = s.serialize = Object.create(null);
165 s.count = s.time = 0;
166 s = metrics.s11n.deserialize = Object.create(null);
167 s.count = s.time = 0;
stephanaec046a2022-09-19 18:22:29 +0000168 //[ // timed routines which are not in state.opIds
169 // 'xFileControl'
170 //].forEach((k)=>r(metrics[k] = Object.create(null)));
stephanf8150112022-09-19 17:09:09 +0000171 }
172 }/*metrics*/;
stephanc5313af2022-09-18 02:35:30 +0000173
stephanc9e26022022-09-20 10:11:52 +0000174 const pDVfs = capi.sqlite3_vfs_find(null)/*pointer to default VFS*/;
175 const dVfs = pDVfs
176 ? new sqlite3_vfs(pDVfs)
177 : null /* dVfs will be null when sqlite3 is built with
178 SQLITE_OS_OTHER. Though we cannot currently handle
179 that case, the hope is to eventually be able to. */;
180 const opfsVfs = new sqlite3_vfs();
181 const opfsIoMethods = new sqlite3_io_methods();
182 opfsVfs.$iVersion = 2/*yes, two*/;
183 opfsVfs.$szOsFile = capi.sqlite3_file.structInfo.sizeof;
184 opfsVfs.$mxPathname = 1024/*sure, why not?*/;
185 opfsVfs.$zName = wasm.allocCString("opfs");
186 // All C-side memory of opfsVfs is zeroed out, but just to be explicit:
187 opfsVfs.$xDlOpen = opfsVfs.$xDlError = opfsVfs.$xDlSym = opfsVfs.$xDlClose = null;
188 opfsVfs.ondispose = [
189 '$zName', opfsVfs.$zName,
190 'cleanup default VFS wrapper', ()=>(dVfs ? dVfs.dispose() : null),
191 'cleanup opfsIoMethods', ()=>opfsIoMethods.dispose()
192 ];
193 /**
194 Pedantic sidebar about opfsVfs.ondispose: the entries in that array
195 are items to clean up when opfsVfs.dispose() is called, but in this
196 environment it will never be called. The VFS instance simply
197 hangs around until the WASM module instance is cleaned up. We
198 "could" _hypothetically_ clean it up by "importing" an
199 sqlite3_os_end() impl into the wasm build, but the shutdown order
200 of the wasm engine and the JS one are undefined so there is no
201 guaranty that the opfsVfs instance would be available in one
202 environment or the other when sqlite3_os_end() is called (_if_ it
203 gets called at all in a wasm build, which is undefined).
204 */
205
stephanc5313af2022-09-18 02:35:30 +0000206 /**
207 State which we send to the async-api Worker or share with it.
208 This object must initially contain only cloneable or sharable
209 objects. After the worker's "inited" message arrives, other types
210 of data may be added to it.
stephanf3860122022-09-18 17:32:35 +0000211
212 For purposes of Atomics.wait() and Atomics.notify(), we use a
213 SharedArrayBuffer with one slot reserved for each of the API
214 proxy's methods. The sync side of the API uses Atomics.wait()
215 on the corresponding slot and the async side uses
216 Atomics.notify() on that slot.
217
218 The approach of using a single SAB to serialize comms for all
219 instances might(?) lead to deadlock situations in multi-db
220 cases. We should probably have one SAB here with a single slot
221 for locking a per-file initialization step and then allocate a
222 separate SAB like the above one for each file. That will
223 require a bit of acrobatics but should be feasible.
stephanc5313af2022-09-18 02:35:30 +0000224 */
225 const state = Object.create(null);
stephan138647a2022-09-20 03:31:02 +0000226 state.littleEndian = true;
stephanc5313af2022-09-18 02:35:30 +0000227 state.verbose = options.verbose;
stephanc9e26022022-09-20 10:11:52 +0000228 /* Size of file I/O buffer block. 64k = max sqlite3 page size. */
stephanf3860122022-09-18 17:32:35 +0000229 state.fileBufferSize =
stephanc9e26022022-09-20 10:11:52 +0000230 1024 * 64;
stephan138647a2022-09-20 03:31:02 +0000231 state.sabS11nOffset = state.fileBufferSize;
stephanc9e26022022-09-20 10:11:52 +0000232 /**
233 The size of the block in our SAB for serializing arguments and
234 result values. Need to be large enough to hold serialized
235 values of any of the proxied APIs. Filenames are the largest
236 part but are limited to opfsVfs.$mxPathname bytes.
237 */
238 state.sabS11nSize = opfsVfs.$mxPathname * 2;
239 /**
240 The SAB used for all data I/O (files and arg/result s11n).
241 */
stephanc4b87be2022-09-20 01:28:47 +0000242 state.sabIO = new SharedArrayBuffer(
stephanc9e26022022-09-20 10:11:52 +0000243 state.fileBufferSize/* file i/o block */
244 + state.sabS11nSize/* argument/result serialization block */
stephanc4b87be2022-09-20 01:28:47 +0000245 );
stephanc5313af2022-09-18 02:35:30 +0000246 state.opIds = Object.create(null);
stephanf8150112022-09-19 17:09:09 +0000247 const metrics = Object.create(null);
stephanc5313af2022-09-18 02:35:30 +0000248 {
stephanc9e26022022-09-20 10:11:52 +0000249 /* Indexes for use in our SharedArrayBuffer... */
stephan3961b262022-08-10 11:26:08 +0000250 let i = 0;
stephanc9e26022022-09-20 10:11:52 +0000251 /* SAB slot used to communicate which operation is desired
252 between both workers. This worker writes to it and the other
253 listens for changes. */
stephan138647a2022-09-20 03:31:02 +0000254 state.opIds.whichOp = i++;
stephanc9e26022022-09-20 10:11:52 +0000255 /* Slot for storing return values. This work listens to that
256 slot and the other worker writes to it. */
257 state.opIds.rc = i++;
258 /* Each function gets an ID which this worker writes to
259 the whichOp slot. The async-api worker uses Atomic.wait()
260 on the whichOp slot to figure out which operation to run
261 next. */
stephanc5313af2022-09-18 02:35:30 +0000262 state.opIds.xAccess = i++;
263 state.opIds.xClose = i++;
264 state.opIds.xDelete = i++;
stephanf3860122022-09-18 17:32:35 +0000265 state.opIds.xDeleteNoWait = i++;
stephanc5313af2022-09-18 02:35:30 +0000266 state.opIds.xFileSize = i++;
267 state.opIds.xOpen = i++;
268 state.opIds.xRead = i++;
269 state.opIds.xSleep = i++;
270 state.opIds.xSync = i++;
271 state.opIds.xTruncate = i++;
272 state.opIds.xWrite = i++;
stephanf3860122022-09-18 17:32:35 +0000273 state.opIds.mkdir = i++;
stephan5e8bb0a2022-09-20 08:27:57 +0000274 state.opIds.xFileControl = i++;
stephanc4b87be2022-09-20 01:28:47 +0000275 state.sabOP = new SharedArrayBuffer(i * 4/*sizeof int32*/);
stephanf8150112022-09-19 17:09:09 +0000276 opfsUtil.metrics.reset();
stephanc5313af2022-09-18 02:35:30 +0000277 }
278
stephanc9e26022022-09-20 10:11:52 +0000279 /**
280 SQLITE_xxx constants to export to the async worker
281 counterpart...
282 */
stephanc5313af2022-09-18 02:35:30 +0000283 state.sq3Codes = Object.create(null);
284 state.sq3Codes._reverse = Object.create(null);
stephanc9e26022022-09-20 10:11:52 +0000285 [
stephanc5313af2022-09-18 02:35:30 +0000286 'SQLITE_ERROR', 'SQLITE_IOERR',
287 'SQLITE_NOTFOUND', 'SQLITE_MISUSE',
288 'SQLITE_IOERR_READ', 'SQLITE_IOERR_SHORT_READ',
289 'SQLITE_IOERR_WRITE', 'SQLITE_IOERR_FSYNC',
290 'SQLITE_IOERR_TRUNCATE', 'SQLITE_IOERR_DELETE',
stephanf3860122022-09-18 17:32:35 +0000291 'SQLITE_IOERR_ACCESS', 'SQLITE_IOERR_CLOSE',
stephanc4b87be2022-09-20 01:28:47 +0000292 'SQLITE_IOERR_DELETE',
293 'SQLITE_OPEN_CREATE', 'SQLITE_OPEN_DELETEONCLOSE',
294 'SQLITE_OPEN_READONLY'
stephanc5313af2022-09-18 02:35:30 +0000295 ].forEach(function(k){
296 state.sq3Codes[k] = capi[k] || toss("Maintenance required: not found:",k);
297 state.sq3Codes._reverse[capi[k]] = k;
stephan3961b262022-08-10 11:26:08 +0000298 });
stephan3961b262022-08-10 11:26:08 +0000299
stephanc5313af2022-09-18 02:35:30 +0000300 const isWorkerErrCode = (n)=>!!state.sq3Codes._reverse[n];
stephan3961b262022-08-10 11:26:08 +0000301
stephanc5313af2022-09-18 02:35:30 +0000302 /**
stephanc9e26022022-09-20 10:11:52 +0000303 Runs the given operation (by name) in the async worker
304 counterpart, waits for its response, and returns the result
305 which the async worker writes to SAB[state.opIds.rc]. The
306 2nd and subsequent arguments must be the aruguments for the
307 async op.
stephanc5313af2022-09-18 02:35:30 +0000308 */
stephan138647a2022-09-20 03:31:02 +0000309 const opRun = (op,...args)=>{
stephan5e8bb0a2022-09-20 08:27:57 +0000310 const opNdx = state.opIds[op] || toss("Invalid op ID:",op);
311 state.s11n.serialize(...args);
stephanc9e26022022-09-20 10:11:52 +0000312 Atomics.store(state.sabOPView, state.opIds.rc, -1);
stephan5e8bb0a2022-09-20 08:27:57 +0000313 Atomics.store(state.sabOPView, state.opIds.whichOp, opNdx);
314 Atomics.notify(state.sabOPView, state.opIds.whichOp) /* async thread will take over here */;
stephanf8150112022-09-19 17:09:09 +0000315 const t = performance.now();
stephanc9e26022022-09-20 10:11:52 +0000316 Atomics.wait(state.sabOPView, state.opIds.rc, -1);
317 const rc = Atomics.load(state.sabOPView, state.opIds.rc);
stephanf8150112022-09-19 17:09:09 +0000318 metrics[op].wait += performance.now() - t;
stephan5e8bb0a2022-09-20 08:27:57 +0000319 return rc;
stephanc5313af2022-09-18 02:35:30 +0000320 };
321
stephan138647a2022-09-20 03:31:02 +0000322 const initS11n = ()=>{
stephanb8c8d4e2022-09-20 13:25:39 +0000323 /**
324 ACHTUNG: this code is 100% duplicated in the other half of
325 this proxy!
326
327 Historical note: this impl was initially about 5% this size by using
328 using JSON.stringify/parse(), but using fit-to-purpose serialization
329 saves considerable runtime.
330 */
stephan138647a2022-09-20 03:31:02 +0000331 if(state.s11n) return state.s11n;
stephanb8c8d4e2022-09-20 13:25:39 +0000332 const textDecoder = new TextDecoder(),
333 textEncoder = new TextEncoder('utf-8'),
334 viewU8 = new Uint8Array(state.sabIO, state.sabS11nOffset, state.sabS11nSize),
335 viewDV = new DataView(state.sabIO, state.sabS11nOffset, state.sabS11nSize);
stephan138647a2022-09-20 03:31:02 +0000336 state.s11n = Object.create(null);
stephanb8c8d4e2022-09-20 13:25:39 +0000337 const TypeIds = Object.create(null);
338 TypeIds.number = { id: 1, size: 8, getter: 'getFloat64', setter: 'setFloat64' };
339 TypeIds.bigint = { id: 2, size: 8, getter: 'getBigInt64', setter: 'setBigInt64' };
340 TypeIds.boolean = { id: 3, size: 4, getter: 'getInt32', setter: 'setInt32' };
341 TypeIds.string = { id: 4 };
342 const getTypeId = (v)=>{
343 return TypeIds[typeof v] || toss("This value type cannot be serialized.",v);
344 };
345 const getTypeIdById = (tid)=>{
346 switch(tid){
347 case TypeIds.number.id: return TypeIds.number;
348 case TypeIds.bigint.id: return TypeIds.bigint;
349 case TypeIds.boolean.id: return TypeIds.boolean;
350 case TypeIds.string.id: return TypeIds.string;
351 default: toss("Invalid type ID:",tid);
352 }
353 };
stephan138647a2022-09-20 03:31:02 +0000354 /**
355 Returns an array of the state serialized by the most recent
356 serialize() operation (here or in the counterpart thread), or
357 null if the serialization buffer is empty.
358 */
359 state.s11n.deserialize = function(){
stephanb8c8d4e2022-09-20 13:25:39 +0000360 ++metrics.s11n.deserialize.count;
361 const t = performance.now();
362 let rc = null;
363 const argc = viewU8[0];
364 if(argc){
365 rc = [];
366 let offset = 1, i, n, v, typeIds = [];
367 for(i = 0; i < argc; ++i, ++offset){
368 typeIds.push(getTypeIdById(viewU8[offset]));
369 }
370 for(i = 0; i < argc; ++i){
371 const t = typeIds[i];
372 if(t.getter){
373 v = viewDV[t.getter](offset, state.littleEndian);
374 offset += t.size;
375 }else{
376 n = viewDV.getInt32(offset, state.littleEndian);
377 offset += 4;
378 v = textDecoder.decode(viewU8.slice(offset, offset+n));
379 offset += n;
380 }
381 rc.push(v);
382 }
383 }
384 //log("deserialize:",argc, rc);
385 metrics.s11n.deserialize.time += performance.now() - t;
386 return rc;
387 };
stephan138647a2022-09-20 03:31:02 +0000388 /**
389 Serializes all arguments to the shared buffer for consumption
stephanb8c8d4e2022-09-20 13:25:39 +0000390 by the counterpart thread.
stephan5e8bb0a2022-09-20 08:27:57 +0000391
stephanb8c8d4e2022-09-20 13:25:39 +0000392 This routine is only intended for serializing OPFS VFS
393 arguments and (in at least one special case) result values,
394 and the buffer is sized to be able to comfortably handle
395 those.
stephan5e8bb0a2022-09-20 08:27:57 +0000396
397 If passed no arguments then it zeroes out the serialization
398 state.
stephan138647a2022-09-20 03:31:02 +0000399 */
400 state.s11n.serialize = function(...args){
stephanb8c8d4e2022-09-20 13:25:39 +0000401 ++metrics.s11n.serialize.count;
402 const t = performance.now();
stephan5e8bb0a2022-09-20 08:27:57 +0000403 if(args.length){
stephanb8c8d4e2022-09-20 13:25:39 +0000404 //log("serialize():",args);
405 let i = 0, offset = 1, typeIds = [];
406 viewU8[0] = args.length & 0xff;
407 for(; i < args.length; ++i, ++offset){
408 typeIds.push(getTypeId(args[i]));
409 viewU8[offset] = typeIds[i].id;
410 }
411 for(i = 0; i < args.length; ++i) {
412 const t = typeIds[i];
413 if(t.setter){
414 viewDV[t.setter](offset, args[i], state.littleEndian);
415 offset += t.size;
416 }else{
417 const s = textEncoder.encode(args[i]);
418 viewDV.setInt32(offset, s.byteLength, state.littleEndian);
419 offset += 4;
420 viewU8.set(s, offset);
421 offset += s.byteLength;
422 }
423 }
424 //log("serialize() result:",viewU8.slice(0,offset));
stephan5e8bb0a2022-09-20 08:27:57 +0000425 }else{
stephanb8c8d4e2022-09-20 13:25:39 +0000426 viewU8[0] = 0;
stephan5e8bb0a2022-09-20 08:27:57 +0000427 }
stephanb8c8d4e2022-09-20 13:25:39 +0000428 metrics.s11n.serialize.time += performance.now() - t;
stephan138647a2022-09-20 03:31:02 +0000429 };
430 return state.s11n;
stephanb8c8d4e2022-09-20 13:25:39 +0000431 }/*initS11n()*/;
stephan138647a2022-09-20 03:31:02 +0000432
stephanc5313af2022-09-18 02:35:30 +0000433 /**
434 Generates a random ASCII string len characters long, intended for
435 use as a temporary file name.
436 */
437 const randomFilename = function f(len=16){
438 if(!f._chars){
439 f._chars = "abcdefghijklmnopqrstuvwxyz"+
440 "ABCDEFGHIJKLMNOPQRSTUVWXYZ"+
441 "012346789";
442 f._n = f._chars.length;
443 }
444 const a = [];
445 let i = 0;
446 for( ; i < len; ++i){
447 const ndx = Math.random() * (f._n * 64) % f._n | 0;
448 a[i] = f._chars[ndx];
449 }
450 return a.join('');
451 };
452
453 /**
454 Map of sqlite3_file pointers to objects constructed by xOpen().
455 */
456 const __openFiles = Object.create(null);
stephanc5313af2022-09-18 02:35:30 +0000457
458 /**
459 Installs a StructBinder-bound function pointer member of the
460 given name and function in the given StructType target object.
461 It creates a WASM proxy for the given function and arranges for
462 that proxy to be cleaned up when tgt.dispose() is called. Throws
463 on the slightest hint of error (e.g. tgt is-not-a StructType,
464 name does not map to a struct-bound member, etc.).
465
466 Returns a proxy for this function which is bound to tgt and takes
467 2 args (name,func). That function returns the same thing,
468 permitting calls to be chained.
469
470 If called with only 1 arg, it has no side effects but returns a
471 func with the same signature as described above.
472 */
473 const installMethod = function callee(tgt, name, func){
stephanf3860122022-09-18 17:32:35 +0000474 if(!(tgt instanceof sqlite3.StructBinder.StructType)){
stephanc5313af2022-09-18 02:35:30 +0000475 toss("Usage error: target object is-not-a StructType.");
476 }
477 if(1===arguments.length){
478 return (n,f)=>callee(tgt,n,f);
479 }
480 if(!callee.argcProxy){
481 callee.argcProxy = function(func,sig){
482 return function(...args){
483 if(func.length!==arguments.length){
484 toss("Argument mismatch. Native signature is:",sig);
485 }
486 return func.apply(this, args);
487 }
488 };
489 callee.removeFuncList = function(){
490 if(this.ondispose.__removeFuncList){
491 this.ondispose.__removeFuncList.forEach(
492 (v,ndx)=>{
493 if('number'===typeof v){
494 try{wasm.uninstallFunction(v)}
495 catch(e){/*ignore*/}
496 }
497 /* else it's a descriptive label for the next number in
498 the list. */
499 }
500 );
501 delete this.ondispose.__removeFuncList;
502 }
503 };
504 }/*static init*/
505 const sigN = tgt.memberSignature(name);
506 if(sigN.length<2){
507 toss("Member",name," is not a function pointer. Signature =",sigN);
508 }
509 const memKey = tgt.memberKey(name);
510 //log("installMethod",tgt, name, sigN);
stephanc2ccd672022-09-20 10:47:36 +0000511 const fProxy = 0
stephanc5313af2022-09-18 02:35:30 +0000512 // We can remove this proxy middle-man once the VFS is working
513 ? callee.argcProxy(func, sigN)
514 : func;
515 const pFunc = wasm.installFunction(fProxy, tgt.memberSignature(name, true));
516 tgt[memKey] = pFunc;
517 if(!tgt.ondispose) tgt.ondispose = [];
518 if(!tgt.ondispose.__removeFuncList){
519 tgt.ondispose.push('ondispose.__removeFuncList handler',
520 callee.removeFuncList);
521 tgt.ondispose.__removeFuncList = [];
522 }
523 tgt.ondispose.__removeFuncList.push(memKey, pFunc);
524 return (n,f)=>callee(tgt, n, f);
525 }/*installMethod*/;
stephanf8150112022-09-19 17:09:09 +0000526
527 const opTimer = Object.create(null);
528 opTimer.op = undefined;
529 opTimer.start = undefined;
530 const mTimeStart = (op)=>{
531 opTimer.start = performance.now();
532 opTimer.op = op;
533 //metrics[op] || toss("Maintenance required: missing metrics for",op);
534 ++metrics[op].count;
535 };
536 const mTimeEnd = ()=>(
537 metrics[opTimer.op].time += performance.now() - opTimer.start
538 );
539
stephanc5313af2022-09-18 02:35:30 +0000540 /**
541 Impls for the sqlite3_io_methods methods. Maintenance reminder:
542 members are in alphabetical order to simplify finding them.
543 */
544 const ioSyncWrappers = {
545 xCheckReservedLock: function(pFile,pOut){
546 // Exclusive lock is automatically acquired when opened
547 //warn("xCheckReservedLock(",arguments,") is a no-op");
548 wasm.setMemValue(pOut,1,'i32');
549 return 0;
550 },
551 xClose: function(pFile){
stephanf8150112022-09-19 17:09:09 +0000552 mTimeStart('xClose');
stephanc5313af2022-09-18 02:35:30 +0000553 let rc = 0;
554 const f = __openFiles[pFile];
555 if(f){
556 delete __openFiles[pFile];
557 rc = opRun('xClose', pFile);
558 if(f.sq3File) f.sq3File.dispose();
559 }
stephanf8150112022-09-19 17:09:09 +0000560 mTimeEnd();
stephanc5313af2022-09-18 02:35:30 +0000561 return rc;
562 },
563 xDeviceCharacteristics: function(pFile){
564 //debug("xDeviceCharacteristics(",pFile,")");
565 return capi.SQLITE_IOCAP_UNDELETABLE_WHEN_OPEN;
566 },
stephanf8150112022-09-19 17:09:09 +0000567 xFileControl: function(pFile, opId, pArg){
568 mTimeStart('xFileControl');
stephanaec046a2022-09-19 18:22:29 +0000569 const rc = (capi.SQLITE_FCNTL_SYNC===opId)
stephan138647a2022-09-20 03:31:02 +0000570 ? opRun('xSync', pFile, 0)
stephanaec046a2022-09-19 18:22:29 +0000571 : capi.SQLITE_NOTFOUND;
stephanf8150112022-09-19 17:09:09 +0000572 mTimeEnd();
stephanaec046a2022-09-19 18:22:29 +0000573 return rc;
stephanc5313af2022-09-18 02:35:30 +0000574 },
575 xFileSize: function(pFile,pSz64){
stephanf8150112022-09-19 17:09:09 +0000576 mTimeStart('xFileSize');
stephanc5313af2022-09-18 02:35:30 +0000577 const rc = opRun('xFileSize', pFile);
578 if(!isWorkerErrCode(rc)){
stephan138647a2022-09-20 03:31:02 +0000579 const sz = state.s11n.deserialize()[0];
580 wasm.setMemValue(pSz64, BigInt(sz), 'i64');
stephanc5313af2022-09-18 02:35:30 +0000581 }
stephanf8150112022-09-19 17:09:09 +0000582 mTimeEnd();
stephanc5313af2022-09-18 02:35:30 +0000583 return rc;
584 },
585 xLock: function(pFile,lockType){
586 //2022-09: OPFS handles lock when opened
587 //warn("xLock(",arguments,") is a no-op");
588 return 0;
589 },
stephan138647a2022-09-20 03:31:02 +0000590 xRead: function(pFile,pDest,n,offset64){
stephanc5313af2022-09-18 02:35:30 +0000591 /* int (*xRead)(sqlite3_file*, void*, int iAmt, sqlite3_int64 iOfst) */
stephanf8150112022-09-19 17:09:09 +0000592 mTimeStart('xRead');
stephanc5313af2022-09-18 02:35:30 +0000593 const f = __openFiles[pFile];
594 let rc;
595 try {
stephan138647a2022-09-20 03:31:02 +0000596 rc = opRun('xRead',pFile, n, Number(offset64));
stephan862281f2022-09-19 09:25:25 +0000597 if(0===rc || capi.SQLITE_IOERR_SHORT_READ===rc){
stephanf8150112022-09-19 17:09:09 +0000598 // set() seems to be the fastest way to copy this...
599 wasm.heap8u().set(f.sabView.subarray(0, n), pDest);
stephan862281f2022-09-19 09:25:25 +0000600 }
stephanc5313af2022-09-18 02:35:30 +0000601 }catch(e){
602 error("xRead(",arguments,") failed:",e,f);
603 rc = capi.SQLITE_IOERR_READ;
604 }
stephanf8150112022-09-19 17:09:09 +0000605 mTimeEnd();
stephanc5313af2022-09-18 02:35:30 +0000606 return rc;
607 },
608 xSync: function(pFile,flags){
stephanaec046a2022-09-19 18:22:29 +0000609 ++metrics.xSync.count;
stephan138647a2022-09-20 03:31:02 +0000610 return 0; // impl'd in xFileControl()
stephanc5313af2022-09-18 02:35:30 +0000611 },
612 xTruncate: function(pFile,sz64){
stephanf8150112022-09-19 17:09:09 +0000613 mTimeStart('xTruncate');
stephan138647a2022-09-20 03:31:02 +0000614 const rc = opRun('xTruncate', pFile, Number(sz64));
stephanf8150112022-09-19 17:09:09 +0000615 mTimeEnd();
616 return rc;
stephanc5313af2022-09-18 02:35:30 +0000617 },
618 xUnlock: function(pFile,lockType){
619 //2022-09: OPFS handles lock when opened
620 //warn("xUnlock(",arguments,") is a no-op");
621 return 0;
622 },
stephan138647a2022-09-20 03:31:02 +0000623 xWrite: function(pFile,pSrc,n,offset64){
stephanc5313af2022-09-18 02:35:30 +0000624 /* int (*xWrite)(sqlite3_file*, const void*, int iAmt, sqlite3_int64 iOfst) */
stephanf8150112022-09-19 17:09:09 +0000625 mTimeStart('xWrite');
stephanc5313af2022-09-18 02:35:30 +0000626 const f = __openFiles[pFile];
stephanf8150112022-09-19 17:09:09 +0000627 let rc;
stephanc5313af2022-09-18 02:35:30 +0000628 try {
stephanf8150112022-09-19 17:09:09 +0000629 f.sabView.set(wasm.heap8u().subarray(pSrc, pSrc+n));
stephan138647a2022-09-20 03:31:02 +0000630 rc = opRun('xWrite', pFile, n, Number(offset64));
stephanc5313af2022-09-18 02:35:30 +0000631 }catch(e){
632 error("xWrite(",arguments,") failed:",e,f);
stephanf8150112022-09-19 17:09:09 +0000633 rc = capi.SQLITE_IOERR_WRITE;
stephanc5313af2022-09-18 02:35:30 +0000634 }
stephanf8150112022-09-19 17:09:09 +0000635 mTimeEnd();
636 return rc;
stephanc5313af2022-09-18 02:35:30 +0000637 }
638 }/*ioSyncWrappers*/;
639
640 /**
641 Impls for the sqlite3_vfs methods. Maintenance reminder: members
642 are in alphabetical order to simplify finding them.
643 */
644 const vfsSyncWrappers = {
645 xAccess: function(pVfs,zName,flags,pOut){
stephanf8150112022-09-19 17:09:09 +0000646 mTimeStart('xAccess');
stephan5e8bb0a2022-09-20 08:27:57 +0000647 const rc = opRun('xAccess', wasm.cstringToJs(zName));
648 wasm.setMemValue( pOut, (rc ? 0 : 1), 'i32' );
stephanf8150112022-09-19 17:09:09 +0000649 mTimeEnd();
stephanc5313af2022-09-18 02:35:30 +0000650 return 0;
651 },
652 xCurrentTime: function(pVfs,pOut){
653 /* If it turns out that we need to adjust for timezone, see:
654 https://stackoverflow.com/a/11760121/1458521 */
655 wasm.setMemValue(pOut, 2440587.5 + (new Date().getTime()/86400000),
656 'double');
657 return 0;
658 },
659 xCurrentTimeInt64: function(pVfs,pOut){
660 // TODO: confirm that this calculation is correct
661 wasm.setMemValue(pOut, (2440587.5 * 86400000) + new Date().getTime(),
662 'i64');
663 return 0;
664 },
665 xDelete: function(pVfs, zName, doSyncDir){
stephanf8150112022-09-19 17:09:09 +0000666 mTimeStart('xDelete');
stephan138647a2022-09-20 03:31:02 +0000667 opRun('xDelete', wasm.cstringToJs(zName), doSyncDir, false);
stephanf3860122022-09-18 17:32:35 +0000668 /* We're ignoring errors because we cannot yet differentiate
669 between harmless and non-harmless failures. */
stephanf8150112022-09-19 17:09:09 +0000670 mTimeEnd();
stephanf3860122022-09-18 17:32:35 +0000671 return 0;
stephanc5313af2022-09-18 02:35:30 +0000672 },
673 xFullPathname: function(pVfs,zName,nOut,pOut){
674 /* Until/unless we have some notion of "current dir"
675 in OPFS, simply copy zName to pOut... */
676 const i = wasm.cstrncpy(pOut, zName, nOut);
677 return i<nOut ? 0 : capi.SQLITE_CANTOPEN
678 /*CANTOPEN is required by the docs but SQLITE_RANGE would be a closer match*/;
679 },
680 xGetLastError: function(pVfs,nOut,pOut){
681 /* TODO: store exception.message values from the async
682 partner in a dedicated SharedArrayBuffer, noting that we'd have
683 to encode them... TextEncoder can do that for us. */
684 warn("OPFS xGetLastError() has nothing sensible to return.");
685 return 0;
686 },
stephan8766fd22022-09-19 05:19:04 +0000687 //xSleep is optionally defined below
stephanc5313af2022-09-18 02:35:30 +0000688 xOpen: function f(pVfs, zName, pFile, flags, pOutFlags){
stephanf8150112022-09-19 17:09:09 +0000689 mTimeStart('xOpen');
stephanc5313af2022-09-18 02:35:30 +0000690 if(!f._){
691 f._ = {
692 fileTypes: {
693 SQLITE_OPEN_MAIN_DB: 'mainDb',
694 SQLITE_OPEN_MAIN_JOURNAL: 'mainJournal',
695 SQLITE_OPEN_TEMP_DB: 'tempDb',
696 SQLITE_OPEN_TEMP_JOURNAL: 'tempJournal',
697 SQLITE_OPEN_TRANSIENT_DB: 'transientDb',
698 SQLITE_OPEN_SUBJOURNAL: 'subjournal',
699 SQLITE_OPEN_SUPER_JOURNAL: 'superJournal',
700 SQLITE_OPEN_WAL: 'wal'
701 },
702 getFileType: function(filename,oflags){
703 const ft = f._.fileTypes;
704 for(let k of Object.keys(ft)){
705 if(oflags & capi[k]) return ft[k];
706 }
707 warn("Cannot determine fileType based on xOpen() flags for file",filename);
708 return '???';
709 }
710 };
711 }
712 if(0===zName){
713 zName = randomFilename();
714 }else if('number'===typeof zName){
715 zName = wasm.cstringToJs(zName);
716 }
stephan138647a2022-09-20 03:31:02 +0000717 const fh = Object.create(null);
718 fh.fid = pFile;
719 fh.filename = zName;
720 fh.sab = new SharedArrayBuffer(state.fileBufferSize);
721 fh.flags = flags;
722 const rc = opRun('xOpen', pFile, zName, flags);
stephanc5313af2022-09-18 02:35:30 +0000723 if(!rc){
724 /* Recall that sqlite3_vfs::xClose() will be called, even on
725 error, unless pFile->pMethods is NULL. */
stephan138647a2022-09-20 03:31:02 +0000726 if(fh.readOnly){
stephanc5313af2022-09-18 02:35:30 +0000727 wasm.setMemValue(pOutFlags, capi.SQLITE_OPEN_READONLY, 'i32');
728 }
stephan138647a2022-09-20 03:31:02 +0000729 __openFiles[pFile] = fh;
730 fh.sabView = state.sabFileBufView;
731 fh.sq3File = new sqlite3_file(pFile);
732 fh.sq3File.$pMethods = opfsIoMethods.pointer;
stephanc5313af2022-09-18 02:35:30 +0000733 }
stephanf8150112022-09-19 17:09:09 +0000734 mTimeEnd();
stephanc5313af2022-09-18 02:35:30 +0000735 return rc;
736 }/*xOpen()*/
737 }/*vfsSyncWrappers*/;
738
stephan8766fd22022-09-19 05:19:04 +0000739 if(dVfs){
740 opfsVfs.$xRandomness = dVfs.$xRandomness;
741 opfsVfs.$xSleep = dVfs.$xSleep;
742 }
stephanc5313af2022-09-18 02:35:30 +0000743 if(!opfsVfs.$xRandomness){
744 /* If the default VFS has no xRandomness(), add a basic JS impl... */
745 vfsSyncWrappers.xRandomness = function(pVfs, nOut, pOut){
746 const heap = wasm.heap8u();
747 let i = 0;
748 for(; i < nOut; ++i) heap[pOut + i] = (Math.random()*255000) & 0xFF;
749 return i;
750 };
751 }
752 if(!opfsVfs.$xSleep){
753 /* If we can inherit an xSleep() impl from the default VFS then
stephan8766fd22022-09-19 05:19:04 +0000754 assume it's sane and use it, otherwise install a JS-based
755 one. */
756 vfsSyncWrappers.xSleep = function(pVfs,ms){
stephanc4b87be2022-09-20 01:28:47 +0000757 Atomics.wait(state.sabOPView, state.opIds.xSleep, 0, ms);
stephan8766fd22022-09-19 05:19:04 +0000758 return 0;
759 };
stephanc5313af2022-09-18 02:35:30 +0000760 }
761
762 /* Install the vfs/io_methods into their C-level shared instances... */
763 let inst = installMethod(opfsIoMethods);
764 for(let k of Object.keys(ioSyncWrappers)) inst(k, ioSyncWrappers[k]);
765 inst = installMethod(opfsVfs);
766 for(let k of Object.keys(vfsSyncWrappers)) inst(k, vfsSyncWrappers[k]);
stephanf3860122022-09-18 17:32:35 +0000767
stephanf3860122022-09-18 17:32:35 +0000768 /**
769 Syncronously deletes the given OPFS filesystem entry, ignoring
770 any errors. As this environment has no notion of "current
771 directory", the given name must be an absolute path. If the 2nd
772 argument is truthy, deletion is recursive (use with caution!).
773
774 Returns true if the deletion succeeded and fails if it fails,
775 but cannot report the nature of the failure.
776 */
stephan0e0687c2022-09-19 13:44:23 +0000777 opfsUtil.deleteEntry = function(fsEntryName,recursive=false){
stephan138647a2022-09-20 03:31:02 +0000778 return 0===opRun('xDelete', fsEntryName, 0, recursive);
stephanf3860122022-09-18 17:32:35 +0000779 };
780 /**
stephanf3860122022-09-18 17:32:35 +0000781 Synchronously creates the given directory name, recursively, in
782 the OPFS filesystem. Returns true if it succeeds or the
783 directory already exists, else false.
784 */
stephan5e8bb0a2022-09-20 08:27:57 +0000785 opfsUtil.mkdir = function(absDirName){
stephanf3860122022-09-18 17:32:35 +0000786 return 0===opRun('mkdir', absDirName);
787 };
788 /**
789 Synchronously checks whether the given OPFS filesystem exists,
790 returning true if it does, false if it doesn't.
791 */
792 opfsUtil.entryExists = function(fsEntryName){
793 return 0===opRun('xAccess', fsEntryName);
794 };
795
796 /**
797 Generates a random ASCII string, intended for use as a
798 temporary file name. Its argument is the length of the string,
799 defaulting to 16.
800 */
801 opfsUtil.randomFilename = randomFilename;
802
803 if(sqlite3.oo1){
804 opfsUtil.OpfsDb = function(...args){
805 const opt = sqlite3.oo1.dbCtorHelper.normalizeArgs(...args);
806 opt.vfs = opfsVfs.$zName;
807 sqlite3.oo1.dbCtorHelper.call(this, opt);
808 };
809 opfsUtil.OpfsDb.prototype = Object.create(sqlite3.oo1.DB.prototype);
810 }
stephanf8150112022-09-19 17:09:09 +0000811
stephanf3860122022-09-18 17:32:35 +0000812 /**
813 Potential TODOs:
814
815 - Expose one or both of the Worker objects via opfsUtil and
816 publish an interface for proxying the higher-level OPFS
817 features like getting a directory listing.
818 */
stephanc5313af2022-09-18 02:35:30 +0000819
stephan5e8bb0a2022-09-20 08:27:57 +0000820 const sanityCheck = function(){
stephanc5313af2022-09-18 02:35:30 +0000821 const scope = wasm.scopedAllocPush();
822 const sq3File = new sqlite3_file();
823 try{
824 const fid = sq3File.pointer;
825 const openFlags = capi.SQLITE_OPEN_CREATE
826 | capi.SQLITE_OPEN_READWRITE
827 //| capi.SQLITE_OPEN_DELETEONCLOSE
828 | capi.SQLITE_OPEN_MAIN_DB;
829 const pOut = wasm.scopedAlloc(8);
stephanb8c8d4e2022-09-20 13:25:39 +0000830 const dbFile = "/sanity/check/file"+randomFilename(8);
stephanc5313af2022-09-18 02:35:30 +0000831 const zDbFile = wasm.scopedAllocCString(dbFile);
832 let rc;
833 vfsSyncWrappers.xAccess(opfsVfs.pointer, zDbFile, 0, pOut);
834 rc = wasm.getMemValue(pOut,'i32');
835 log("xAccess(",dbFile,") exists ?=",rc);
836 rc = vfsSyncWrappers.xOpen(opfsVfs.pointer, zDbFile,
837 fid, openFlags, pOut);
stephanc4b87be2022-09-20 01:28:47 +0000838 log("open rc =",rc,"state.sabOPView[xOpen] =",
839 state.sabOPView[state.opIds.xOpen]);
stephanc5313af2022-09-18 02:35:30 +0000840 if(isWorkerErrCode(rc)){
841 error("open failed with code",rc);
842 return;
843 }
844 vfsSyncWrappers.xAccess(opfsVfs.pointer, zDbFile, 0, pOut);
845 rc = wasm.getMemValue(pOut,'i32');
846 if(!rc) toss("xAccess() failed to detect file.");
847 rc = ioSyncWrappers.xSync(sq3File.pointer, 0);
848 if(rc) toss('sync failed w/ rc',rc);
849 rc = ioSyncWrappers.xTruncate(sq3File.pointer, 1024);
850 if(rc) toss('truncate failed w/ rc',rc);
851 wasm.setMemValue(pOut,0,'i64');
852 rc = ioSyncWrappers.xFileSize(sq3File.pointer, pOut);
853 if(rc) toss('xFileSize failed w/ rc',rc);
854 log("xFileSize says:",wasm.getMemValue(pOut, 'i64'));
855 rc = ioSyncWrappers.xWrite(sq3File.pointer, zDbFile, 10, 1);
856 if(rc) toss("xWrite() failed!");
857 const readBuf = wasm.scopedAlloc(16);
858 rc = ioSyncWrappers.xRead(sq3File.pointer, readBuf, 6, 2);
859 wasm.setMemValue(readBuf+6,0);
860 let jRead = wasm.cstringToJs(readBuf);
861 log("xRead() got:",jRead);
862 if("sanity"!==jRead) toss("Unexpected xRead() value.");
stephan8766fd22022-09-19 05:19:04 +0000863 if(vfsSyncWrappers.xSleep){
864 log("xSleep()ing before close()ing...");
865 vfsSyncWrappers.xSleep(opfsVfs.pointer,2000);
866 log("waking up from xSleep()");
867 }
stephanc5313af2022-09-18 02:35:30 +0000868 rc = ioSyncWrappers.xClose(fid);
stephanc4b87be2022-09-20 01:28:47 +0000869 log("xClose rc =",rc,"sabOPView =",state.sabOPView);
stephanc5313af2022-09-18 02:35:30 +0000870 log("Deleting file:",dbFile);
871 vfsSyncWrappers.xDelete(opfsVfs.pointer, zDbFile, 0x1234);
872 vfsSyncWrappers.xAccess(opfsVfs.pointer, zDbFile, 0, pOut);
873 rc = wasm.getMemValue(pOut,'i32');
874 if(rc) toss("Expecting 0 from xAccess(",dbFile,") after xDelete().");
stephanc9e26022022-09-20 10:11:52 +0000875 warn("End of OPFS sanity checks.");
stephanc5313af2022-09-18 02:35:30 +0000876 }finally{
877 sq3File.dispose();
878 wasm.scopedAllocPop(scope);
879 }
880 }/*sanityCheck()*/;
881
stephanf8150112022-09-19 17:09:09 +0000882
stephanc5313af2022-09-18 02:35:30 +0000883 W.onmessage = function({data}){
884 //log("Worker.onmessage:",data);
885 switch(data.type){
stephan138647a2022-09-20 03:31:02 +0000886 case 'opfs-async-loaded':
stephanc5313af2022-09-18 02:35:30 +0000887 /*Pass our config and shared state on to the async worker.*/
stephan5e8bb0a2022-09-20 08:27:57 +0000888 W.postMessage({type: 'opfs-async-init',args: state});
stephanc5313af2022-09-18 02:35:30 +0000889 break;
stephan138647a2022-09-20 03:31:02 +0000890 case 'opfs-async-inited':{
stephanc5313af2022-09-18 02:35:30 +0000891 /*Indicates that the async partner has received the 'init',
892 so we now know that the state object is no longer subject to
893 being copied by a pending postMessage() call.*/
894 try {
stephan0e0687c2022-09-19 13:44:23 +0000895 const rc = capi.sqlite3_vfs_register(opfsVfs.pointer, 0);
stephanc5313af2022-09-18 02:35:30 +0000896 if(rc){
897 opfsVfs.dispose();
898 toss("sqlite3_vfs_register(OPFS) failed with rc",rc);
899 }
900 if(opfsVfs.pointer !== capi.sqlite3_vfs_find("opfs")){
901 toss("BUG: sqlite3_vfs_find() failed for just-installed OPFS VFS");
902 }
903 capi.sqlite3_vfs_register.addReference(opfsVfs, opfsIoMethods);
stephanc4b87be2022-09-20 01:28:47 +0000904 state.sabOPView = new Int32Array(state.sabOP);
stephan138647a2022-09-20 03:31:02 +0000905 state.sabFileBufView = new Uint8Array(state.sabIO, 0, state.fileBufferSize);
906 state.sabS11nView = new Uint8Array(state.sabIO, state.sabS11nOffset, state.sabS11nSize);
907 initS11n();
stephanc5313af2022-09-18 02:35:30 +0000908 if(options.sanityChecks){
909 warn("Running sanity checks because of opfs-sanity-check URL arg...");
910 sanityCheck();
911 }
stephanf3860122022-09-18 17:32:35 +0000912 W.onerror = W._originalOnError;
913 delete W._originalOnError;
914 sqlite3.opfs = opfsUtil;
stephanc5313af2022-09-18 02:35:30 +0000915 log("End of OPFS sqlite3_vfs setup.", opfsVfs);
stephan509f4052022-09-19 09:58:01 +0000916 promiseResolve(sqlite3);
stephanc5313af2022-09-18 02:35:30 +0000917 }catch(e){
918 error(e);
919 promiseReject(e);
920 }
921 break;
922 }
923 default:
924 promiseReject(e);
925 error("Unexpected message from the async worker:",data);
926 break;
927 }
928 };
929 })/*thePromise*/;
930 return thePromise;
931}/*installOpfsVfs()*/;
932sqlite3.installOpfsVfs.defaultProxyUri = "sqlite3-opfs-async-proxy.js";
933}/*sqlite3ApiBootstrap.initializers.push()*/);