1
0
mirror of https://github.com/sqlite/sqlite.git synced 2025-07-29 08:01:23 +03:00

Internal restructuring of the OPFS sqlite3_vfs in order to facilitate certain experimentation and improve error reporting/hints if it cannot be activated. Deprecate the name sqlite3.opfs.OpfsDb, preferring sqlite3.oo1.OpfsDb for consistency with JsStorageDb and any future DB subclasses.

FossilOrigin-Name: 0c5c51f4fb04a4b90c50ec9704cfea9a3fb7d7d0ee55c1b0d4476129188217a6
This commit is contained in:
stephan
2022-11-29 05:25:08 +00:00
parent e6b0154138
commit 04184761de
6 changed files with 919 additions and 876 deletions

View File

@ -76,15 +76,25 @@ self.sqlite3ApiBootstrap.initializers.push(function(sqlite3){
`opfs` property, containing several OPFS-specific utilities.
*/
const installOpfsVfs = function callee(options){
if(!self.SharedArrayBuffer ||
!self.Atomics ||
!self.FileSystemHandle ||
if(!self.SharedArrayBuffer
|| !self.Atomics){
return Promise.reject(
new Error("Cannot install OPFS: Missing SharedArrayBuffer and/or Atomics. "+
"The server must emit the COOP/COEP response headers to enable those. "+
"See https://sqlite.org/wasm/doc/trunk/persistence.md#coop-coep")
);
}else if(self.window===self && self.document){
return Promise.reject(
new Error("The OPFS sqlite3_vfs cannot run in the main thread "+
"because it requires Atomics.wait().")
);
}else if(!self.FileSystemHandle ||
!self.FileSystemDirectoryHandle ||
!self.FileSystemFileHandle ||
!self.FileSystemFileHandle.prototype.createSyncAccessHandle ||
!navigator.storage.getDirectory){
return Promise.reject(
new Error("This environment does not have OPFS support.")
new Error("Missing required OPFS APIs.")
);
}
if(!options || 'object'!==typeof options){
@ -134,6 +144,18 @@ const installOpfsVfs = function callee(options){
OPFS-specific sqlite3_vfs evolves.
*/
const opfsUtil = Object.create(null);
/**
Returns true if _this_ thread has access to the OPFS APIs.
*/
const thisThreadHasOPFS = ()=>{
return self.FileSystemHandle &&
self.FileSystemDirectoryHandle &&
self.FileSystemFileHandle &&
self.FileSystemFileHandle.prototype.createSyncAccessHandle &&
navigator.storage.getDirectory;
};
/**
Not part of the public API. Solely for internal/development
use.
@ -1179,12 +1201,16 @@ const installOpfsVfs = function callee(options){
//consideration.
if(sqlite3.oo1){
opfsUtil.OpfsDb = function(...args){
const OpfsDb = function(...args){
const opt = sqlite3.oo1.DB.dbCtorHelper.normalizeArgs(...args);
opt.vfs = opfsVfs.$zName;
sqlite3.oo1.DB.dbCtorHelper.call(this, opt);
};
opfsUtil.OpfsDb.prototype = Object.create(sqlite3.oo1.DB.prototype);
sqlite3.oo1.OpfsDb =
opfsUtil.OpfsDb /* sqlite3.opfs.OpfsDb => deprecated name -
will be phased out Real Soon */ =
OpfsDb;
OpfsDb.prototype = Object.create(sqlite3.oo1.DB.prototype);
sqlite3.oo1.DB.dbCtorHelper.setVfsPostOpenSql(
opfsVfs.pointer,
[
@ -1206,13 +1232,6 @@ const installOpfsVfs = function callee(options){
);
}
/**
Potential TODOs:
- Expose one or both of the Worker objects via opfsUtil and
publish an interface for proxying the higher-level OPFS
features like getting a directory listing.
*/
const sanityCheck = function(){
const scope = wasm.scopedAllocPush();
const sq3File = new sqlite3_file();
@ -1282,6 +1301,11 @@ const installOpfsVfs = function callee(options){
W.onmessage = function({data}){
//log("Worker.onmessage:",data);
switch(data.type){
case 'opfs-unavailable':
/* Async proxy has determined that OPFS is unavailable. There's
nothing more for us to do here. */
promiseReject(new Error(data.payload.join(' ')));
break;
case 'opfs-async-loaded':
/*Arrives as soon as the asyc proxy finishes loading.
Pass our config and shared state on to the async worker.*/
@ -1308,6 +1332,7 @@ const installOpfsVfs = function callee(options){
warn("Running sanity checks because of opfs-sanity-check URL arg...");
sanityCheck();
}
if(thisThreadHasOPFS()){
navigator.storage.getDirectory().then((d)=>{
W.onerror = W._originalOnError;
delete W._originalOnError;
@ -1316,6 +1341,9 @@ const installOpfsVfs = function callee(options){
log("End of OPFS sqlite3_vfs setup.", opfsVfs);
promiseResolve(sqlite3);
});
}else{
promiseResolve(sqlite3);
}
}catch(e){
error(e);
promiseReject(e);
@ -1334,9 +1362,6 @@ const installOpfsVfs = function callee(options){
installOpfsVfs.defaultProxyUri =
"sqlite3-opfs-async-proxy.js";
self.sqlite3ApiBootstrap.initializersAsync.push(async (sqlite3)=>{
if(sqlite3.scriptInfo && !sqlite3.scriptInfo.isWorker){
return;
}
try{
let proxyJs = installOpfsVfs.defaultProxyUri;
if(sqlite3.scriptInfo.sqlite3Dir){

View File

@ -47,43 +47,45 @@
usage of those methods to remove the "await".
*/
"use strict";
const toss = function(...args){throw new Error(args.join(' '))};
if(self.window === self){
const wPost = (type,...args)=>postMessage({type, payload:args});
const installAsyncProxy = function(self){
const toss = function(...args){throw new Error(args.join(' '))};
if(self.window === self){
toss("This code cannot run from the main thread.",
"Load it as a Worker from a separate Worker.");
}else if(!navigator.storage.getDirectory){
}else if(!navigator.storage.getDirectory){
toss("This API requires navigator.storage.getDirectory.");
}
}
/**
/**
Will hold state copied to this object from the syncronous side of
this API.
*/
const state = Object.create(null);
*/
const state = Object.create(null);
/**
/**
verbose:
0 = no logging output
1 = only errors
2 = warnings and errors
3 = debug, warnings, and errors
*/
state.verbose = 1;
*/
state.verbose = 1;
const loggers = {
const loggers = {
0:console.error.bind(console),
1:console.warn.bind(console),
2:console.log.bind(console)
};
const logImpl = (level,...args)=>{
};
const logImpl = (level,...args)=>{
if(state.verbose>level) loggers[level]("OPFS asyncer:",...args);
};
const log = (...args)=>logImpl(2, ...args);
const warn = (...args)=>logImpl(1, ...args);
const error = (...args)=>logImpl(0, ...args);
const metrics = Object.create(null);
metrics.reset = ()=>{
};
const log = (...args)=>logImpl(2, ...args);
const warn = (...args)=>logImpl(1, ...args);
const error = (...args)=>logImpl(0, ...args);
const metrics = Object.create(null);
metrics.reset = ()=>{
let k;
const r = (m)=>(m.count = m.time = m.wait = 0);
for(k in state.opIds){
@ -94,8 +96,8 @@ metrics.reset = ()=>{
s.count = s.time = 0;
s = metrics.s11n.deserialize = Object.create(null);
s.count = s.time = 0;
};
metrics.dump = ()=>{
};
metrics.dump = ()=>{
let k, n = 0, t = 0, w = 0;
for(k in state.opIds){
const m = metrics[k];
@ -110,17 +112,17 @@ metrics.dump = ()=>{
"\nTotal of",n,"op(s) for",t,"ms",
"approx",w,"ms spent waiting on OPFS APIs.");
console.log("Serialization metrics:",metrics.s11n);
};
};
/**
/**
__openFiles is a map of sqlite3_file pointers (integers) to
metadata related to a given OPFS file handles. The pointers are, in
this side of the interface, opaque file handle IDs provided by the
synchronous part of this constellation. Each value is an object
with a structure demonstrated in the xOpen() impl.
*/
const __openFiles = Object.create(null);
/**
*/
const __openFiles = Object.create(null);
/**
__implicitLocks is a Set of sqlite3_file pointers (integers) which were
"auto-locked". i.e. those for which we obtained a sync access
handle without an explicit xLock() call. Such locks will be
@ -132,29 +134,29 @@ const __openFiles = Object.create(null);
operation which acquires them, we pay a massive performance
penalty: speedtest1 benchmarks take up to 4x as long. By delaying
the lock release until idle time, the hit is negligible.
*/
const __implicitLocks = new Set();
*/
const __implicitLocks = new Set();
/**
/**
Expects an OPFS file path. It gets resolved, such that ".."
components are properly expanded, and returned. If the 2nd arg is
true, the result is returned as an array of path elements, else an
absolute path string is returned.
*/
const getResolvedPath = function(filename,splitIt){
*/
const getResolvedPath = function(filename,splitIt){
const p = new URL(
filename, 'file://irrelevant'
).pathname;
return splitIt ? p.split('/').filter((v)=>!!v) : p;
};
};
/**
/**
Takes the absolute path to a filesystem element. Returns an array
of [handleOfContainingDir, filename]. If the 2nd argument is truthy
then each directory element leading to the file is created along
the way. Throws if any creation or resolution fails.
*/
const getDirForFilename = async function f(absFilename, createDirs = false){
*/
const getDirForFilename = async function f(absFilename, createDirs = false){
const path = getResolvedPath(absFilename, true);
const filename = path.pop();
let dh = state.rootDir;
@ -164,9 +166,9 @@ const getDirForFilename = async function f(absFilename, createDirs = false){
}
}
return [dh, filename];
};
};
/**
/**
If the given file-holding object has a sync handle attached to it,
that handle is remove and asynchronously closed. Though it may
sound sensible to continue work as soon as the close() returns
@ -175,8 +177,8 @@ const getDirForFilename = async function f(absFilename, createDirs = false){
because they may happen out of order from the close(). OPFS does
not guaranty that the actual order of operations is retained in
such cases. i.e. always "await" on the result of this function.
*/
const closeSyncHandle = async (fh)=>{
*/
const closeSyncHandle = async (fh)=>{
if(fh.syncHandle){
log("Closing sync handle for",fh.filenameAbs);
const h = fh.syncHandle;
@ -185,9 +187,9 @@ const closeSyncHandle = async (fh)=>{
__implicitLocks.delete(fh.fid);
return h.close();
}
};
};
/**
/**
A proxy for closeSyncHandle() which is guaranteed to not throw.
This function is part of a lock/unlock step in functions which
@ -197,16 +199,16 @@ const closeSyncHandle = async (fh)=>{
_attempt_ at reducing cross-tab contention but it may prove
to be more of a problem than a solution and may need to be
removed.
*/
const closeSyncHandleNoThrow = async (fh)=>{
*/
const closeSyncHandleNoThrow = async (fh)=>{
try{await closeSyncHandle(fh)}
catch(e){
warn("closeSyncHandleNoThrow() ignoring:",e,fh);
}
};
};
/* Release all auto-locks. */
const releaseImplicitLocks = async ()=>{
/* Release all auto-locks. */
const releaseImplicitLocks = async ()=>{
if(__implicitLocks.size){
/* Release all auto-locks. */
for(const fid of __implicitLocks){
@ -215,30 +217,30 @@ const releaseImplicitLocks = async ()=>{
log("Auto-unlocked",fid,fh.filenameAbs);
}
}
};
};
/**
/**
An experiment in improving concurrency by freeing up implicit locks
sooner. This is known to impact performance dramatically but it has
also shown to improve concurrency considerably.
If fh.releaseImplicitLocks is truthy and fh is in __implicitLocks,
this routine returns closeSyncHandleNoThrow(), else it is a no-op.
*/
const releaseImplicitLock = async (fh)=>{
*/
const releaseImplicitLock = async (fh)=>{
if(fh.releaseImplicitLocks && __implicitLocks.has(fh.fid)){
return closeSyncHandleNoThrow(fh);
}
};
};
/**
/**
An error class specifically for use with getSyncHandle(), the goal
of which is to eventually be able to distinguish unambiguously
between locking-related failures and other types, noting that we
cannot currently do so because createSyncAccessHandle() does not
define its exceptions in the required level of detail.
*/
class GetSyncHandleError extends Error {
*/
class GetSyncHandleError extends Error {
constructor(errorObject, ...msg){
super();
this.error = errorObject;
@ -248,8 +250,8 @@ class GetSyncHandleError extends Error {
].join(' ');
this.name = 'GetSyncHandleError';
}
};
GetSyncHandleError.convertRc = (e,rc)=>{
};
GetSyncHandleError.convertRc = (e,rc)=>{
if(0){
/* This approach makes the very wild assumption that such a
failure _is_ a locking error. In practice that appears to be
@ -264,8 +266,8 @@ GetSyncHandleError.convertRc = (e,rc)=>{
}else{
return rc;
}
}
/**
}
/**
Returns the sync access handle associated with the given file
handle object (which must be a valid handle object, as created by
xOpen()), lazily opening it if needed.
@ -275,8 +277,8 @@ GetSyncHandleError.convertRc = (e,rc)=>{
will wait briefly and try again, up to 3 times. If acquisition
still fails at that point it will give up and propagate the
exception.
*/
const getSyncHandle = async (fh,opName)=>{
*/
const getSyncHandle = async (fh,opName)=>{
if(!fh.syncHandle){
const t = performance.now();
log("Acquiring sync handle for",fh.filenameAbs);
@ -310,25 +312,25 @@ const getSyncHandle = async (fh,opName)=>{
}
}
return fh.syncHandle;
};
};
/**
/**
Stores the given value at state.sabOPView[state.opIds.rc] and then
Atomics.notify()'s it.
*/
const storeAndNotify = (opName, value)=>{
*/
const storeAndNotify = (opName, value)=>{
log(opName+"() => notify(",value,")");
Atomics.store(state.sabOPView, state.opIds.rc, value);
Atomics.notify(state.sabOPView, state.opIds.rc);
};
};
/**
/**
Throws if fh is a file-holding object which is flagged as read-only.
*/
const affirmNotRO = function(opName,fh){
*/
const affirmNotRO = function(opName,fh){
if(fh.readOnly) toss(opName+"(): File is read-only: "+fh.filenameAbs);
};
const affirmLocked = function(opName,fh){
};
const affirmLocked = function(opName,fh){
//if(!fh.syncHandle) toss(opName+"(): File does not have a lock: "+fh.filenameAbs);
/**
Currently a no-op, as speedtest1 triggers xRead() without a
@ -337,54 +339,54 @@ const affirmLocked = function(opName,fh){
acquisition of a lock but never let it go until xUnlock() is
called (which it likely won't be if xLock() was not called).
*/
};
};
/**
/**
We track 2 different timers: the "metrics" timer records how much
time we spend performing work. The "wait" timer records how much
time we spend waiting on the underlying OPFS timer. See the calls
to mTimeStart(), mTimeEnd(), wTimeStart(), and wTimeEnd()
throughout this file to see how they're used.
*/
const __mTimer = Object.create(null);
__mTimer.op = undefined;
__mTimer.start = undefined;
const mTimeStart = (op)=>{
*/
const __mTimer = Object.create(null);
__mTimer.op = undefined;
__mTimer.start = undefined;
const mTimeStart = (op)=>{
__mTimer.start = performance.now();
__mTimer.op = op;
//metrics[op] || toss("Maintenance required: missing metrics for",op);
++metrics[op].count;
};
const mTimeEnd = ()=>(
};
const mTimeEnd = ()=>(
metrics[__mTimer.op].time += performance.now() - __mTimer.start
);
const __wTimer = Object.create(null);
__wTimer.op = undefined;
__wTimer.start = undefined;
const wTimeStart = (op)=>{
);
const __wTimer = Object.create(null);
__wTimer.op = undefined;
__wTimer.start = undefined;
const wTimeStart = (op)=>{
__wTimer.start = performance.now();
__wTimer.op = op;
//metrics[op] || toss("Maintenance required: missing metrics for",op);
};
const wTimeEnd = ()=>(
};
const wTimeEnd = ()=>(
metrics[__wTimer.op].wait += performance.now() - __wTimer.start
);
);
/**
/**
Gets set to true by the 'opfs-async-shutdown' command to quit the
wait loop. This is only intended for debugging purposes: we cannot
inspect this file's state while the tight waitLoop() is running and
need a way to stop that loop for introspection purposes.
*/
let flagAsyncShutdown = false;
*/
let flagAsyncShutdown = false;
/**
/**
Asynchronous wrappers for sqlite3_vfs and sqlite3_io_methods
methods, as well as helpers like mkdir(). Maintenance reminder:
members are in alphabetical order to simplify finding them.
*/
const vfsAsyncImpls = {
*/
const vfsAsyncImpls = {
'opfs-async-metrics': async ()=>{
mTimeStart('opfs-async-metrics');
metrics.dump();
@ -700,9 +702,9 @@ const vfsAsyncImpls = {
storeAndNotify('xWrite',rc);
mTimeEnd();
}
}/*vfsAsyncImpls*/;
}/*vfsAsyncImpls*/;
const initS11n = ()=>{
const initS11n = ()=>{
/**
ACHTUNG: this code is 100% duplicated in the other half of this
proxy! The documentation is maintained in the "synchronous half".
@ -806,9 +808,9 @@ const initS11n = ()=>{
: ()=>{};
return state.s11n;
}/*initS11n()*/;
}/*initS11n()*/;
const waitLoop = async function f(){
const waitLoop = async function f(){
const opHandlers = Object.create(null);
for(let k of Object.keys(state.opIds)){
const vi = vfsAsyncImpls[k];
@ -848,10 +850,9 @@ const waitLoop = async function f(){
error('in waitLoop():',e);
}
}
};
};
navigator.storage.getDirectory().then(function(d){
const wMsg = (type)=>postMessage({type});
navigator.storage.getDirectory().then(function(d){
state.rootDir = d;
self.onmessage = function({data}){
switch(data.type){
@ -880,7 +881,7 @@ navigator.storage.getDirectory().then(function(d){
initS11n();
metrics.reset();
log("init state",state);
wMsg('opfs-async-inited');
wPost('opfs-async-inited');
waitLoop();
break;
}
@ -896,5 +897,21 @@ navigator.storage.getDirectory().then(function(d){
break;
}
};
wMsg('opfs-async-loaded');
}).catch((e)=>error("error initializing OPFS asyncer:",e));
wPost('opfs-async-loaded');
}).catch((e)=>error("error initializing OPFS asyncer:",e));
}/*installAsyncProxy()*/;
if(!self.SharedArrayBuffer){
wPost('opfs-unavailable', "Missing SharedArrayBuffer API.",
"The server must emit the COOP/COEP response headers to enable that.");
}else if(!self.Atomics){
wPost('opfs-unavailable', "Missing Atomics API.",
"The server must emit the COOP/COEP response headers to enable that.");
}else if(!self.FileSystemHandle ||
!self.FileSystemDirectoryHandle ||
!self.FileSystemFileHandle ||
!self.FileSystemFileHandle.prototype.createSyncAccessHandle ||
!navigator.storage.getDirectory){
wPost('opfs-unavailable',"Missing required OPFS APIs.");
}else{
installAsyncProxy(self);
}

View File

@ -1784,13 +1784,12 @@ self.sqlite3InitModule = sqlite3InitModule;
.t({
name: 'OPFS sanity checks',
test: async function(sqlite3){
const opfs = sqlite3.opfs;
const filename = 'sqlite3-tester1.db';
const pVfs = capi.sqlite3_vfs_find('opfs');
T.assert(pVfs);
const unlink = (fn=filename)=>wasm.sqlite3_wasm_vfs_unlink(pVfs,fn);
unlink();
let db = new opfs.OpfsDb(filename);
let db = new sqlite3.oo1.OpfsDb(filename);
try {
db.exec([
'create table p(a);',
@ -1798,7 +1797,7 @@ self.sqlite3InitModule = sqlite3InitModule;
]);
T.assert(3 === db.selectValue('select count(*) from p'));
db.close();
db = new opfs.OpfsDb(filename);
db = new sqlite3.oo1.OpfsDb(filename);
db.exec('insert into p(a) values(4),(5),(6)');
T.assert(6 === db.selectValue('select count(*) from p'));
}finally{
@ -1806,8 +1805,9 @@ self.sqlite3InitModule = sqlite3InitModule;
unlink();
}
if(1){
if(sqlite3.opfs){
// Sanity-test sqlite3_wasm_vfs_create_file()...
const opfs = sqlite3.opfs;
const fSize = 1379;
let sh;
try{
@ -1824,7 +1824,6 @@ self.sqlite3InitModule = sqlite3InitModule;
if(sh) await sh.close();
unlink();
}
}
// Some sanity checks of the opfs utility functions...
const testDir = '/sqlite3-opfs-'+opfs.randomFilename(12);
@ -1839,6 +1838,7 @@ self.sqlite3InitModule = sqlite3InitModule;
.assert(!(await opfs.entryExists(testDir)),
"entryExists(",testDir,") should have failed");
}
}
}/*OPFS sanity checks*/)
;/* end OPFS tests */

View File

@ -33,9 +33,10 @@
with <code>unlock-asap=0-1</code>.
</p>
<p>Achtung: if it does not start to do anything within a couple of
seconds, check the dev console: Chrome often fails with "cannot allocate
WasmMemory" at startup. Closing and re-opening the tab usually resolves
it.
seconds, check the dev console: Chrome sometimes fails to load
the wasm module due to "cannot allocate WasmMemory." Closing and
re-opening the tab usually resolves it, but sometimes restarting
the browser is required.
</p>
<div class='input-wrapper'>
<input type='checkbox' id='cb-log-reverse'>

View File

@ -1,5 +1,5 @@
C Add\san\sexplicit\swarning\sabout\sthe\scurrent\sAPI-instability\sof\sthe\ssqlite3.opfs\snamespace,\swhich\smay\sneed\sto\sbe\seliminated\sbased\son\sre-thinking\sof\show\sthe\sOPFS\ssqlite3_vfs\sis\sregistered.\sComment\schanges\sonly\s-\sno\scode.
D 2022-11-29T02:23:12.943
C Internal\srestructuring\sof\sthe\sOPFS\ssqlite3_vfs\sin\sorder\sto\sfacilitate\scertain\sexperimentation\sand\simprove\serror\sreporting/hints\sif\sit\scannot\sbe\sactivated.\sDeprecate\sthe\sname\ssqlite3.opfs.OpfsDb,\spreferring\ssqlite3.oo1.OpfsDb\sfor\sconsistency\swith\sJsStorageDb\sand\sany\sfuture\sDB\ssubclasses.
D 2022-11-29T05:25:08.036
F .fossil-settings/empty-dirs dbb81e8fc0401ac46a1491ab34a7f2c7c0452f2f06b54ebb845d024ca8283ef1
F .fossil-settings/ignore-glob 35175cdfcf539b2318cb04a9901442804be81cd677d8b889fcc9149c21f239ea
F LICENSE.md df5091916dbb40e6e9686186587125e1b2ff51f022cc334e886c19a0e9982724
@ -505,11 +505,11 @@ F ext/wasm/api/pre-js.js b88499dc303c21fc3f55f2c364a0f814f587b60a95784303881169f
F ext/wasm/api/sqlite3-api-cleanup.js ecdc69dbfccfe26146f04799fcfd4a6f5790d46e7e3b9b6e9b0491f92ed8ae34
F ext/wasm/api/sqlite3-api-glue.js 056f44b82c126358a0175e08a892d56fadfce177b0d7a0012502a6acf67ea6d5
F ext/wasm/api/sqlite3-api-oo1.js 06ad2079368e16cb9f182c18cd37bdc3932536856dff4f60582d0ca5f6c491a8
F ext/wasm/api/sqlite3-api-opfs.js 3cdae7e98c500f89f9468a260e2a0e1b528c845a107bf72d368e5222769214d3
F ext/wasm/api/sqlite3-api-opfs.js 583650ffdc1452496df6b9459d018fa2aede221ae6ea0cbbbe83bd2e1bdba966
F ext/wasm/api/sqlite3-api-prologue.js 7fce4c6a138ec3d7c285b7c125cee809e6b668d2cb0d2328a1b790b7037765bd
F ext/wasm/api/sqlite3-api-worker1.js e94ba98e44afccfa482874cd9acb325883ade50ed1f9f9526beb9de1711f182f
F ext/wasm/api/sqlite3-license-version-header.js a661182fc93fc2cf212dfd0b987f8e138a3ac98f850b1112e29b5fbdaecc87c3
F ext/wasm/api/sqlite3-opfs-async-proxy.js 798383f6b46fd5dac122d6e35962d25b10401ddb825b5c66df1d21e6b1d8aacc
F ext/wasm/api/sqlite3-opfs-async-proxy.js b5dd7eda8e74e07453457925a0dd793d7785da720954e0e37e847c5c6e4d9526
F ext/wasm/api/sqlite3-wasi.h 25356084cfe0d40458a902afb465df8c21fc4152c1d0a59b563a3fba59a068f9
F ext/wasm/api/sqlite3-wasm.c 8b32787a3b6bb2990cbaba2304bd5b75a9652acbc8d29909b3279019b6cbaef5
F ext/wasm/api/sqlite3-worker1-promiser.js 0c7a9826dbf82a5ed4e4f7bf7816e825a52aff253afbf3350431f5773faf0e4b
@ -554,8 +554,8 @@ F ext/wasm/test-opfs-vfs.html 1f2d672f3f3fce810dfd48a8d56914aba22e45c6834e262555
F ext/wasm/test-opfs-vfs.js 44363db07b2a20e73b0eb1808de4400ca71b703af718d0fa6d962f15e73bf2ac
F ext/wasm/tester1-worker.html 5ef353348c37cf2e4fd0b23da562d3275523e036260b510734e9a3239ba8c987
F ext/wasm/tester1.c-pp.html 74aa9b31c75f12490653f814b53c3dd39f40cd3f70d6a53a716f4e8587107399
F ext/wasm/tester1.c-pp.js 3b91f192c159088004fba6fe3441edea58421a8b88bccf3dd20978a077648d19
F ext/wasm/tests/opfs/concurrency/index.html e8fec75ea6eddc600c8a382da7ea2579feece2263a2fb4417f2cf3e9d451744c
F ext/wasm/tester1.c-pp.js a4b6a165aafcd3b86118efaec6b47c70fbb6c64b5ab86d21ca8c250d42617dfa
F ext/wasm/tests/opfs/concurrency/index.html 2b1cda51d6c786102875a28eba22f0da3eecb732a5e677b0d1ecdb53546d1a62
F ext/wasm/tests/opfs/concurrency/test.js bfc3d7e27b207f0827f12568986b8d516a744529550b449314f5c21c9e9faf4a
F ext/wasm/tests/opfs/concurrency/worker.js 0eff027cbd3a495acb2ac94f57ca9e4d21125ab9fda07d45f3701b0efe82d450
F ext/wasm/version-info.c 3b36468a90faf1bbd59c65fd0eb66522d9f941eedd364fabccd72273503ae7d5
@ -2064,8 +2064,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 46cdd3637d6a206ad2bcf8653cc6f2c7a886a16cc7685c45967938609941a755
R 3870d04bfd54da096e986662fe29b1c8
P 0cb2fd14179397051a25d066256a553fc198656d5668c7010c016f2b8f495bf4
R 115b7898d7b2ce79a9261f36a9b959d1
U stephan
Z ad4a8c5f45a34a10f588c0c6dc455846
Z f4ff31d5e2499971cf67cb62dbdd0ac3
# Remove this line to create a well-formed Fossil manifest.

View File

@ -1 +1 @@
0cb2fd14179397051a25d066256a553fc198656d5668c7010c016f2b8f495bf4
0c5c51f4fb04a4b90c50ec9704cfea9a3fb7d7d0ee55c1b0d4476129188217a6