]> git.ipfire.org Git - thirdparty/sqlite.git/commitdiff
More work on how to configure the sqlite3 JS API bootstrapping process from higher...
authorstephan <stephan@noemail.net>
Wed, 24 Aug 2022 05:59:23 +0000 (05:59 +0000)
committerstephan <stephan@noemail.net>
Wed, 24 Aug 2022 05:59:23 +0000 (05:59 +0000)
FossilOrigin-Name: b030f321bd5a38cdd5d6f6735f201afa62d30d2b0ba02e67f055b4895553a878

ext/wasm/api/sqlite3-api-cleanup.js
ext/wasm/api/sqlite3-api-prologue.js
ext/wasm/api/sqlite3-api-worker1.js
ext/wasm/sqlite3-worker1-promiser.js [new file with mode: 0644]
ext/wasm/testing-worker-promise.js [deleted file]
ext/wasm/testing-worker1-promiser.html [moved from ext/wasm/testing-worker-promise.html with 91% similarity]
ext/wasm/testing-worker1-promiser.js [new file with mode: 0644]
manifest
manifest.uuid

index d989faccaf2b6a593b48d440c1601ed0e6807850..ed6b8c40eaca1c2dfd82d44f8471cff1b7f38d46 100644 (file)
 'use strict';
 if('undefined' !== typeof Module){ // presumably an Emscripten build
   /**
-     Replace sqlite3ApiBootstrap() with a variant which plugs in the
-     Emscripten-based config for all config options which the client
-     does not provide.
+     Install a suitable default configuration for sqlite3ApiBootstrap().
   */
-  const SAB = self.sqlite3ApiBootstrap;
-  self.sqlite3ApiBootstrap = function(apiConfig){
-    apiConfig = apiConfig || {};
-    const configDefaults = {
-      Module: Module /* ==> Emscripten-style Module object. Currently
-                        needs to be exposed here for test code. NOT part
-                        of the public API. */,
-      exports: Module['asm'],
-      memory: Module.wasmMemory /* gets set if built with -sIMPORT_MEMORY */
-    };
-    const config = {};
-    Object.keys(configDefaults).forEach(function(k){
-      config[k] = Object.getOwnPropertyDescriptor(apiConfig, k)
-        ? apiConfig[k] : configDefaults[k];
-    });
-    // Copy over any properties apiConfig defines but configDefaults does not...
-    Object.keys(apiConfig).forEach(function(k){
-      if(!Object.getOwnPropertyDescriptor(config, k)){
-        config[k] = apiConfig[k];
-      }
-    });
-    return SAB(config);
-  };
+  const SABC = self.sqlite3ApiBootstrap.defaultConfig;
+  SABC.Module = Module /* ==>  Current needs to be exposed here for test code. NOT part
+                          of the public API. */;
+  SABC.exports = Module['asm'];
+  SABC.memory = Module.wasmMemory /* gets set if built with -sIMPORT_MEMORY */;
 
   /**
      For current (2022-08-22) purposes, automatically call
      sqlite3ApiBootstrap().  That decision will be revisited at some
      point, as we really want client code to be able to call this to
-     configure certain parts. If the global sqliteApiConfig property
-     is available, it is assumed to be a config object for
-     sqlite3ApiBootstrap().
+     configure certain parts. Clients may modify
+     self.sqlite3ApiBootstrap.defaultConfig to tweak the default
+     configuration used by a no-args call to sqlite3ApiBootstrap().
   */
   //console.warn("self.sqlite3ApiConfig = ",self.sqlite3ApiConfig);
-  const sqlite3 = self.sqlite3ApiBootstrap(self.sqlite3ApiConfig || Object.create(null));
+  const sqlite3 = self.sqlite3ApiBootstrap();
   delete self.sqlite3ApiBootstrap;
 
   if(self.location && +self.location.port > 1024){
@@ -67,4 +47,9 @@ if('undefined' !== typeof Module){ // presumably an Emscripten build
   delete sqlite3.capi.util /* arguable, but these are (currently) internal-use APIs */;
   //console.warn("Module.sqlite3 =",Module.sqlite3);
   Module.sqlite3 = sqlite3 /* Currently needed by test code and sqlite3-worker1.js */;
+}else{
+  console.warn("This is not running in an Emscripten module context, so",
+               "self.sqlite3ApiBootstrap() is _not_ being called due to lack",
+               "of config info for the WASM environment.",
+               "It must be called manually.");
 }
index 1a38f0343a4c46498fc369426bd5f3edb1ffb5bd..d92b948fce68c83e012049eae79a0d966bb8b6b5 100644 (file)
 
    The config object properties include:
 
-   - `Module`: Emscripten-style module object. Currently only required
+   - `Module`[^1]: Emscripten-style module object. Currently only required
      by certain test code and is _not_ part of the public interface.
      (TODO: rename this to EmscriptenModule to be more explicit.)
 
-   - `exports`: the "exports" object for the current WASM
+   - `exports`[^1]: the "exports" object for the current WASM
      environment. In an Emscripten build, this should be set to
      `Module['asm']`.
 
-   - `memory`: optional WebAssembly.Memory object, defaulting to
-     `exports.memory`.  In Emscripten environments this should be set
+   - `memory`[^1]: optional WebAssembly.Memory object, defaulting to
+     `exports.memory`. In Emscripten environments this should be set
      to `Module.wasmMemory` if the build uses `-sIMPORT_MEMORY`, or be
      left undefined/falsy to default to `exports.memory` when using
      WASM-exported memory.
      the `free(3)`-compatible routine for the WASM
      environment. Defaults to `"free"`.
 
-   - `persistentDirName`: if the environment supports persistent storage, this
+   - `persistentDirName`[^1]: if the environment supports persistent storage, this
      directory names the "mount point" for that directory. It must be prefixed
      by `/` and may currently contain only a single directory-name part. Using
      the root directory name is not supported by any current persistent backend.
+
+
+   [^1] = This property may optionally be a function, in which case this
+          function re-assigns it to the value returned from that function,
+          enabling delayed evaluation.
+
 */
-self.sqlite3ApiBootstrap = function sqlite3ApiBootstrap(apiConfig){
-  'use strict';
-  
+'use strict';
+self.sqlite3ApiBootstrap = function sqlite3ApiBootstrap(
+  apiConfig = (sqlite3ApiBootstrap.defaultConfig || self.sqlite3ApiConfig)
+){
   if(sqlite3ApiBootstrap.sqlite3){ /* already initalized */
     console.warn("sqlite3ApiBootstrap() called multiple times.",
                  "Config and external initializers are ignored on calls after the first.");
     return sqlite3ApiBootstrap.sqlite3;
   }
-
   apiConfig = apiConfig || {};
   const config = Object.create(null);
   {
@@ -158,6 +164,16 @@ self.sqlite3ApiBootstrap = function sqlite3ApiBootstrap(apiConfig){
     });
   }
 
+  [
+    // If any of these config options are functions, replace them with
+    // the result of calling that function...
+    'Module', 'exports', 'memory', 'persistentDirName'
+  ].forEach((k)=>{
+    if('function' === typeof config[k]){
+      config[k] = config[k]();
+    }
+  });
+  
   /** Throws a new Error, the message of which is the concatenation
       all args with a space between each. */
   const toss = (...args)=>{throw new Error(args.join(' '))};
@@ -750,4 +766,16 @@ self.sqlite3ApiBootstrap = function sqlite3ApiBootstrap(apiConfig){
   this array is deleted.
 */
 self.sqlite3ApiBootstrap.initializers = [];
-self.sqlite3ApiBootstrap.sqlite3 = undefined /* installed at first call */;
+/**
+   Client code may assign sqlite3ApiBootstrap.defaultConfig an
+   object-type value before calling sqlite3ApiBootstrap() (without
+   arguments) in order to tell that call to use this object as its
+   default config value. The intention of this is to provide
+   downstream clients with a reasonably flexible approach for plugging in
+   an environment-suitable configuration without having to define a new
+   global-scope symbol.
+*/
+self.sqlite3ApiBootstrap.defaultConfig = Object.create(null);
+/** Placeholder: gets installed by the first call to
+    self.sqlite3ApiBootstrap(). */
+self.sqlite3ApiBootstrap.sqlite3 = undefined;
index 2e8d5a25d043649df2e0ea71c9408405b73367f4..39263a4abea2387764fd5e5c1a68f4b50a6df810 100644 (file)
@@ -92,11 +92,8 @@ sqlite3.initWorker1API = function(){
     defaultDb: undefined,
     idSeq: 0,
     idMap: new WeakMap,
-    open: function(arg){
-      // TODO? if arg is a filename, look for a db in this.dbs with the
-      // same filename and close/reopen it (or just pass it back as is?).
-      if(!arg && this.defaultDb) return this.defaultDb;
-      const db = (Array.isArray(arg) ? new DB(...arg) : new DB(arg));
+    open: function(opt){
+      const db = new DB(opt.filename);
       this.dbs[getDbId(db)] = db;
       if(!this.defaultDb) this.defaultDb = db;
       return db;
@@ -169,14 +166,26 @@ sqlite3.initWorker1API = function(){
                envelope to other calls in this API to tell them which
                db to use. If it is not provided to future calls, they
                will default to operating on the first-opened db.
+
+          persistent: prepend sqlite3.capi.sqlite3_web_persistent_dir()
+                      to the given filename so that it is stored
+                      in persistent storage _if_ the environment supports it.
+                      If persistent storage is not supported, the filename
+                      is used as-is.
        }
     */
     open: function(ev){
-      const oargs = [], args = (ev.args || {});
+      const oargs = Object.create(null), args = (ev.args || Object.create(null));
       if(args.simulateError){ // undocumented internal testing option
         toss("Throwing because of simulateError flag.");
       }
-      if(args.filename) oargs.push(args.filename);
+      if(args.persistent && args.filename){
+        oargs.filaname = sqlite3.capi.sqlite3_web_persistent_dir() + args.filename;
+      }else if('' === args.filename){
+        oargs.filename = args.filename;
+      }else{
+        oargs.filename = args.filename || ':memory:';
+      }      
       const db = wState.open(oargs);
       return {
         filename: db.filename,
@@ -184,15 +193,15 @@ sqlite3.initWorker1API = function(){
       };
     },
     /**
-       Proxy for DB.close(). ev.args may either be a boolean or an
-       object with an `unlink` property. If that value is truthy then
-       the db file (if the db is currently open) will be unlinked from
-       the virtual filesystem, else it will be kept intact. The
-       result object is:
+       Proxy for DB.close(). ev.args may be elided or an object with
+       an `unlink` property. If that value is truthy then the db file
+       (if the db is currently open) will be unlinked from the virtual
+       filesystem, else it will be kept intact. The result object is:
 
        {
          filename: db filename _if_ the db is opened when this
                    is called, else the undefined value
+         dbId: the ID of the closed b, or undefined if none is closed
        }
 
        It does not error if the given db is already closed or no db is
@@ -356,6 +365,7 @@ sqlite3.initWorker1API = function(){
        dbId: DB handle ID,
        [messageId: if set in the inbound message],
        result: {
+         operation: "inbound message's 'type' value",
          message: error string,
          errorClass: class name of the error type,
          input: ev.data
@@ -378,6 +388,7 @@ sqlite3.initWorker1API = function(){
     }catch(err){
       evType = 'error';
       result = {
+        operation: ev.type,
         message: err.message,
         errorClass: err.name,
         input: ev
@@ -405,7 +416,7 @@ sqlite3.initWorker1API = function(){
       result: result
     }, wMsgHandler.xfer);
   };
-  setTimeout(()=>self.postMessage({type:'sqlite3-api',result:'worker1-ready'}), 0);
+  self.postMessage({type:'sqlite3-api',result:'worker1-ready'});
 }.bind({self, sqlite3});
 });
 
diff --git a/ext/wasm/sqlite3-worker1-promiser.js b/ext/wasm/sqlite3-worker1-promiser.js
new file mode 100644 (file)
index 0000000..c01ed9a
--- /dev/null
@@ -0,0 +1,232 @@
+/*
+  2022-08-24
+
+  The author disclaims copyright to this source code.  In place of a
+  legal notice, here is a blessing:
+
+  *   May you do good and not evil.
+  *   May you find forgiveness for yourself and forgive others.
+  *   May you share freely, never taking more than you give.
+
+  ***********************************************************************
+
+  This file implements a Promise-based proxy for the sqlite3 Worker
+  API #1. It is intended to be included either from the main thread or
+  a Worker, but only if (A) the environment supports nested Workers
+  and (B) it's _not_ a Worker which loads the sqlite3 WASM/JS
+  module. This file's features will load that module and provide a
+  slightly simpler client-side interface than the slightly-lower-level
+  Worker API does.
+
+  This script necessarily exposes on global symbol, but clients may
+  freely `delete` that symbol after calling it.
+*/
+'use strict';
+/**
+   Configures an sqlite3 Worker API #1 Worker such that it can be
+   manipulated via a Promise-based interface and returns a factory
+   function which returns Promises for communicating with the worker.
+   This proxy has an _almost_ identical interface to the normal
+   worker API, with any exceptions noted below.
+
+   It requires a configuration object with the following properties:
+
+   - `worker` (required): a Worker instance which loads
+   `sqlite3-worker1.js` or a functional equivalent. Note that this
+   function replaces the worker.onmessage property. This property
+   may alternately be a function, in which case this function
+   re-assigns this property with the result of calling that
+   function, enabling delayed instantiation of a Worker.
+
+   - `onready` (optional, but...): this callback is called with no
+   arguments when the worker fires its initial
+   'sqlite3-api'/'worker1-ready' message, which it does when
+   sqlite3.initWorker1API() completes its initialization. This is
+   the simplest way to tell the worker to kick of work at the
+   earliest opportunity.
+
+   - `onerror` (optional): a callback to pass error-type events from
+   the worker. The object passed to it will be the error message
+   payload from the worker. This is _not_ the same as the
+   worker.onerror property!
+
+   - `onunhandled` (optional): a callback which gets passed the
+   message event object for any worker.onmessage() events which
+   are not handled by this proxy. Ideally that "should" never
+   happen, as this proxy aims to handle all known message types.
+
+   - `generateMessageId` (optional): a function which, when passed
+   an about-to-be-posted message object, generates a _unique_
+   message ID for the message, which this API then assigns as the
+   messageId property of the message. It _must_ generate unique
+   IDs so that dispatching can work. If not defined, a default
+   generator is used.
+
+   - `dbId` (optional): is the database ID to be used by the
+   worker. This must initially be unset or a falsy value. The
+   first `open` message sent to the worker will cause this config
+   entry to be assigned to the ID of the opened database. That ID
+   "should" be set as the `dbId` property of the message sent in
+   future requests, so that the worker uses that database.
+   However, if the worker is not given an explicit dbId, it will
+   use the first-opened database by default. If client code needs
+   to work with multiple database IDs, the client-level code will
+   need to juggle those themselves. A `close` message will clear
+   this property if it matches the ID of the closed db. Potential
+   TODO: add a config callback specifically for reporting `open`
+   and `close` message results, so that clients may track those
+   values.
+
+   - `debug` (optional): a console.debug()-style function for logging
+   information about messages.
+
+
+   This function returns a stateful factory function with the following
+   interfaces:
+
+   - Promise function(messageType, messageArgs)
+   - Promise function({message object})
+
+   The first form expects the "type" and "args" values for a Worker
+   message. The second expects an object in the form {type:...,
+   args:...}  plus any other properties the client cares to set. This
+   function will always set the messageId property on the object,
+   even if it's already set, and will set the dbId property to
+   config.dbId if it is _not_ set in the message object.
+
+   The function throws on error.
+
+   The function installs a temporarily message listener, posts a
+   message to the configured Worker, and handles the message's
+   response via the temporary message listener. The then() callback
+   of the returned Promise is passed the `message.data` property from
+   the resulting message, i.e. the payload from the worker, stripped
+   of the lower-level event state which the onmessage() handler
+   receives.
+
+   Example usage:
+
+   ```
+   const config = {...};
+   const eventPromiser = sqlite3Worker1Promiser(config);
+   eventPromiser('open', {filename:"/foo.db"}).then(function(msg){
+     console.log("open response",msg); // => {type:'open', result: {filename:'/foo.db'}, ...}
+     // Recall that config.dbId will be set for the first 'open'
+     // call and cleared for a matching 'close' call.
+   });
+   eventPromiser({type:'close'}).then((msg)=>{
+     console.log("open response",msg); // => {type:'open', result: {filename:'/foo.db'}, ...}
+     // Recall that config.dbId will be used by default for the message's dbId if
+     // none is explicitly provided, and a 'close' op will clear config.dbId if it
+     // closes that exact db.
+   });
+   ```
+
+   Differences from Worker API #1:
+
+   - exec's {callback: STRING} option does not work via this
+   interface (it triggers an exception), but {callback: function}
+   does and works exactly like the STRING form does in the Worker:
+   the callback is called one time for each row of the result set
+   and once more, at the end, passed only `null`, to indicate that
+   the end of the result set has been reached. Note that the rows
+   arrive via worker-posted messages, with all the implications
+   of that.
+
+
+   TODO?: a config option which causes it to queue up events to fire
+   one at a time and flush the event queue on the first error. The
+   main use for this is test runs which must fail at the first error.
+*/
+self.sqlite3Worker1Promiser = function callee(config = callee.defaultConfig){
+  // Inspired by: https://stackoverflow.com/a/52439530
+  let idNumber = 0;
+  const handlerMap = Object.create(null);
+  const noop = function(){};
+  const err = config.onerror || noop;
+  const debug = config.debug || noop;
+  const genMsgId = config.generateMessageId || function(msg){
+    return msg.type+'#'+(++idNumber);
+  };
+  const toss = (...args)=>{throw new Error(args.join(' '))};
+  if('function'===typeof config.worker) config.worker = config.worker();
+  config.worker.onmessage = function(ev){
+    ev = ev.data;
+    debug('worker1.onmessage',ev);
+    let msgHandler = handlerMap[ev.messageId];
+    if(!msgHandler){
+      if(ev && 'sqlite3-api'===ev.type && 'worker1-ready'===ev.result) {
+        /*fired one time when the Worker1 API initializes*/
+        if(config.onready) config.onready();
+        return;
+      }
+      msgHandler = handlerMap[ev.type] /* check for exec per-row callback */;
+      if(msgHandler && msgHandler.onrow){
+        msgHandler.onrow(ev.row);
+        return;
+      }        
+      if(config.onunhandled) config.onunhandled(arguments[0]);
+      else err("sqlite3Worker1Promiser() unhandled worker message:",ev);
+      return;
+    }
+    delete handlerMap[ev.messageId];
+    switch(ev.type){
+        case 'error':
+          msgHandler.reject(ev);
+          return;
+        case 'open':
+          if(!config.dbId) config.dbId = ev.dbId;
+          break;
+        case 'close':
+          if(config.dbId === ev.dbId) config.dbId = undefined;
+          break;
+        default:
+          break;
+    }
+    msgHandler.resolve(ev);
+  }/*worker.onmessage()*/;
+  return function(/*(msgType, msgArgs) || (msg)*/){
+    let msg;
+    if(1===arguments.length){
+      msg = arguments[0];
+    }else if(2===arguments.length){
+      msg = {
+        type: arguments[0],
+        args: arguments[1]
+      };
+    }else{
+      toss("Invalid arugments for sqlite3Worker1Promiser()-created factory.");
+    }
+    if(!msg.dbId) msg.dbId = config.dbId;
+    msg.messageId = genMsgId(msg);
+    msg.departureTime = performance.now();
+    const proxy = Object.create(null);
+    proxy.message = msg;
+    let cbId /* message handler ID for exec on-row callback proxy */;
+    if('exec'===msg.type && msg.args){
+      if('function'===typeof msg.args.callback){
+        cbId = genMsgId(msg)+':row';
+        proxy.onrow = msg.args.callback;
+        msg.args.callback = cbId;
+        handlerMap[cbId] = proxy;
+      }else if('string' === typeof msg.args.callback){
+        toss("exec callback may not be a string when using the Promise interface.");
+      }
+    }
+    //debug("requestWork", msg);
+    const p = new Promise(function(resolve, reject){
+      proxy.resolve = resolve;
+      proxy.reject = reject;
+      handlerMap[msg.messageId] = proxy;
+      debug("Posting",msg.type,"message to Worker dbId="+(config.dbId||'default')+':',msg);
+      config.worker.postMessage(msg);
+    });
+    if(cbId) p.finally(()=>delete handlerMap[cbId]);
+    return p;
+  };
+}/*sqlite3Worker1Promiser()*/;
+self.sqlite3Worker1Promiser.defaultConfig = {
+  worker: ()=>new Worker('sqlite3-worker1.js'),
+  onerror: console.error.bind(console),
+  dbId: undefined
+};
diff --git a/ext/wasm/testing-worker-promise.js b/ext/wasm/testing-worker-promise.js
deleted file mode 100644 (file)
index c60c6c2..0000000
+++ /dev/null
@@ -1,139 +0,0 @@
-/*
-  2022-08-23
-
-  The author disclaims copyright to this source code.  In place of a
-  legal notice, here is a blessing:
-
-  *   May you do good and not evil.
-  *   May you find forgiveness for yourself and forgive others.
-  *   May you share freely, never taking more than you give.
-
-  ***********************************************************************
-
-  
-  UNDER CONSTRUCTION: a Promise-based proxy for for the sqlite3 Worker
-  #1 API.
-*/
-'use strict';
-(function(){
-  const T = self.SqliteTestUtil;
-  const DbState = {
-    id: undefined
-  };
-  const eOutput = document.querySelector('#test-output');
-  const log = console.log.bind(console);
-  const logHtml = async function(cssClass,...args){
-    log.apply(this, args);
-    const ln = document.createElement('div');
-    if(cssClass) ln.classList.add(cssClass);
-    ln.append(document.createTextNode(args.join(' ')));
-    eOutput.append(ln);
-  };
-  const warn = console.warn.bind(console);
-  const error = console.error.bind(console);
-
-  let startTime;
-  const logEventResult = async function(evd){
-    logHtml(evd.errorClass ? 'error' : '',
-            "response to",evd.messageId,"Worker time =",
-            (evd.workerRespondTime - evd.workerReceivedTime),"ms.",
-            "Round-trip event time =",
-            (performance.now() - evd.departureTime),"ms.",
-            (evd.errorClass ? evd.message : "")
-           );
-  };
-
-  const testCount = async ()=>{
-    logHtml("","Total test count:",T.counter+". Total time =",(performance.now() - startTime),"ms");
-  };
-
-  // Inspiration: https://stackoverflow.com/a/52439530
-  const worker = new Worker("sqlite3-worker1.js");
-  worker.onerror = function(event){
-    error("worker.onerror",event);
-  };
-  const WorkerPromiseHandler = Object.create(null);
-  WorkerPromiseHandler.nextId = function f(){
-    return 'msg#'+(f._ = (f._ || 0) + 1);
-  };
-
-  /** Posts a worker message as {type:eventType, data:eventData}. */
-  const requestWork = async function(eventType, eventData){
-    //log("requestWork", eventType, eventData);
-    T.assert(eventData && 'object'===typeof eventData);
-    /* ^^^ that is for the testing and messageId-related code, not
-       a hard requirement of all of the Worker-exposed APIs. */
-    const wph = WorkerPromiseHandler;
-    const msgId = wph.nextId();
-    const proxy = wph[msgId] = Object.create(null);
-    proxy.promise = new Promise(function(resolve, reject){
-      proxy.resolve = resolve;
-      proxy.reject = reject;
-      const msg = {
-        type: eventType,
-        args: eventData,
-        dbId: DbState.id,
-        messageId: msgId,
-        departureTime: performance.now()
-      };
-      log("Posting",eventType,"message to worker dbId="+(DbState.id||'default')+':',msg);
-      worker.postMessage(msg);
-    });
-    log("Set up promise",proxy);
-    return proxy.promise;
-  };
-
-
-  const runOneTest = async function(eventType, eventData, callback){
-    T.assert(eventData && 'object'===typeof eventData);
-    /* ^^^ that is for the testing and messageId-related code, not
-       a hard requirement of all of the Worker-exposed APIs. */
-    let p = requestWork(eventType, eventData);
-    if(callback) p.then(callback).finally(testCount);
-    return p;
-  };
-
-  const runTests = async function(){
-    logHtml('',
-            "Sending 'open' message and waiting for its response before continuing.");
-    startTime = performance.now();
-    runOneTest('open', {
-      filename:'testing2.sqlite3',
-      simulateError: 0 /* if true, fail the 'open' */
-    }, function(ev){
-      log("then open result",ev);
-      T.assert('testing2.sqlite3'===ev.result.filename)
-        .assert(ev.dbId)
-        .assert(ev.messageId)
-        .assert(DbState.id === ev.dbId);
-    }).catch((err)=>error("error response:",err));
-  };
-
-  worker.onmessage = function(ev){
-    ev = ev.data;
-    (('error'===ev.type) ? error : log)('worker.onmessage',ev);
-    const msgHandler = WorkerPromiseHandler[ev.messageId];
-    if(!msgHandler){
-      if('worker1-ready'===ev.result) {
-        /*sqlite3-api/worker1-ready is fired when the Worker1 API initializes*/
-        self.sqlite3TestModule.setStatus(null)/*hide the HTML-side is-loading spinner*/;
-        runTests();
-        return;
-      }
-      error("Unhandled worker message:",ev);
-      return;
-    }
-    logEventResult(ev);
-    delete WorkerPromiseHandler[ev.messageId];
-    if('error'===ev.type){
-      msgHandler.reject(ev);
-    }
-    else{
-      if(!DbState.id && ev.dbId) DbState.id = ev.dbId;
-      msgHandler.resolve(ev); // async, so testCount() results on next line are out of order
-      //testCount();
-    }
-  };
-  
-  log("Init complete, but async init bits may still be running.");
-})();
similarity index 91%
rename from ext/wasm/testing-worker-promise.html
rename to ext/wasm/testing-worker1-promiser.html
index eab30c301fecfceee2e555871170b879e54f4513..9af809d9ed583519c400cbc4f5e518ecd52162bb 100644 (file)
@@ -28,6 +28,7 @@
     <hr>
     <div id='test-output'></div>
     <script src="common/SqliteTestUtil.js"></script>
-    <script src="testing-worker-promise.js"></script>
+    <script src="sqlite3-worker1-promiser.js"></script>
+    <script src="testing-worker1-promiser.js"></script>
   </body>
 </html>
diff --git a/ext/wasm/testing-worker1-promiser.js b/ext/wasm/testing-worker1-promiser.js
new file mode 100644 (file)
index 0000000..9475df4
--- /dev/null
@@ -0,0 +1,214 @@
+/*
+  2022-08-23
+
+  The author disclaims copyright to this source code.  In place of a
+  legal notice, here is a blessing:
+
+  *   May you do good and not evil.
+  *   May you find forgiveness for yourself and forgive others.
+  *   May you share freely, never taking more than you give.
+
+  ***********************************************************************
+  
+  Demonstration of the sqlite3 Worker API #1 Promiser: a Promise-based
+  proxy for for the sqlite3 Worker #1 API.
+*/
+'use strict';
+(function(){
+  const T = self.SqliteTestUtil;
+  const eOutput = document.querySelector('#test-output');
+  const warn = console.warn.bind(console);
+  const error = console.error.bind(console);
+  const log = console.log.bind(console);
+  const logHtml = async function(cssClass,...args){
+    log.apply(this, args);
+    const ln = document.createElement('div');
+    if(cssClass) ln.classList.add(cssClass);
+    ln.append(document.createTextNode(args.join(' ')));
+    eOutput.append(ln);
+  };
+
+  let startTime;
+  const logEventResult = async function(evd){
+    logHtml(evd.errorClass ? 'error' : '',
+            "response to",evd.messageId,"Worker time =",
+            (evd.workerRespondTime - evd.workerReceivedTime),"ms.",
+            "Round-trip event time =",
+            (performance.now() - evd.departureTime),"ms.",
+            (evd.errorClass ? evd.message : "")
+           );
+  };
+
+  const testCount = async ()=>{
+    logHtml("","Total test count:",T.counter+". Total time =",(performance.now() - startTime),"ms");
+  };
+
+  //why is this triggered even when we catch() a Promise?
+  //window.addEventListener('unhandledrejection', function(event) {
+  //  warn('unhandledrejection',event);
+  //});
+
+  const promiserConfig = {
+    worker: ()=>{
+      const w = new Worker("sqlite3-worker1.js");
+      w.onerror = (event)=>error("worker.onerror",event);
+      return w;
+    },
+    //debug: (...args)=>console.debug('worker debug',...args),
+    onunhandled: function(ev){
+      error("Unhandled worker message:",ev.data);
+    },
+    onready: function(){
+      self.sqlite3TestModule.setStatus(null)/*hide the HTML-side is-loading spinner*/;
+      runTests();
+    },
+    onerror: function(ev){
+      error("worker1 error:",ev);
+    }
+  };
+  const workerPromise = self.sqlite3Worker1Promiser(promiserConfig);
+  delete self.sqlite3Worker1Promiser;
+
+  const wtest = async function(msgType, msgArgs, callback){
+    let p = workerPromise({type: msgType, args:msgArgs});
+    if(callback) p.then(callback).finally(testCount);
+    return p;
+  };
+
+  const runTests = async function(){
+    logHtml('',
+            "Sending 'open' message and waiting for its response before continuing.");
+    startTime = performance.now();
+    wtest('open', {
+      filename:'testing2.sqlite3',
+      simulateError: 0 /* if true, fail the 'open' */
+    }, function(ev){
+      log("then open result",ev);
+      T.assert('testing2.sqlite3'===ev.result.filename)
+        .assert(ev.dbId)
+        .assert(ev.messageId)
+        .assert(promiserConfig.dbId === ev.dbId);
+    }).then(runTests2)
+      .catch((err)=>error("error response:",err));
+  };
+
+  const runTests2 = async function(){
+    const mustNotReach = ()=>toss("This is not supposed to be reached.");
+
+    await wtest('exec',{
+      sql: ["create table t(a,b)",
+            "insert into t(a,b) values(1,2),(3,4),(5,6)"
+           ].join(';'),
+      multi: true,
+      resultRows: [], columnNames: []
+    }, function(ev){
+      ev = ev.result;
+      T.assert(0===ev.resultRows.length)
+        .assert(0===ev.columnNames.length);
+    });
+
+    await wtest('exec',{
+      sql: 'select a a, b b from t order by a',
+      resultRows: [], columnNames: [],
+    }, function(ev){
+      ev = ev.result;
+      T.assert(3===ev.resultRows.length)
+        .assert(1===ev.resultRows[0][0])
+        .assert(6===ev.resultRows[2][1])
+        .assert(2===ev.columnNames.length)
+        .assert('b'===ev.columnNames[1]);
+    });
+
+    await wtest('exec',{
+      sql: 'select a a, b b from t order by a',
+      resultRows: [], columnNames: [],
+      rowMode: 'object'
+    }, function(ev){
+      ev = ev.result;
+      T.assert(3===ev.resultRows.length)
+        .assert(1===ev.resultRows[0].a)
+        .assert(6===ev.resultRows[2].b)
+    });
+
+    await wtest(
+      'exec',
+      {sql:'intentional_error'},
+      mustNotReach
+    ).catch((e)=>{
+      warn("Intentional error:",e);
+      // Why does the browser report console.error "Uncaught (in
+      // promise)" when we catch(), and does so _twice_ if we don't
+      // catch()? According to all docs, that error must be supressed
+      // if we explicitly catch().
+    });
+
+    await wtest('exec',{
+      sql:'select 1 union all select 3',
+      resultRows: [],
+      //rowMode: 'array', // array is the default in the Worker interface
+    }, function(ev){
+      ev = ev.result;
+      T.assert(2 === ev.resultRows.length)
+        .assert(1 === ev.resultRows[0][0])
+        .assert(3 === ev.resultRows[1][0]);
+    });
+
+    const resultRowTest1 = function f(row){
+      if(undefined === f.counter) f.counter = 0;
+      if(row) ++f.counter;
+      //log("exec() result row:",row);
+      T.assert(null===row || 'number' === typeof row.b);
+    };
+    await wtest('exec',{
+      sql: 'select a a, b b from t order by a',
+      callback: resultRowTest1,
+      rowMode: 'object'
+    }, function(ev){
+      T.assert(3===resultRowTest1.counter);
+      resultRowTest1.counter = 0;
+    });
+
+    await wtest('exec',{
+      multi: true,
+      sql:[
+        'pragma foreign_keys=0;',
+        // ^^^ arbitrary query with no result columns
+        'select a, b from t order by a desc; select a from t;'
+        // multi-exec only honors results from the first
+        // statement with result columns (regardless of whether)
+        // it has any rows).
+      ],
+      rowMode: 1,
+      resultRows: []
+    },function(ev){
+      const rows = ev.result.resultRows;
+      T.assert(3===rows.length).
+        assert(6===rows[0]);
+    });
+
+    await wtest('exec',{sql: 'delete from t where a>3'});
+
+    await wtest('exec',{
+      sql: 'select count(a) from t',
+      resultRows: []
+    },function(ev){
+      ev = ev.result;
+      T.assert(1===ev.resultRows.length)
+        .assert(2===ev.resultRows[0][0]);
+    });
+
+    /***** close() tests must come last. *****/
+    await wtest('close',{unlink:true},function(ev){
+      T.assert(!promiserConfig.dbId);
+      T.assert('string' === typeof ev.result.filename);
+    });
+
+    await wtest('close').then((ev)=>{
+      T.assert(undefined === ev.result.filename);
+      log("That's all, folks!");
+    });
+  }/*runTests2()*/;
+
+  
+  log("Init complete, but async init bits may still be running.");
+})();
index d26d33dea697d21ae13c81722cd81367df981fba..05095d6b811433016c1c8f22b78cbf82cbe71d55 100644 (file)
--- a/manifest
+++ b/manifest
@@ -1,5 +1,5 @@
-C The\svery\sbasics\sof\sa\sPromise-based\sproxy\sfor\sthe\sWorker\s#1\sAPI.\sStill\srequires\sconsiderable\scleanup,\stesting,\sand\sa\ssolution\sfor\sthe\sexec-callback-via-event-type-name\sproblem.
-D 2022-08-24T00:51:39.887
+C More\swork\son\show\sto\sconfigure\sthe\ssqlite3\sJS\sAPI\sbootstrapping\sprocess\sfrom\shigher-level\scode.\sInitial\sversion\sof\ssqlite3-worker1-promiser,\sa\sPromise-based\sproxy\sfor\sthe\sWorker\sAPI\s#1.
+D 2022-08-24T05:59:23.851
 F .fossil-settings/empty-dirs dbb81e8fc0401ac46a1491ab34a7f2c7c0452f2f06b54ebb845d024ca8283ef1
 F .fossil-settings/ignore-glob 35175cdfcf539b2318cb04a9901442804be81cd677d8b889fcc9149c21f239ea
 F LICENSE.md df5091916dbb40e6e9686186587125e1b2ff51f022cc334e886c19a0e9982724
@@ -481,12 +481,12 @@ F ext/wasm/api/EXPORTED_RUNTIME_METHODS.sqlite3-api 1ec3c73e7d66e95529c3c64ac3de
 F ext/wasm/api/README.md d876597edd2b9542b6ea031adaaff1c042076fde7b670b1dc6d8a87b28a6631b
 F ext/wasm/api/post-js-footer.js b64319261d920211b8700004d08b956a6c285f3b0bba81456260a713ed04900c
 F ext/wasm/api/post-js-header.js 0e853b78db83cb1c06b01663549e0e8b4f377f12f5a2d9a4a06cb776c003880b
-F ext/wasm/api/sqlite3-api-cleanup.js acf798ce96285c0d52738466a96c9deb9d66647f711a40caecab90b5ce66ac3c
+F ext/wasm/api/sqlite3-api-cleanup.js 4c353bdc2452623f0c1c1e55ae1a0589db9cbaed9756760bb15179ef9b58bc98
 F ext/wasm/api/sqlite3-api-glue.js 67ca83974410961953eeaa1dfed3518530d68381729ed1d27f95122f5baeabd3
 F ext/wasm/api/sqlite3-api-oo1.js f6dcaac3270182471f97efcfda25bd4a4ac1777b8ec52ebd1c6846721160e54c
 F ext/wasm/api/sqlite3-api-opfs.js 011799db398157cbd254264b6ebae00d7234b93d0e9e810345f213a5774993c0
-F ext/wasm/api/sqlite3-api-prologue.js 6e0e7787ed955ea2b6158e0bb7608f63b54236847700d183e49e1f10d0525b8f
-F ext/wasm/api/sqlite3-api-worker1.js c9e4edb89f41a4fa65d136ae180c1bc0beb694eb95f7d9e6936fbb702914c160
+F ext/wasm/api/sqlite3-api-prologue.js 4a279604272851696975837534739597206c0800c8ea78810fe8e211ee101374
+F ext/wasm/api/sqlite3-api-worker1.js 9691e144a77490f482caa2c0f0bd38a8f955c6dc9c10b2f39c6491e817aefd8c
 F ext/wasm/api/sqlite3-wasi.h 25356084cfe0d40458a902afb465df8c21fc4152c1d0a59b563a3fba59a068f9
 F ext/wasm/api/sqlite3-wasm.c 0d81282eaeff2a6e9fc5c28a388c5c5b45cf25a9393992fa511ac009b27df982
 F ext/wasm/common/SqliteTestUtil.js eb96275bed43fdb364b7d65bcded0ca5e22aaacff120d593d1385f852f486247
@@ -508,9 +508,10 @@ F ext/wasm/scratchpad-opfs-main.js 69e960e9161f6412fd0c30f355d4112f1894d6609eb43
 F ext/wasm/scratchpad-opfs-worker.html 66c1d15d678f3bd306373d76b61c6c8aef988f61f4a8dd40185d452f9c6d2bf5
 F ext/wasm/scratchpad-opfs-worker.js 3ec2868c669713145c76eb5877c64a1b20741f741817b87c907a154b676283a9
 F ext/wasm/scratchpad-opfs-worker2.js 5f2237427ac537b8580b1c659ff14ad2621d1694043eaaf41ae18dbfef2e48c0
+F ext/wasm/sqlite3-worker1-promiser.js 291f89330bc856e7ef8a321b4891554633c6407b52efc69c9b1d1b3e7c69d4a6
 F ext/wasm/sqlite3-worker1.js 0c1e7626304543969c3846573e080c082bf43bcaa47e87d416458af84f340a9e
-F ext/wasm/testing-worker-promise.html ba3d5423cfbdc96c332af3632dfcb61527ba8fd7e487b3bf3f07542f890c3e08
-F ext/wasm/testing-worker-promise.js c05c46a3a22b1910f6a1db11f3da6df701259eaa1277ddba085247b7f9059423
+F ext/wasm/testing-worker1-promiser.html 6eaec6e04a56cf24cf4fa8ef49d78ce8905dde1354235c9125dca6885f7ce893 w ext/wasm/testing-worker-promise.html
+F ext/wasm/testing-worker1-promiser.js 3c13fda53cc8b5d148ae34f621eba99aff393d66718b216bfd9d3f9075dd83bc w ext/wasm/testing-worker-promise.js
 F ext/wasm/testing1.html 528001c7e32ee567abc195aa071fd9820cc3c8ffc9c8a39a75e680db05f0c409
 F ext/wasm/testing1.js 2def7a86c52ff28b145cb86188d5c7a49d5993f9b78c50d140e1c31551220955
 F ext/wasm/testing2.html a66951c38137ff1d687df79466351f3c734fa9c6d9cce71d3cf97c291b2167e3
@@ -2008,8 +2009,8 @@ F vsixtest/vsixtest.tcl 6a9a6ab600c25a91a7acc6293828957a386a8a93
 F vsixtest/vsixtest.vcxproj.data 2ed517e100c66dc455b492e1a33350c1b20fbcdc
 F vsixtest/vsixtest.vcxproj.filters 37e51ffedcdb064aad6ff33b6148725226cd608e
 F vsixtest/vsixtest_TemporaryKey.pfx e5b1b036facdb453873e7084e1cae9102ccc67a0
-P 03b9db9b98cb36faa7de5a8a64d2e13c4aeaadfefb33ac92bb41056f6be3f121
-R 5b16a785d8c414a8eb9d618c8d9d8cea
+P 1e447849fb65887e806e3348a8a68f70ea6802bc0a1e56c385a279f27cc0cdda
+R 20d6ad983c84af7c7f2e33fe283b134d
 U stephan
-Z fdaa5cf9f1bfd4215c6ebf07223819c1
+Z 996279c2387a16066b296229c9c99a7d
 # Remove this line to create a well-formed Fossil manifest.
index 1b823c223368e1e9896ac8c077685c370c50dd37..26a4257f52ae84c3b4395172689138213ee6dcf6 100644 (file)
@@ -1 +1 @@
-1e447849fb65887e806e3348a8a68f70ea6802bc0a1e56c385a279f27cc0cdda
\ No newline at end of file
+b030f321bd5a38cdd5d6f6735f201afa62d30d2b0ba02e67f055b4895553a878
\ No newline at end of file