1
0
mirror of https://github.com/sqlite/sqlite.git synced 2025-07-27 20:41:58 +03:00

Expand the worker1 'exec' op handling for per-row callbacks for API-level consistency and smooth some edges between worker1 core and worker1-promiser. Add worker1 'config-get' message to fetch the serializable parts of the sqlite3.config state. Improve the 'open' op's handling of the 'persistent' option (noting that we cannot yet test that case from a worker).

FossilOrigin-Name: 509f8839201ec1ea4863bd31493e6c29a0721ca6340755bb96656b828758fea7
This commit is contained in:
stephan
2022-08-24 18:39:46 +00:00
parent 9c765e7945
commit 3734401a95
7 changed files with 264 additions and 142 deletions

View File

@ -252,6 +252,21 @@ self.sqlite3ApiBootstrap.initializers.push(function(sqlite3){
out.cbArg = (stmt)=>stmt.get(out.opt.rowMode);
break;
}
/*
TODO?: how can we define rowMode such that it uses
rowMode of 'object' and returns a given named field from
the object. Something like:
if(?what goes here?){
out.cbArg = function f(stmt){return stmt.get(this.obj)[this.colName]}
.bind({obj:{}, colName: ???what goes here???}});
break;
}
Maybe rowMode:['colName1',... 'colNameN']? That could be
ambiguous: might mean "return an object with just these
columns".
*/
toss3("Invalid rowMode:",out.opt.rowMode);
}
}

View File

