1453af2f6Sstephan/*
2453af2f6Sstephan  2022-07-22
3453af2f6Sstephan
4453af2f6Sstephan  The author disclaims copyright to this source code.  In place of a
5453af2f6Sstephan  legal notice, here is a blessing:
6453af2f6Sstephan
7453af2f6Sstephan  *   May you do good and not evil.
8453af2f6Sstephan  *   May you find forgiveness for yourself and forgive others.
9453af2f6Sstephan  *   May you share freely, never taking more than you give.
10453af2f6Sstephan
11453af2f6Sstephan  ***********************************************************************
12453af2f6Sstephan
13453af2f6Sstephan  This file implements the initializer for the sqlite3 "Worker API
14453af2f6Sstephan  #1", a very basic DB access API intended to be scripted from a main
15453af2f6Sstephan  window thread via Worker-style messages. Because of limitations in
16453af2f6Sstephan  that type of communication, this API is minimalistic and only
17453af2f6Sstephan  capable of serving relatively basic DB requests (e.g. it cannot
18453af2f6Sstephan  process nested query loops concurrently).
19453af2f6Sstephan
20453af2f6Sstephan  This file requires that the core C-style sqlite3 API and OO API #1
21453af2f6Sstephan  have been loaded.
22453af2f6Sstephan*/
23453af2f6Sstephan
24453af2f6Sstephan/**
259afff9f3Sstephan  sqlite3.initWorker1API() implements a Worker-based wrapper around
269afff9f3Sstephan  SQLite3 OO API #1, colloquially known as "Worker API #1".
27453af2f6Sstephan
28453af2f6Sstephan  In order to permit this API to be loaded in worker threads without
29453af2f6Sstephan  automatically registering onmessage handlers, initializing the
309afff9f3Sstephan  worker API requires calling initWorker1API(). If this function is
319afff9f3Sstephan  called from a non-worker thread then it throws an exception.  It
329afff9f3Sstephan  must only be called once per Worker.
33453af2f6Sstephan
34453af2f6Sstephan  When initialized, it installs message listeners to receive Worker
35453af2f6Sstephan  messages and then it posts a message in the form:
36453af2f6Sstephan
37453af2f6Sstephan  ```
38a9ac2ed0Sstephan  {type:'sqlite3-api', result:'worker1-ready'}
39453af2f6Sstephan  ```
40453af2f6Sstephan
41453af2f6Sstephan  to let the client know that it has been initialized. Clients may
42453af2f6Sstephan  optionally depend on this function not returning until
43453af2f6Sstephan  initialization is complete, as the initialization is synchronous.
44453af2f6Sstephan  In some contexts, however, listening for the above message is
45453af2f6Sstephan  a better fit.
463734401aSstephan
473734401aSstephan  Note that the worker-based interface can be slightly quirky because
483734401aSstephan  of its async nature. In particular, any number of messages may be posted
493734401aSstephan  to the worker before it starts handling any of them. If, e.g., an
503734401aSstephan  "open" operation fails, any subsequent messages will fail. The
513734401aSstephan  Promise-based wrapper for this API (`sqlite3-worker1-promiser.js`)
523734401aSstephan  is more comfortable to use in that regard.
533734401aSstephan
549afff9f3Sstephan  The documentation for the input and output worker messages for
559afff9f3Sstephan  this API follows...
563734401aSstephan
579afff9f3Sstephan  ====================================================================
589afff9f3Sstephan  Common message format...
599afff9f3Sstephan
609afff9f3Sstephan  Each message posted to the worker has an operation-independent
619afff9f3Sstephan  envelope and operation-dependent arguments:
629afff9f3Sstephan
639afff9f3Sstephan  ```
649afff9f3Sstephan  {
659afff9f3Sstephan    type: string, // one of: 'open', 'close', 'exec', 'config-get'
669afff9f3Sstephan
679afff9f3Sstephan    messageId: OPTIONAL arbitrary value. The worker will copy it as-is
689afff9f3Sstephan    into response messages to assist in client-side dispatching.
699afff9f3Sstephan
709afff9f3Sstephan    dbId: a db identifier string (returned by 'open') which tells the
719afff9f3Sstephan    operation which database instance to work on. If not provided, the
729afff9f3Sstephan    first-opened db is used. This is an "opaque" value, with no
739afff9f3Sstephan    inherently useful syntax or information. Its value is subject to
749afff9f3Sstephan    change with any given build of this API and cannot be used as a
759afff9f3Sstephan    basis for anything useful beyond its one intended purpose.
769afff9f3Sstephan
779afff9f3Sstephan    args: ...operation-dependent arguments...
789afff9f3Sstephan
799afff9f3Sstephan    // the framework may add other properties for testing or debugging
809afff9f3Sstephan    // purposes.
819afff9f3Sstephan
829afff9f3Sstephan  }
839afff9f3Sstephan  ```
849afff9f3Sstephan
859afff9f3Sstephan  Response messages, posted back to the main thread, look like:
869afff9f3Sstephan
879afff9f3Sstephan  ```
889afff9f3Sstephan  {
899afff9f3Sstephan    type: string. Same as above except for error responses, which have the type
909afff9f3Sstephan    'error',
919afff9f3Sstephan
929afff9f3Sstephan    messageId: same value, if any, provided by the inbound message
939afff9f3Sstephan
949afff9f3Sstephan    dbId: the id of the db which was operated on, if any, as returned
959afff9f3Sstephan    by the corresponding 'open' operation.
969afff9f3Sstephan
979afff9f3Sstephan    result: ...operation-dependent result...
989afff9f3Sstephan
999afff9f3Sstephan  }
1009afff9f3Sstephan  ```
1019afff9f3Sstephan
1029afff9f3Sstephan  ====================================================================
1039afff9f3Sstephan  Error responses
1049afff9f3Sstephan
1059afff9f3Sstephan  Errors are reported messages in an operation-independent format:
1069afff9f3Sstephan
1079afff9f3Sstephan  ```
1089afff9f3Sstephan  {
109fd31ae3bSstephan    type: "error",
1109afff9f3Sstephan
1119afff9f3Sstephan    messageId: ...as above...,
1129afff9f3Sstephan
1139afff9f3Sstephan    dbId: ...as above...
1149afff9f3Sstephan
1159afff9f3Sstephan    result: {
1169afff9f3Sstephan
1179afff9f3Sstephan      operation: type of the triggering operation: 'open', 'close', ...
1189afff9f3Sstephan
1199afff9f3Sstephan      message: ...error message text...
1209afff9f3Sstephan
1219afff9f3Sstephan      errorClass: string. The ErrorClass.name property from the thrown exception.
1229afff9f3Sstephan
1239afff9f3Sstephan      input: the message object which triggered the error.
1249afff9f3Sstephan
1259afff9f3Sstephan      stack: _if available_, a stack trace array.
1269afff9f3Sstephan
1279afff9f3Sstephan    }
1289afff9f3Sstephan
1299afff9f3Sstephan  }
1309afff9f3Sstephan  ```
1319afff9f3Sstephan
1329afff9f3Sstephan
1339afff9f3Sstephan  ====================================================================
1349afff9f3Sstephan  "config-get"
1359afff9f3Sstephan
1369afff9f3Sstephan  This operation fetches the serializable parts of the sqlite3 API
1379afff9f3Sstephan  configuration.
1389afff9f3Sstephan
1399afff9f3Sstephan  Message format:
1409afff9f3Sstephan
1419afff9f3Sstephan  ```
1429afff9f3Sstephan  {
1439afff9f3Sstephan    type: "config-get",
1449afff9f3Sstephan    messageId: ...as above...,
1459afff9f3Sstephan    args: currently ignored and may be elided.
1469afff9f3Sstephan  }
1479afff9f3Sstephan  ```
1489afff9f3Sstephan
1499afff9f3Sstephan  Response:
1509afff9f3Sstephan
1519afff9f3Sstephan  ```
1529afff9f3Sstephan  {
153fd31ae3bSstephan    type: "config-get",
1549afff9f3Sstephan    messageId: ...as above...,
1559afff9f3Sstephan    result: {
1569afff9f3Sstephan
157d18f1bbfSstephan      version: sqlite3.version object
1589afff9f3Sstephan
1599afff9f3Sstephan      bigIntEnabled: bool. True if BigInt support is enabled.
1609afff9f3Sstephan
161d18f1bbfSstephan      wasmfsOpfsDir: path prefix, if any, _intended_ for use with
162ae589b69Sstephan      WASMFS OPFS persistent storage.
1639afff9f3Sstephan
164d18f1bbfSstephan      wasmfsOpfsEnabled: true if persistent storage is enabled in the
165d18f1bbfSstephan      current environment. Only files stored under wasmfsOpfsDir
166d18f1bbfSstephan      will persist using that mechanism, however. It is legal to use
167d18f1bbfSstephan      the non-WASMFS OPFS VFS to open a database via a URI-style
168d18f1bbfSstephan      db filename.
169d18f1bbfSstephan
1708a8244b5Sstephan      vfsList: result of sqlite3.capi.sqlite3_js_vfs_list()
1719afff9f3Sstephan   }
1729afff9f3Sstephan  }
1739afff9f3Sstephan  ```
1749afff9f3Sstephan
1759afff9f3Sstephan
1769afff9f3Sstephan  ====================================================================
1779afff9f3Sstephan  "open" a database
1789afff9f3Sstephan
1799afff9f3Sstephan  Message format:
1809afff9f3Sstephan
1819afff9f3Sstephan  ```
1829afff9f3Sstephan  {
1839afff9f3Sstephan    type: "open",
1849afff9f3Sstephan    messageId: ...as above...,
1859afff9f3Sstephan    args:{
1869afff9f3Sstephan
1879afff9f3Sstephan      filename [=":memory:" or "" (unspecified)]: the db filename.
188d18f1bbfSstephan      See the sqlite3.oo1.DB constructor for peculiarities and
189d18f1bbfSstephan      transformations,
1909afff9f3Sstephan
19149048b14Sstephan      vfs: sqlite3_vfs name. Ignored if filename is ":memory:" or "".
19249048b14Sstephan           This may change how the given filename is resolved.
1939afff9f3Sstephan    }
1949afff9f3Sstephan  }
1959afff9f3Sstephan  ```
1969afff9f3Sstephan
1979afff9f3Sstephan  Response:
1989afff9f3Sstephan
1999afff9f3Sstephan  ```
2009afff9f3Sstephan  {
201fd31ae3bSstephan    type: "open",
2029afff9f3Sstephan    messageId: ...as above...,
2039afff9f3Sstephan    result: {
2049afff9f3Sstephan      filename: db filename, possibly differing from the input.
2059afff9f3Sstephan
2069afff9f3Sstephan      dbId: an opaque ID value which must be passed in the message
2079afff9f3Sstephan      envelope to other calls in this API to tell them which db to
2089afff9f3Sstephan      use. If it is not provided to future calls, they will default to
20902d15a7eSstephan      operating on the least-recently-opened db. This property is, for
21002d15a7eSstephan      API consistency's sake, also part of the containing message
21102d15a7eSstephan      envelope.  Only the `open` operation includes it in the `result`
21202d15a7eSstephan      property.
2139afff9f3Sstephan
2149afff9f3Sstephan      persistent: true if the given filename resides in the
215ae589b69Sstephan      known-persistent storage, else false.
216d18f1bbfSstephan
21749048b14Sstephan      vfs: name of the VFS the "main" db is using.
2189afff9f3Sstephan   }
2199afff9f3Sstephan  }
2209afff9f3Sstephan  ```
2219afff9f3Sstephan
2229afff9f3Sstephan  ====================================================================
2239afff9f3Sstephan  "close" a database
2249afff9f3Sstephan
2259afff9f3Sstephan  Message format:
2269afff9f3Sstephan
2279afff9f3Sstephan  ```
2289afff9f3Sstephan  {
2299afff9f3Sstephan    type: "close",
2309afff9f3Sstephan    messageId: ...as above...
2319afff9f3Sstephan    dbId: ...as above...
2328a8244b5Sstephan    args: OPTIONAL {unlink: boolean}
2339afff9f3Sstephan  }
2349afff9f3Sstephan  ```
2359afff9f3Sstephan
2368a8244b5Sstephan  If the `dbId` does not refer to an opened ID, this is a no-op. If
2378a8244b5Sstephan  the `args` object contains a truthy `unlink` value then the database
23896b6371dSstephan  will be unlinked (deleted) after closing it. The inability to close a
2398a8244b5Sstephan  db (because it's not opened) or delete its file does not trigger an
2408a8244b5Sstephan  error.
2419afff9f3Sstephan
2429afff9f3Sstephan  Response:
2439afff9f3Sstephan
2449afff9f3Sstephan  ```
2459afff9f3Sstephan  {
246fd31ae3bSstephan    type: "close",
2479afff9f3Sstephan    messageId: ...as above...,
2489afff9f3Sstephan    result: {
2499afff9f3Sstephan
2509afff9f3Sstephan      filename: filename of closed db, or undefined if no db was closed
2519afff9f3Sstephan
2529afff9f3Sstephan    }
2539afff9f3Sstephan  }
2549afff9f3Sstephan  ```
2559afff9f3Sstephan
2569afff9f3Sstephan  ====================================================================
2579afff9f3Sstephan  "exec" SQL
2589afff9f3Sstephan
2599afff9f3Sstephan  All SQL execution is processed through the exec operation. It offers
2609afff9f3Sstephan  most of the features of the oo1.DB.exec() method, with a few limitations
2619afff9f3Sstephan  imposed by the state having to cross thread boundaries.
2629afff9f3Sstephan
2639afff9f3Sstephan  Message format:
2649afff9f3Sstephan
2659afff9f3Sstephan  ```
2669afff9f3Sstephan  {
2679afff9f3Sstephan    type: "exec",
2689afff9f3Sstephan    messageId: ...as above...
2699afff9f3Sstephan    dbId: ...as above...
2709afff9f3Sstephan    args: string (SQL) or {... see below ...}
2719afff9f3Sstephan  }
2729afff9f3Sstephan  ```
2739afff9f3Sstephan
2749afff9f3Sstephan  Response:
2759afff9f3Sstephan
2769afff9f3Sstephan  ```
2779afff9f3Sstephan  {
278fd31ae3bSstephan    type: "exec",
2799afff9f3Sstephan    messageId: ...as above...,
2809afff9f3Sstephan    dbId: ...as above...
2819afff9f3Sstephan    result: {
2829afff9f3Sstephan      input arguments, possibly modified. See below.
2839afff9f3Sstephan    }
2849afff9f3Sstephan  }
2859afff9f3Sstephan  ```
2869afff9f3Sstephan
2879afff9f3Sstephan  The arguments are in the same form accepted by oo1.DB.exec(), with
2889afff9f3Sstephan  the exceptions noted below.
2899afff9f3Sstephan
2909afff9f3Sstephan  A function-type args.callback property cannot cross
2919afff9f3Sstephan  the window/Worker boundary, so is not useful here. If
2929afff9f3Sstephan  args.callback is a string then it is assumed to be a
2939afff9f3Sstephan  message type key, in which case a callback function will be
2949afff9f3Sstephan  applied which posts each row result via:
2959afff9f3Sstephan
2969afff9f3Sstephan  postMessage({type: thatKeyType,
2979afff9f3Sstephan               rowNumber: 1-based-#,
2989afff9f3Sstephan               row: theRow,
2999afff9f3Sstephan               columnNames: anArray
3009afff9f3Sstephan               })
3019afff9f3Sstephan
3029afff9f3Sstephan  And, at the end of the result set (whether or not any result rows
3039afff9f3Sstephan  were produced), it will post an identical message with
3049afff9f3Sstephan  (row=undefined, rowNumber=null) to alert the caller than the result
3059afff9f3Sstephan  set is completed. Note that a row value of `null` is a legal row
3069afff9f3Sstephan  result for certain arg.rowMode values.
3079afff9f3Sstephan
3089afff9f3Sstephan    (Design note: we don't use (row=undefined, rowNumber=undefined) to
3099afff9f3Sstephan    indicate end-of-results because fetching those would be
3109afff9f3Sstephan    indistinguishable from fetching from an empty object unless the
3119afff9f3Sstephan    client used hasOwnProperty() (or similar) to distinguish "missing
3129afff9f3Sstephan    property" from "property with the undefined value".  Similarly,
3139afff9f3Sstephan    `null` is a legal value for `row` in some case , whereas the db
3149afff9f3Sstephan    layer won't emit a result value of `undefined`.)
3159afff9f3Sstephan
3169afff9f3Sstephan  The callback proxy must not recurse into this interface. An exec()
317ae589b69Sstephan  call will tie up the Worker thread, causing any recursion attempt
3189afff9f3Sstephan  to wait until the first exec() is completed.
3199afff9f3Sstephan
3209afff9f3Sstephan  The response is the input options object (or a synthesized one if
3219afff9f3Sstephan  passed only a string), noting that options.resultRows and
3229afff9f3Sstephan  options.columnNames may be populated by the call to db.exec().
3233734401aSstephan
324453af2f6Sstephan*/
325e3cd6760Sstephanself.sqlite3ApiBootstrap.initializers.push(function(sqlite3){
326e3cd6760Sstephansqlite3.initWorker1API = function(){
327453af2f6Sstephan  'use strict';
328453af2f6Sstephan  const toss = (...args)=>{throw new Error(args.join(' '))};
329fa5aac74Sstephan  if('function' !== typeof importScripts){
330ac51eb77Sstephan    toss("initWorker1API() must be run from a Worker thread.");
331453af2f6Sstephan  }
332453af2f6Sstephan  const self = this.self;
333453af2f6Sstephan  const sqlite3 = this.sqlite3 || toss("Missing this.sqlite3 object.");
334ac51eb77Sstephan  const DB = sqlite3.oo1.DB;
335453af2f6Sstephan
336453af2f6Sstephan  /**
337453af2f6Sstephan     Returns the app-wide unique ID for the given db, creating one if
338453af2f6Sstephan     needed.
339453af2f6Sstephan  */
340453af2f6Sstephan  const getDbId = function(db){
341453af2f6Sstephan    let id = wState.idMap.get(db);
342453af2f6Sstephan    if(id) return id;
343453af2f6Sstephan    id = 'db#'+(++wState.idSeq)+'@'+db.pointer;
344453af2f6Sstephan    /** ^^^ can't simply use db.pointer b/c closing/opening may re-use
345453af2f6Sstephan        the same address, which could map pending messages to a wrong
346453af2f6Sstephan        instance. */
347453af2f6Sstephan    wState.idMap.set(db, id);
348453af2f6Sstephan    return id;
349453af2f6Sstephan  };
350453af2f6Sstephan
351453af2f6Sstephan  /**
3523734401aSstephan     Internal helper for managing Worker-level state.
353453af2f6Sstephan  */
354453af2f6Sstephan  const wState = {
35502d15a7eSstephan    /**
35602d15a7eSstephan       Each opened DB is added to this.dbList, and the first entry in
35702d15a7eSstephan       that list is the default db. As each db is closed, its entry is
35802d15a7eSstephan       removed from the list.
35902d15a7eSstephan    */
36002d15a7eSstephan    dbList: [],
3619afff9f3Sstephan    /** Sequence number of dbId generation. */
362453af2f6Sstephan    idSeq: 0,
3639afff9f3Sstephan    /** Map of DB instances to dbId. */
364453af2f6Sstephan    idMap: new WeakMap,
3659afff9f3Sstephan    /** Temp holder for "transferable" postMessage() state. */
3669afff9f3Sstephan    xfer: [],
3679a34509aSstephan    open: function(opt){
36849048b14Sstephan      const db = new DB(opt);
369453af2f6Sstephan      this.dbs[getDbId(db)] = db;
37002d15a7eSstephan      if(this.dbList.indexOf(db)<0) this.dbList.push(db);
371453af2f6Sstephan      return db;
372453af2f6Sstephan    },
373453af2f6Sstephan    close: function(db,alsoUnlink){
374453af2f6Sstephan      if(db){
375453af2f6Sstephan        delete this.dbs[getDbId(db)];
37614ae1a53Sstephan        const filename = db.filename;
3778948fbeeSstephan        const pVfs = sqlite3.wasm.sqlite3_wasm_db_vfs(db.pointer, 0);
378453af2f6Sstephan        db.close();
37902d15a7eSstephan        const ddNdx = this.dbList.indexOf(db);
38002d15a7eSstephan        if(ddNdx>=0) this.dbList.splice(ddNdx, 1);
381842c5ee8Sstephan        if(alsoUnlink && filename && pVfs){
3828948fbeeSstephan          sqlite3.wasm.sqlite3_wasm_vfs_unlink(pVfs, filename);
383453af2f6Sstephan        }
384453af2f6Sstephan      }
385453af2f6Sstephan    },
3863734401aSstephan    /**
3873734401aSstephan       Posts the given worker message value. If xferList is provided,
3883734401aSstephan       it must be an array, in which case a copy of it passed as
3893734401aSstephan       postMessage()'s second argument and xferList.length is set to
3903734401aSstephan       0.
3913734401aSstephan    */
392a9ac2ed0Sstephan    post: function(msg,xferList){
3933734401aSstephan      if(xferList && xferList.length){
3943734401aSstephan        self.postMessage( msg, Array.from(xferList) );
395453af2f6Sstephan        xferList.length = 0;
396453af2f6Sstephan      }else{
397a9ac2ed0Sstephan        self.postMessage(msg);
398453af2f6Sstephan      }
399453af2f6Sstephan    },
400453af2f6Sstephan    /** Map of DB IDs to DBs. */
401453af2f6Sstephan    dbs: Object.create(null),
4029afff9f3Sstephan    /** Fetch the DB for the given id. Throw if require=true and the
4039afff9f3Sstephan        id is not valid, else return the db or undefined. */
404453af2f6Sstephan    getDb: function(id,require=true){
405453af2f6Sstephan      return this.dbs[id]
406453af2f6Sstephan        || (require ? toss("Unknown (or closed) DB ID:",id) : undefined);
407453af2f6Sstephan    }
408453af2f6Sstephan  };
409453af2f6Sstephan
41002d15a7eSstephan  /** Throws if the given db is falsy or not opened, else returns its
41102d15a7eSstephan      argument. */
41202d15a7eSstephan  const affirmDbOpen = function(db = wState.dbList[0]){
413453af2f6Sstephan    return (db && db.pointer) ? db : toss("DB is not opened.");
414453af2f6Sstephan  };
415453af2f6Sstephan
416453af2f6Sstephan  /** Extract dbId from the given message payload. */
417453af2f6Sstephan  const getMsgDb = function(msgData,affirmExists=true){
41802d15a7eSstephan    const db = wState.getDb(msgData.dbId,false) || wState.dbList[0];
419453af2f6Sstephan    return affirmExists ? affirmDbOpen(db) : db;
420453af2f6Sstephan  };
421453af2f6Sstephan
422453af2f6Sstephan  const getDefaultDbId = function(){
42302d15a7eSstephan    return wState.dbList[0] && getDbId(wState.dbList[0]);
424453af2f6Sstephan  };
425453af2f6Sstephan
426f45c3370Sstephan  const guessVfs = function(filename){
427f45c3370Sstephan    const m = /^file:.+(vfs=(\w+))/.exec(filename);
428f45c3370Sstephan    return sqlite3.capi.sqlite3_vfs_find(m ? m[2] : 0);
429f45c3370Sstephan  };
430f45c3370Sstephan
431f45c3370Sstephan  const isSpecialDbFilename = (n)=>{
432*4df2ab57Sstephan    return ""===n || ':'===n[0];
433f45c3370Sstephan  };
434f45c3370Sstephan
435453af2f6Sstephan  /**
43602d15a7eSstephan     A level of "organizational abstraction" for the Worker1
43702d15a7eSstephan     API. Each method in this object must map directly to a Worker1
438453af2f6Sstephan     message type key. The onmessage() dispatcher attempts to
439453af2f6Sstephan     dispatch all inbound messages to a method of this object,
440453af2f6Sstephan     passing it the event.data part of the inbound event object. All
4413734401aSstephan     methods must return a plain Object containing any result
442453af2f6Sstephan     state, which the dispatcher may amend. All methods must throw
443453af2f6Sstephan     on error.
444453af2f6Sstephan  */
445453af2f6Sstephan  const wMsgHandler = {
446453af2f6Sstephan    open: function(ev){
4479a34509aSstephan      const oargs = Object.create(null), args = (ev.args || Object.create(null));
448a9ac2ed0Sstephan      if(args.simulateError){ // undocumented internal testing option
449453af2f6Sstephan        toss("Throwing because of simulateError flag.");
450453af2f6Sstephan      }
4513734401aSstephan      const rc = Object.create(null);
4525b9973d8Sstephan      const pDir = sqlite3.capi.sqlite3_wasmfs_opfs_dir();
453f45c3370Sstephan      let byteArray, pVfs;
454f45c3370Sstephan      oargs.vfs = args.vfs;
455f45c3370Sstephan      if(isSpecialDbFilename(args.filename)){
456*4df2ab57Sstephan        oargs.filename = args.filename || "";
4579a34509aSstephan      }else{
4583734401aSstephan        oargs.filename = args.filename;
459f45c3370Sstephan        byteArray = args.byteArray;
460f45c3370Sstephan        if(byteArray) pVfs = guessVfs(args.filename);
461f45c3370Sstephan      }
462f45c3370Sstephan      if(pVfs){
463f45c3370Sstephan        /* 2022-11-02: this feature is as-yet untested except that
464f45c3370Sstephan           sqlite3_wasm_vfs_create_file() has been tested from the
465f45c3370Sstephan           browser dev console. */
466f45c3370Sstephan        let pMem;
467f45c3370Sstephan        try{
468f45c3370Sstephan          pMem = sqlite3.wasm.allocFromTypedArray(byteArray);
469f45c3370Sstephan          const rc = sqlite3.wasm.sqlite3_wasm_vfs_create_file(
470f45c3370Sstephan            pVfs, oargs.filename, pMem, byteArray.byteLength
471f45c3370Sstephan          );
472f45c3370Sstephan          if(rc) sqlite3.SQLite3Error.toss(rc);
473f45c3370Sstephan        }catch(e){
474f45c3370Sstephan          throw new sqlite3.SQLite3Error(
475f45c3370Sstephan            e.name+' creating '+args.filename+": "+e.message, {
476f45c3370Sstephan              cause: e
477f45c3370Sstephan            }
478f45c3370Sstephan          );
479f45c3370Sstephan        }finally{
480f45c3370Sstephan          if(pMem) sqlite3.wasm.dealloc(pMem);
481f45c3370Sstephan        }
4829a34509aSstephan      }
483a9ac2ed0Sstephan      const db = wState.open(oargs);
4843734401aSstephan      rc.filename = db.filename;
4858a8244b5Sstephan      rc.persistent = (!!pDir && db.filename.startsWith(pDir+'/'))
48602d15a7eSstephan        || !!sqlite3.capi.sqlite3_js_db_uses_vfs(db.pointer, "opfs");
4873734401aSstephan      rc.dbId = getDbId(db);
48849048b14Sstephan      rc.vfs = db.dbVfsName();
4893734401aSstephan      return rc;
490453af2f6Sstephan    },
491453af2f6Sstephan
492453af2f6Sstephan    close: function(ev){
493453af2f6Sstephan      const db = getMsgDb(ev,false);
494453af2f6Sstephan      const response = {
4959afff9f3Sstephan        filename: db && db.filename
496453af2f6Sstephan      };
497453af2f6Sstephan      if(db){
498f45c3370Sstephan        const doUnlink = ((ev.args && 'object'===typeof ev.args)
499f45c3370Sstephan                         ? !!ev.args.unlink : false);
500f45c3370Sstephan        wState.close(db, doUnlink);
501453af2f6Sstephan      }
502453af2f6Sstephan      return response;
503453af2f6Sstephan    },
504453af2f6Sstephan
505453af2f6Sstephan    exec: function(ev){
5063734401aSstephan      const rc = (
507a9ac2ed0Sstephan        'string'===typeof ev.args
508a9ac2ed0Sstephan      ) ? {sql: ev.args} : (ev.args || Object.create(null));
509407f7537Sstephan      if('stmt'===rc.rowMode){
5103734401aSstephan        toss("Invalid rowMode for 'exec': stmt mode",
511453af2f6Sstephan             "does not work in the Worker API.");
5129afff9f3Sstephan      }else if(!rc.sql){
5139afff9f3Sstephan        toss("'exec' requires input SQL.");
514453af2f6Sstephan      }
515453af2f6Sstephan      const db = getMsgDb(ev);
5163734401aSstephan      if(rc.callback || Array.isArray(rc.resultRows)){
517453af2f6Sstephan        // Part of a copy-avoidance optimization for blobs
5183734401aSstephan        db._blobXfer = wState.xfer;
519453af2f6Sstephan      }
520407f7537Sstephan      const theCallback = rc.callback;
5213734401aSstephan      let rowNumber = 0;
522407f7537Sstephan      const hadColNames = !!rc.columnNames;
523407f7537Sstephan      if('string' === typeof theCallback){
524407f7537Sstephan        if(!hadColNames) rc.columnNames = [];
525453af2f6Sstephan        /* Treat this as a worker message type and post each
526453af2f6Sstephan           row as a message of that type. */
527407f7537Sstephan        rc.callback = function(row,stmt){
528407f7537Sstephan          wState.post({
529407f7537Sstephan            type: theCallback,
530407f7537Sstephan            columnNames: rc.columnNames,
531407f7537Sstephan            rowNumber: ++rowNumber,
532407f7537Sstephan            row: row
533407f7537Sstephan          }, wState.xfer);
534407f7537Sstephan        }
535453af2f6Sstephan      }
536453af2f6Sstephan      try {
5373734401aSstephan        db.exec(rc);
5383734401aSstephan        if(rc.callback instanceof Function){
539407f7537Sstephan          rc.callback = theCallback;
540407f7537Sstephan          /* Post a sentinel message to tell the client that the end
541407f7537Sstephan             of the result set has been reached (possibly with zero
542407f7537Sstephan             rows). */
543407f7537Sstephan          wState.post({
544407f7537Sstephan            type: theCallback,
545407f7537Sstephan            columnNames: rc.columnNames,
546407f7537Sstephan            rowNumber: null /*null to distinguish from "property not set"*/,
547407f7537Sstephan            row: undefined /*undefined because null is a legal row value
548407f7537Sstephan                             for some rowType values, but undefined is not*/
549407f7537Sstephan          });
550453af2f6Sstephan        }
5513734401aSstephan      }finally{
552453af2f6Sstephan        delete db._blobXfer;
553407f7537Sstephan        if(rc.callback) rc.callback = theCallback;
554453af2f6Sstephan      }
5553734401aSstephan      return rc;
556453af2f6Sstephan    }/*exec()*/,
5573734401aSstephan
5583734401aSstephan    'config-get': function(){
5593734401aSstephan      const rc = Object.create(null), src = sqlite3.config;
5603734401aSstephan      [
5615b9973d8Sstephan        'wasmfsOpfsDir', 'bigIntEnabled'
5623734401aSstephan      ].forEach(function(k){
5633734401aSstephan        if(Object.getOwnPropertyDescriptor(src, k)) rc[k] = src[k];
5643734401aSstephan      });
5655b9973d8Sstephan      rc.wasmfsOpfsEnabled = !!sqlite3.capi.sqlite3_wasmfs_opfs_dir();
566d18f1bbfSstephan      rc.version = sqlite3.version;
5678a8244b5Sstephan      rc.vfsList = sqlite3.capi.sqlite3_js_vfs_list();
56849048b14Sstephan      rc.opfsEnabled = !!sqlite3.opfs;
5693734401aSstephan      return rc;
5703734401aSstephan    },
5719afff9f3Sstephan
5723734401aSstephan    /**
57302d15a7eSstephan       Exports the database to a byte array, as per
57402d15a7eSstephan       sqlite3_serialize(). Response is an object:
575453af2f6Sstephan
576453af2f6Sstephan       {
577f45c3370Sstephan         byteArray:  Uint8Array (db file contents),
578453af2f6Sstephan         filename: the current db filename,
579453af2f6Sstephan         mimetype: 'application/x-sqlite3'
580453af2f6Sstephan       }
581453af2f6Sstephan    */
582453af2f6Sstephan    export: function(ev){
58302d15a7eSstephan      const db = getMsgDb(ev);
584453af2f6Sstephan      const response = {
585f45c3370Sstephan        byteArray: sqlite3.capi.sqlite3_js_db_export(db.pointer),
586453af2f6Sstephan        filename: db.filename,
587453af2f6Sstephan        mimetype: 'application/x-sqlite3'
588453af2f6Sstephan      };
589f45c3370Sstephan      wState.xfer.push(response.byteArray.buffer);
59002d15a7eSstephan      return response;
591453af2f6Sstephan    }/*export()*/,
5929afff9f3Sstephan
593453af2f6Sstephan    toss: function(ev){
594453af2f6Sstephan      toss("Testing worker exception");
59549048b14Sstephan    },
59649048b14Sstephan
59749048b14Sstephan    'opfs-tree': async function(ev){
59849048b14Sstephan      if(!sqlite3.opfs) toss("OPFS support is unavailable.");
59949048b14Sstephan      const response = await sqlite3.opfs.treeList();
60049048b14Sstephan      return response;
601453af2f6Sstephan    }
602453af2f6Sstephan  }/*wMsgHandler*/;
603453af2f6Sstephan
60449048b14Sstephan  self.onmessage = async function(ev){
605453af2f6Sstephan    ev = ev.data;
606a9ac2ed0Sstephan    let result, dbId = ev.dbId, evType = ev.type;
607453af2f6Sstephan    const arrivalTime = performance.now();
608453af2f6Sstephan    try {
609453af2f6Sstephan      if(wMsgHandler.hasOwnProperty(evType) &&
610453af2f6Sstephan         wMsgHandler[evType] instanceof Function){
61149048b14Sstephan        result = await wMsgHandler[evType](ev);
612453af2f6Sstephan      }else{
613453af2f6Sstephan        toss("Unknown db worker message type:",ev.type);
614453af2f6Sstephan      }
615453af2f6Sstephan    }catch(err){
616453af2f6Sstephan      evType = 'error';
617a9ac2ed0Sstephan      result = {
6189a34509aSstephan        operation: ev.type,
619453af2f6Sstephan        message: err.message,
620453af2f6Sstephan        errorClass: err.name,
621453af2f6Sstephan        input: ev
622453af2f6Sstephan      };
623453af2f6Sstephan      if(err.stack){
624a9ac2ed0Sstephan        result.stack = ('string'===typeof err.stack)
625335ad526Sstephan          ? err.stack.split(/\n\s*/) : err.stack;
626453af2f6Sstephan      }
627453af2f6Sstephan      if(0) console.warn("Worker is propagating an exception to main thread.",
628a9ac2ed0Sstephan                         "Reporting it _here_ for the stack trace:",err,result);
629453af2f6Sstephan    }
630453af2f6Sstephan    if(!dbId){
631a9ac2ed0Sstephan      dbId = result.dbId/*from 'open' cmd*/
632453af2f6Sstephan        || getDefaultDbId();
633453af2f6Sstephan    }
634453af2f6Sstephan    // Timing info is primarily for use in testing this API. It's not part of
635453af2f6Sstephan    // the public API. arrivalTime = when the worker got the message.
636a9ac2ed0Sstephan    wState.post({
637a9ac2ed0Sstephan      type: evType,
638a9ac2ed0Sstephan      dbId: dbId,
639a9ac2ed0Sstephan      messageId: ev.messageId,
640a9ac2ed0Sstephan      workerReceivedTime: arrivalTime,
641a9ac2ed0Sstephan      workerRespondTime: performance.now(),
642a9ac2ed0Sstephan      departureTime: ev.departureTime,
6433734401aSstephan      // TODO: move the timing bits into...
6443734401aSstephan      //timing:{
6453734401aSstephan      //  departure: ev.departureTime,
6463734401aSstephan      //  workerReceived: arrivalTime,
6473734401aSstephan      //  workerResponse: performance.now();
6483734401aSstephan      //},
649a9ac2ed0Sstephan      result: result
6503734401aSstephan    }, wState.xfer);
651453af2f6Sstephan  };
6529a34509aSstephan  self.postMessage({type:'sqlite3-api',result:'worker1-ready'});
653e3cd6760Sstephan}.bind({self, sqlite3});
654e3cd6760Sstephan});
655