More work on how to configure the sqlite3 JS API bootstrapping process from higher-level code. Initial version of sqlite3-worker1-promiser, a Promise-based proxy for the Worker API #1.

FossilOrigin-Name: b030f321bd5a38cdd5d6f6735f201afa62d30d2b0ba02e67f055b4895553a878
This commit is contained in:
stephan 2022-08-24 05:59:23 +00:00
parent efeee19a95
commit 9a34509a06
9 changed files with 537 additions and 204 deletions

View File

@ -18,44 +18,24 @@
'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.");
}

View File

@ -93,16 +93,16 @@
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.
@ -120,20 +120,26 @@
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;

View 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});
});

View File

@ -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
};

View File

@ -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.");
})();

View 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>

View File

@ -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.");
})();

View File

@ -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.

View File

@ -1 +1 @@
1e447849fb65887e806e3348a8a68f70ea6802bc0a1e56c385a279f27cc0cdda
b030f321bd5a38cdd5d6f6735f201afa62d30d2b0ba02e67f055b4895553a878