@ -42,25 +42,21 @@
initialization is complete, as the initialization is synchronous.
In some contexts, however, listening for the above message is
a better fit.
Note that the worker-based interface can be slightly quirky because
of its async nature. In particular, any number of messages may be posted
to the worker before it starts handling any of them. If, e.g., an
"open" operation fails, any subsequent messages will fail. The
Promise-based wrapper for this API (`sqlite3-worker1-promiser.js`)
is more comfortable to use in that regard.
TODO: hoist the message API docs from deep in this code to here.
*/
self.sqlite3ApiBootstrap.initializers.push(function(sqlite3){
sqlite3.initWorker1API = function(){
'use strict';
/**
UNDER CONSTRUCTION
We need an API which can proxy the DB API via a Worker message
interface. The primary quirky factor in such an API is that we
cannot pass callback functions between the window thread and a
worker thread, so we have to receive all db results via
asynchronous message-passing. That requires an asychronous API
with a distinctly different shape than OO API #1.
TODOs include, but are not necessarily limited to:
- Support for handling multiple DBs via this interface is under
development.
*/
const toss = (...args)=>{throw new Error(args.join(' '))};
if('function' !== typeof importScripts){
toss("Cannot initalize the sqlite3 worker API in the main thread.");
@ -86,12 +82,13 @@ sqlite3.initWorker1API = function(){
};
/**
Helper for managing Worker-level state.
Internal helper for managing Worker-level state.
*/
const wState = {
defaultDb: undefined,
idSeq: 0,
idMap: new WeakMap,
xfer: [/*Temp holder for "transferable" postMessage() state.*/],
open: function(opt){
const db = new DB(opt.filename);
this.dbs[getDbId(db)] = db;
@ -109,9 +106,15 @@ sqlite3.initWorker1API = function(){
}
}
},
/**
Posts the given worker message value. If xferList is provided,
it must be an array, in which case a copy of it passed as
postMessage()'s second argument and xferList.length is set to
0.
*/
post: function(msg,xferList){
if(xferList){
self.postMessage( msg, xferList );
if(xferList && xferList.length){
self.postMessage( msg, Array.from(xferList) );
xferList.length = 0;
}else{
self.postMessage(msg);
@ -146,32 +149,40 @@ sqlite3.initWorker1API = function(){
message type key. The onmessage() dispatcher attempts to
dispatch all inbound messages to a method of this object,
passing it the event.data part of the inbound event object. All
methods must return a plain Object containing any response
methods must return a plain Object containing any result
state, which the dispatcher may amend. All methods must throw
on error.
*/
const wMsgHandler = {
xfer: [/*Temp holder for "transferable" postMessage() state.*/],
/**
Proxy for the DB constructor. Expects to be passed a single
object or a falsy value to use defaults. The object may have a
filename property to name the db file (see the DB constructor
for peculiarities and transformations). The response is an
object:
object or a falsy value to use defaults:
{
filename: db filename (possibly differing from the input),
filename [=":memory:" or "" (unspecified)]: the db filename.
See the sqlite3.oo1.DB constructor for peculiarities and transformations,
persistent [=false]: if true and filename is not one of ("",
":memory:"), 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.
}
The response object looks like:
{
filename: db filename, possibly differing from the input.
dbId: an opaque ID value which must be passed in the message
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.
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.
persistent: true if the given filename resides in the
known-persistent storage, else false. This determination is
independent of the `persistent` input argument.
}
*/
open: function(ev){
@ -179,28 +190,32 @@ sqlite3.initWorker1API = function(){
if(args.simulateError){ // undocumented internal testing option
toss("Throwing because of simulateError flag.");
}
if(args.persistent && args.filename){
oargs.filename = sqlite3.capi.sqlite3_web_persistent_dir() + args.filename;
}else if('' === args.filename){
oargs.filename = args.filename;
const rc = Object.create(null);
const pDir = sqlite3.capi.sqlite3_web_persistent_dir();
if(!args.filename || ':memory:'===args.filename){
oargs.filename = args.filename || '';
}else if(pDir){
oargs.filename = pDir + ('/'===args.filename[0] ? args.filename : ('/'+args.filename));
}else{
oargs.filename = args.filename || ':memory:';
}
oargs.filename = args.filename;
}
const db = wState.open(oargs);
return {
filename: db.filename,
dbId: getDbId(db)
};
rc.filename = db.filename;
rc.persistent = !!pDir && db.filename.startsWith(pDir);
rc.dbId = getDbId(db);
return rc;
},
/**
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:
filesystem, else it will be kept intact, noting that unlink
failure is ignored. The result object is:
{
filename: db filename _if_ the db is opened when this
is called, else the undefined value
is called, else the undefined value
dbId: the ID of the closed b, or undefined if none is closed
}
@ -211,7 +226,7 @@ sqlite3.initWorker1API = function(){
const db = getMsgDb(ev,false);
const response = {
filename: db && db.filename,
dbId: db ? getDbId(db) : undefined
dbId: db && getDbId(db)
};
if(db){
wState.close(db, ((ev.args && 'object'===typeof ev.args)
@ -220,7 +235,7 @@ sqlite3.initWorker1API = function(){
return response;
},
/**
Proxy for DB.exec() which expects a single argument of type
Proxy for oo1.DB.exec() which expects a single argument of type
string (SQL to execute) or an options object in the form
expected by exec(). The notable differences from exec()
include:
@ -234,12 +249,21 @@ sqlite3.initWorker1API = function(){
message type key, in which case a callback function will be
applied which posts each row result via:
postMessage({type: thatKeyType, row: theRow})
postMessage({type: thatKeyType, rowNumber: 1-based-#, row: theRow})
And, at the end of the result set (whether or not any
result rows were produced), it will post an identical
message with row:null to alert the caller than the result
set is completed.
And, at the end of the result set (whether or not any result
rows were produced), it will post an identical message with
(row=undefined, rowNumber=null) to alert the caller than the
result set is completed. Note that a row value of `null` is
a legal row result for certain `rowMode` values.
(Design note: we don't use (row=undefined, rowNumber=undefined)
to indicate end-of-results because fetching those would be
indistinguishable from fetching from an empty object unless the
client used hasOwnProperty() (or similar) to distinguish
"missing property" from "property with the undefined value".
Similarly, `null` is a legal value for `row` in some case ,
whereas the db layer won't emit a result value of `undefined`.)
The callback proxy must not recurse into this interface, or
results are undefined. (It hypothetically cannot recurse
@ -250,52 +274,73 @@ sqlite3.initWorker1API = function(){
The response is the input options object (or a synthesized
one if passed only a string), noting that
options.resultRows and options.columnNames may be populated
by the call to exec().
This opens/creates the Worker's db if needed.
by the call to db.exec().
*/
exec: function(ev){
const opt = (
const rc = (
'string'===typeof ev.args
) ? {sql: ev.args} : (ev.args || Object.create(null));
if(undefined===opt.rowMode){
if(undefined===rc.rowMode){
/* Since the default rowMode of 'stmt' is not useful
for the Worker interface, we'll default to
something else. */
opt.rowMode = 'array';
}else if('stmt'===opt.rowMode){
toss("Invalid rowMode for exec(): stmt mode",
rc.rowMode = 'array';
}else if('stmt'===rc.rowMode){
toss("Invalid rowMode for 'exec': stmt mode",
"does not work in the Worker API.");
}
const db = getMsgDb(ev);
if(opt.callback || Array.isArray(opt.resultRows)){
if(rc.callback || Array.isArray(rc.resultRows)){
// Part of a copy-avoidance optimization for blobs
db._blobXfer = this.xfer;
db._blobXfer = wState.xfer;
}
const callbackMsgType = opt.callback;
const callbackMsgType = rc.callback;
let rowNumber = 0;
if('string' === typeof callbackMsgType){
/* Treat this as a worker message type and post each
row as a message of that type. */
const that = this;
opt.callback =
(row)=>wState.post({type: callbackMsgType, row:row}, this.xfer);
rc.callback =
(row)=>wState.post({type: callbackMsgType, rowNumber:++rowNumber, row:row}, wState.xfer);
}
try {
db.exec(opt);
if(opt.callback instanceof Function){
opt.callback = callbackMsgType;
wState.post({type: callbackMsgType, row: null});
db.exec(rc);
if(rc.callback instanceof Function){
rc.callback = callbackMsgType;
wState.post({type: callbackMsgType, rowNumber: null, row: undefined});
}
}/*catch(e){
console.warn("Worker is propagating:",e);throw e;
}*/finally{
delete db._blobXfer;
if(opt.callback){
opt.callback = callbackMsgType;
}
}
return opt;
}finally{
delete db._blobXfer;
if(rc.callback){
rc.callback = callbackMsgType;
}
}
return rc;
}/*exec()*/,
/**
Returns a JSON-friendly form of a _subset_ of sqlite3.config,
sans any parts which cannot be serialized. Because we cannot,
from here, distingush whether or not certain objects can be
serialized, this routine selectively copies certain properties
rather than trying JSON.stringify() and seeing what happens
(the results are horrid if the config object contains an
Emscripten module object).
In addition to the "real" config properties, it sythesizes
the following:
- persistenceEnabled: true if persistent dir support is available,
else false.
*/
'config-get': function(){
const rc = Object.create(null), src = sqlite3.config;
[
'persistentDirName', 'bigIntEnabled'
].forEach(function(k){
if(Object.getOwnPropertyDescriptor(src, k)) rc[k] = src[k];
});
rc.persistenceEnabled = !!sqlite3.capi.sqlite3_web_persistent_dir();
return rc;
},
/**
TO(RE)DO, once we can abstract away access to the
JS environment's virtual filesystem. Currently this
@ -329,7 +374,7 @@ sqlite3.initWorker1API = function(){
filename: db.filename,
mimetype: 'application/x-sqlite3'
};
this.xfer.push(response.buffer.buffer);
wState.xfer.push(response.buffer.buffer);
return response;**/
}/*export()*/,
toss: function(ev){
@ -344,22 +389,32 @@ sqlite3.initWorker1API = function(){
form:
{ type: apiCommand,
dbId: optional DB ID value (else uses a default db handle),
args: apiArguments,
dbId: optional DB ID value (else uses a default db handle),
messageId: optional client-specific value
}
As a rule, these commands respond with a postMessage() of their
own in the form:
TODO: refactoring is underway.
The responses always have an object-format `result` part. If the
inbound object has a `messageId` property, that property is
own. The responses always have a `type` property equal to the
input message's type and an object-format `result` part. If
the inbound object has a `messageId` property, that property is
always mirrored in the result object, for use in client-side
dispatching of these asynchronous results. Exceptions thrown
during processing result in an `error`-type event with a payload
in the form:
dispatching of these asynchronous results. For example:
{
type: 'open',
messageId: ...copied from inbound message...,
dbId: ID of db which was opened,
result: {
dbId: repeat of ^^^, for API consistency's sake,
filename: ...,
persistent: false
},
...possibly other framework-internal/testing/debugging info...
}
Exceptions thrown during processing result in an `error`-type
event with a payload in the form:
{ type: 'error',
dbId: DB handle ID,
@ -413,10 +468,15 @@ sqlite3.initWorker1API = function(){
workerReceivedTime: arrivalTime,
workerRespondTime: performance.now(),
departureTime: ev.departureTime,
// TODO: move the timing bits into...
//timing:{
// departure: ev.departureTime,
// workerReceived: arrivalTime,
// workerResponse: performance.now();
//},
result: result
}, wMsgHandler.xfer);
}, wState.xfer);
};
self.postMessage({type:'sqlite3-api',result:'worker1-ready'});
}.bind({self, sqlite3});
});

View File

@ -27,7 +27,7 @@
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.
worker API, with any exceptions documented below.
It requires a configuration object with the following properties:
@ -127,26 +127,34 @@
- 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 callback is called one time for each row of the result set,
passed the same worker message format as the worker API emits:
{type:typeString, row:VALUE, rowNumber:1-based-#}
Where `typeString` is an internally-synthesized message type string
used temporarily for worker message dispatching. It can be ignored
by all client code except that which tests this API. The `row`
property contains the row result in the form implied by the
`rowMode` option (defaulting to `'array'`). The `rowNumber` is a
1-based integer value incremented by 1 on each call into th
callback.
At the end of the result set, the same event is fired with
(row=undefined, rowNumber=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 idTypeMap = config.generateMessageId ? undefined : Object.create(null);
const genMsgId = config.generateMessageId || function(msg){
return msg.type+'#'+(++idNumber);
return msg.type+'#'+(idTypeMap[msg.type] = (idTypeMap[msg.type]||0) + 1);
};
const toss = (...args)=>{throw new Error(args.join(' '))};
if('function'===typeof config.worker) config.worker = config.worker();
@ -162,7 +170,7 @@ self.sqlite3Worker1Promiser = function callee(config = callee.defaultConfig){
}
msgHandler = handlerMap[ev.type] /* check for exec per-row callback */;
if(msgHandler && msgHandler.onrow){
msgHandler.onrow(ev.row);
msgHandler.onrow(ev);
return;
}
if(config.onunhandled) config.onunhandled(arguments[0]);
@ -183,13 +191,10 @@ self.sqlite3Worker1Promiser = function callee(config = callee.defaultConfig){
default:
break;
}
try {
msgHandler.resolve(ev);
}catch(e){
msgHandler.reject(e);
}
try {msgHandler.resolve(ev)}
catch(e){msgHandler.reject(e)}
}/*worker.onmessage()*/;
return function(/*(msgType, msgArgs) || (msg)*/){
return function(/*(msgType, msgArgs) || (msgEnvelope)*/){
let msg;
if(1===arguments.length){
msg = arguments[0];
@ -206,15 +211,29 @@ self.sqlite3Worker1Promiser = function callee(config = callee.defaultConfig){
msg.departureTime = performance.now();
const proxy = Object.create(null);
proxy.message = msg;
let cbId /* message handler ID for exec on-row callback proxy */;
let rowCallbackId /* 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';
rowCallbackId = msg.messageId+':row';
proxy.onrow = msg.args.callback;
msg.args.callback = cbId;
handlerMap[cbId] = proxy;
msg.args.callback = rowCallbackId;
handlerMap[rowCallbackId] = proxy;
}else if('string' === typeof msg.args.callback){
toss("exec callback may not be a string when using the Promise interface.");
/**
Design note: the reason for this limitation is that this
API takes over worker.onmessage() and the client has no way
of adding their own message-type handlers to it. Per-row
callbacks are implemented as short-lived message.type
mappings for worker.onmessage().
We "could" work around this by providing a new
config.fallbackMessageHandler (or some such) which contains
a map of event type names to callbacks. Seems like overkill
for now, seeing as the client can pass callback functions
to this interface (whereas the string-form "callback" is
needed for the over-the-Worker interface).
*/
}
}
//debug("requestWork", msg);
@ -225,7 +244,7 @@ self.sqlite3Worker1Promiser = function callee(config = callee.defaultConfig){
debug("Posting",msg.type,"message to Worker dbId="+(config.dbId||'default')+':',msg);
config.worker.postMessage(msg);
});
if(cbId) p = p.finally(()=>delete handlerMap[cbId]);
if(rowCallbackId) p = p.finally(()=>delete handlerMap[rowCallbackId]);
return p;
};
}/*sqlite3Worker1Promiser()*/;

View File

@ -60,24 +60,41 @@
delete self.sqlite3Worker1Promiser;
const wtest = async function(msgType, msgArgs, callback){
if(2===arguments.length && 'function'===typeof msgArgs){
callback = msgArgs;
msgArgs = undefined;
}
const p = workerPromise({type: msgType, args:msgArgs});
return callback ? p.then(callback).finally(testCount) : p;
};
const runTests = async function(){
const dbFilename = '/testing2.sqlite3';
logHtml('',
"Sending 'open' message and waiting for its response before continuing.");
startTime = performance.now();
let sqConfig;
await wtest('config-get', (ev)=>{
const r = ev.result;
log('sqlite3.config subset:', r);
T.assert('boolean' === typeof r.bigIntEnabled)
.assert('string'===typeof r.persistentDirName)
.assert('boolean' === typeof r.persistenceEnabled);
sqConfig = r;
});
logHtml('',
"Sending 'open' message and waiting for its response before continuing...");
await wtest('open', {
filename: dbFilename,
persistent: true,
persistent: sqConfig.persistenceEnabled,
simulateError: 0 /* if true, fail the 'open' */,
}, function(ev){
log("then open result",ev);
T.assert(1 && (dbFilename===ev.result.filename
|| (sqlite3TestModule.sqlite3ApiConfig.persistentDirName
+ dbFilename)==ev.result.filename))
const r = ev.result;
log("then open result",r);
T.assert(r.persistent === sqConfig.persistenceEnabled)
.assert(r.persistent
? (dbFilename!==r.filename)
: (dbFilename==r.filename))
.assert(ev.dbId)
.assert(ev.messageId)
.assert(promiserConfig.dbId === ev.dbId);
@ -145,11 +162,17 @@
.assert(3 === ev.resultRows[1][0]);
});
const resultRowTest1 = function f(row){
const resultRowTest1 = function f(ev){
if(undefined === f.counter) f.counter = 0;
if(row) ++f.counter;
//log("exec() result row:",row);
T.assert(null===row || 'number' === typeof row.b);
if(null === ev.rowNumber){
/* End of result set. */
T.assert(undefined === ev.row);
}else{
T.assert(ev.rowNumber > 0);
++f.counter;
}
log("exec() result row:",ev);
T.assert(null === ev.rowNumber || 'number' === typeof ev.row.b);
};
await wtest('exec',{
sql: 'select a a, b b from t order by a',
@ -195,10 +218,9 @@
T.assert('string' === typeof ev.result.filename);
});
await wtest('close').then((ev)=>{
await wtest('close', (ev)=>{
T.assert(undefined === ev.result.filename);
log("That's all, folks!");
});
}).finally(()=>log("That's all, folks!"));
}/*runTests2()*/;

View File

@ -115,9 +115,15 @@
},
resultRowTest1: function f(ev){
if(undefined === f.counter) f.counter = 0;
if(ev.row) ++f.counter;
//log("exec() result row:",ev.row);
T.assert(null===ev.row || 'number' === typeof ev.row.b);
if(null === ev.rowNumber){
/* End of result set. */
T.assert(undefined === ev.row);
}else{
T.assert(ev.rowNumber > 0);
++f.counter;
}
//log("exec() result row:",ev);
T.assert(null === ev.rowNumber || 'number' === typeof ev.row.b);
}
};

View File

@ -1,5 +1,5 @@
C js:\sresolve\sthe\smysterious\s"extra"\sunhandled\sexception\snotification,\scaused\sby\sinadvertently\sforking\sone\spromise\sinto\stwo\sseparate\sones\s(failing\sto\sproperly\sreassign\sa\sthen()\sresult).\sFix\sa\stypo\sin\snew\sWorker\s1\scode\swhich\scaused\sthe\sDB(filename)\sname\sto\sbe\sincorrect.
D 2022-08-24T14:50:10.920
C Expand\sthe\sworker1\s'exec'\sop\shandling\sfor\sper-row\scallbacks\sfor\sAPI-level\sconsistency\sand\ssmooth\ssome\sedges\sbetween\sworker1\score\sand\sworker1-promiser.\sAdd\sworker1\s'config-get'\smessage\sto\sfetch\sthe\sserializable\sparts\sof\sthe\ssqlite3.config\sstate.\sImprove\sthe\s'open'\sop's\shandling\sof\sthe\s'persistent'\soption\s(noting\sthat\swe\scannot\syet\stest\sthat\scase\sfrom\sa\sworker).
D 2022-08-24T18:39:46.246
F .fossil-settings/empty-dirs dbb81e8fc0401ac46a1491ab34a7f2c7c0452f2f06b54ebb845d024ca8283ef1
F .fossil-settings/ignore-glob 35175cdfcf539b2318cb04a9901442804be81cd677d8b889fcc9149c21f239ea
F LICENSE.md df5091916dbb40e6e9686186587125e1b2ff51f022cc334e886c19a0e9982724
@ -483,10 +483,10 @@ F ext/wasm/api/post-js-footer.js b64319261d920211b8700004d08b956a6c285f3b0bba814
F ext/wasm/api/post-js-header.js 0e853b78db83cb1c06b01663549e0e8b4f377f12f5a2d9a4a06cb776c003880b
F ext/wasm/api/sqlite3-api-cleanup.js 1a12e64060c2cb0defd34656a76a9b1d7ed58459c290249bb31567c806fd44de
F ext/wasm/api/sqlite3-api-glue.js 67ca83974410961953eeaa1dfed3518530d68381729ed1d27f95122f5baeabd3
F ext/wasm/api/sqlite3-api-oo1.js f6dcaac3270182471f97efcfda25bd4a4ac1777b8ec52ebd1c6846721160e54c
F ext/wasm/api/sqlite3-api-oo1.js 5ce93b89165e1eb6ef26ba67ae9d3c25379df74eea82edb7b46255f86db21cfc
F ext/wasm/api/sqlite3-api-opfs.js 011799db398157cbd254264b6ebae00d7234b93d0e9e810345f213a5774993c0
F ext/wasm/api/sqlite3-api-prologue.js 2d5c5d3355f55eefe51922cec5bfedbec0f8300db98a17685ab7a34a03953c7a
F ext/wasm/api/sqlite3-api-worker1.js b23f66ef5afd350a17fbadb795007098e518a40e5c7c439cd83ef34aa55a45af
F ext/wasm/api/sqlite3-api-worker1.js f7372b84b6d71ebdc0d2a9e7944ce571b4f18e0dd4c1be78282c68b4582558ca
F ext/wasm/api/sqlite3-wasi.h 25356084cfe0d40458a902afb465df8c21fc4152c1d0a59b563a3fba59a068f9
F ext/wasm/api/sqlite3-wasm.c 0d81282eaeff2a6e9fc5c28a388c5c5b45cf25a9393992fa511ac009b27df982
F ext/wasm/common/SqliteTestUtil.js eb96275bed43fdb364b7d65bcded0ca5e22aaacff120d593d1385f852f486247
@ -508,14 +508,14 @@ 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 9638b0ced7f02806c3220b616f08729dde9eb13fb56e125cd4759f40bfa81210
F ext/wasm/sqlite3-worker1-promiser.js 92b8da5f38439ffec459a8215775d30fa498bc0f1ab929ff341fc3dd479660b9
F ext/wasm/sqlite3-worker1.js 0c1e7626304543969c3846573e080c082bf43bcaa47e87d416458af84f340a9e
F ext/wasm/testing-worker1-promiser.html 6eaec6e04a56cf24cf4fa8ef49d78ce8905dde1354235c9125dca6885f7ce893
F ext/wasm/testing-worker1-promiser.js 931d909c769c57292f1cafdf10c7dab402d17cd16a6d0ec32089f67b559b058f
F ext/wasm/testing-worker1-promiser.js 81d81eda77c9d4a3e43cfeee91df6c3b039782cc998020d72fe1fdf91790242d
F ext/wasm/testing1.html 528001c7e32ee567abc195aa071fd9820cc3c8ffc9c8a39a75e680db05f0c409
F ext/wasm/testing1.js 2def7a86c52ff28b145cb86188d5c7a49d5993f9b78c50d140e1c31551220955
F ext/wasm/testing2.html a66951c38137ff1d687df79466351f3c734fa9c6d9cce71d3cf97c291b2167e3
F ext/wasm/testing2.js f01e8d7b32f6d4790f8352bf8dc17d2b860afa58f6c8ea1f824ef1c0d2068138
F ext/wasm/testing2.js 04a4194188d54856027eb4cad7239223a8f7a60e64b0aac81fc1a5a70363b98e
F install-sh 9d4de14ab9fb0facae2f48780b874848cbf2f895 x
F ltmain.sh 3ff0879076df340d2e23ae905484d8c15d5fdea8
F magic.txt 8273bf49ba3b0c8559cb2774495390c31fd61c60
@ -2009,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 b030f321bd5a38cdd5d6f6735f201afa62d30d2b0ba02e67f055b4895553a878
R a11ec2b23afafa9cff8838dfde907f0c
P 7467ac88801224089b51c6ba7924f93283dd87beca602a186c83632df26cfc85
R 4d53a1266e528a954f73a792c3648256
U stephan
Z 64f88270da37ddd3ce54e6e07b8ab1af
Z cffa6daad399e2d401f0fd69f4f04b4f
# Remove this line to create a well-formed Fossil manifest.

View File

@ -1 +1 @@
7467ac88801224089b51c6ba7924f93283dd87beca602a186c83632df26cfc85
509f8839201ec1ea4863bd31493e6c29a0721ca6340755bb96656b828758fea7