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

Minor cleanups and doc improvements in the OPFS sqlite3_vfs proxy.

FossilOrigin-Name: 48645f7bcacf81c4149f26d20ee1752fbe93a02f96b85bd7e28bfa49322137e5
This commit is contained in:
stephan
2022-10-25 08:06:17 +00:00
parent 7704a535d0
commit f861b36bf4
5 changed files with 132 additions and 118 deletions

View File

@ -1,6 +1,9 @@
/* extern-post-js.js must be appended to the resulting sqlite3.js
file. It gets its name from being used as the value for
the --extern-post-js=... Emscripten flag. */
file. It gets its name from being used as the value for the
--extern-post-js=... Emscripten flag. Note that this code, unlike
most of the associated JS code, runs outside of the
Emscripten-generated module init scope, in the current
global scope. */
(function(){
/**
In order to hide the sqlite3InitModule()'s resulting Emscripten

View File

@ -15,14 +15,17 @@
asynchronous Origin-Private FileSystem (OPFS) APIs using a second
Worker, implemented in sqlite3-opfs-async-proxy.js. This file is
intended to be appended to the main sqlite3 JS deliverable somewhere
after sqlite3-api-glue.js and before sqlite3-api-cleanup.js.
after sqlite3-api-oo1.js and before sqlite3-api-cleanup.js.
*/
'use strict';
self.sqlite3ApiBootstrap.initializers.push(function(sqlite3){
/**
installOpfsVfs() returns a Promise which, on success, installs
an sqlite3_vfs named "opfs", suitable for use with all sqlite3 APIs
which accept a VFS. It uses the Origin-Private FileSystem API for
installOpfsVfs() returns a Promise which, on success, installs an
sqlite3_vfs named "opfs", suitable for use with all sqlite3 APIs
which accept a VFS. It is intended to be called via
sqlite3ApiBootstrap.initializersAsync or an equivalent mechanism.
The installed VFS uses the Origin-Private FileSystem API for
all file storage. On error it is rejected with an exception
explaining the problem. Reasons for rejection include, but are
not limited to:
@ -48,31 +51,31 @@ self.sqlite3ApiBootstrap.initializers.push(function(sqlite3){
proxying OPFS's synchronous API via the synchronous interface
required by the sqlite3_vfs API.
- This function may only be called a single time and it must be
called from the client, as opposed to the library initialization,
in case the client requires a custom path for this API's
"counterpart": this function's argument is the relative URI to
this module's "asynchronous half". When called, this function removes
itself from the sqlite3 object.
- This function may only be called a single time. When called, this
function removes itself from the sqlite3 object.
The argument may optionally be a plain object with the following
configuration options:
All arguments to this function are for internal/development purposes
only. They do not constitute a public API and may change at any
time.
- proxyUri: as described above
The argument may optionally be a plain object with the following
configuration options:
- verbose (=2): an integer 0-3. 0 disables all logging, 1 enables
logging of errors. 2 enables logging of warnings and errors. 3
additionally enables debugging info.
- proxyUri: as described above
- sanityChecks (=false): if true, some basic sanity tests are
run on the OPFS VFS API after it's initialized, before the
returned Promise resolves.
- verbose (=2): an integer 0-3. 0 disables all logging, 1 enables
logging of errors. 2 enables logging of warnings and errors. 3
additionally enables debugging info.
On success, the Promise resolves to the top-most sqlite3 namespace
object and that object gets a new object installed in its
`opfs` property, containing several OPFS-specific utilities.
- sanityChecks (=false): if true, some basic sanity tests are
run on the OPFS VFS API after it's initialized, before the
returned Promise resolves.
On success, the Promise resolves to the top-most sqlite3 namespace
object and that object gets a new object installed in its
`opfs` property, containing several OPFS-specific utilities.
*/
const installOpfsVfs = function callee(asyncProxyUri = callee.defaultProxyUri){
const installOpfsVfs = function callee(options){
if(!self.SharedArrayBuffer ||
!self.Atomics ||
!self.FileSystemHandle ||
@ -84,9 +87,9 @@ const installOpfsVfs = function callee(asyncProxyUri = callee.defaultProxyUri){
new Error("This environment does not have OPFS support.")
);
}
const options = (asyncProxyUri && 'object'===asyncProxyUri) ? asyncProxyUri : {
proxyUri: asyncProxyUri
};
if(!options || 'object'!==typeof options){
options = Object.create(null);
}
const urlParams = new URL(self.location.href).searchParams;
if(undefined===options.verbose){
options.verbose = urlParams.has('opfs-verbose') ? 3 : 2;
@ -113,7 +116,6 @@ const installOpfsVfs = function callee(asyncProxyUri = callee.defaultProxyUri){
const log = (...args)=>logImpl(2, ...args);
const warn = (...args)=>logImpl(1, ...args);
const error = (...args)=>logImpl(0, ...args);
//warn("The OPFS VFS feature is very much experimental and under construction.");
const toss = function(...args){throw new Error(args.join(' '))};
const capi = sqlite3.capi;
const wasm = capi.wasm;
@ -158,9 +160,6 @@ const installOpfsVfs = function callee(asyncProxyUri = callee.defaultProxyUri){
s.count = s.time = 0;
s = metrics.s11n.deserialize = Object.create(null);
s.count = s.time = 0;
//[ // timed routines which are not in state.opIds
// 'xFileControl'
//].forEach((k)=>r(metrics[k] = Object.create(null)));
}
}/*metrics*/;
const promiseReject = function(err){
@ -223,32 +222,36 @@ const installOpfsVfs = function callee(asyncProxyUri = callee.defaultProxyUri){
cases. We should probably have one SAB here with a single slot
for locking a per-file initialization step and then allocate a
separate SAB like the above one for each file. That will
require a bit of acrobatics but should be feasible.
require a bit of acrobatics but should be feasible. The most
problematic part is that xOpen() would have to use
postMessage() to communicate its SharedArrayBuffer, and mixing
that approach with Atomics.wait/notify() gets a bit messy.
*/
const state = Object.create(null);
state.verbose = options.verbose;
state.littleEndian = (()=>{
const buffer = new ArrayBuffer(2);
new DataView(buffer).setInt16(0, 256, true /* littleEndian */);
new DataView(buffer).setInt16(0, 256, true /* ==>littleEndian */);
// Int16Array uses the platform's endianness.
return new Int16Array(buffer)[0] === 256;
})();
/** Whether the async counterpart should log exceptions to
the serialization channel. That produces a great deal of
noise for seemingly innocuous things like xAccess() checks
for missing files, so this option may have one of 3 values:
/**
Whether the async counterpart should log exceptions to
the serialization channel. That produces a great deal of
noise for seemingly innocuous things like xAccess() checks
for missing files, so this option may have one of 3 values:
0 = no exception logging
0 = no exception logging
1 = only log exceptions for "significant" ops like xOpen(),
xRead(), and xWrite().
1 = only log exceptions for "significant" ops like xOpen(),
xRead(), and xWrite().
2 = log all exceptions.
2 = log all exceptions.
*/
state.asyncS11nExceptions = 1;
/* Size of file I/O buffer block. 64k = max sqlite3 page size. */
state.fileBufferSize =
1024 * 64;
/* Size of file I/O buffer block. 64k = max sqlite3 page size, and
xRead/xWrite() will never deal in blocks larger than that. */
state.fileBufferSize = 1024 * 64;
state.sabS11nOffset = state.fileBufferSize;
/**
The size of the block in our SAB for serializing arguments and
@ -258,7 +261,8 @@ const installOpfsVfs = function callee(asyncProxyUri = callee.defaultProxyUri){
*/
state.sabS11nSize = opfsVfs.$mxPathname * 2;
/**
The SAB used for all data I/O (files and arg/result s11n).
The SAB used for all data I/O between the synchronous and
async halves (file i/o and arg/result s11n).
*/
state.sabIO = new SharedArrayBuffer(
state.fileBufferSize/* file i/o block */
@ -297,7 +301,13 @@ const installOpfsVfs = function callee(asyncProxyUri = callee.defaultProxyUri){
state.opIds.mkdir = i++;
state.opIds['opfs-async-metrics'] = i++;
state.opIds['opfs-async-shutdown'] = i++;
state.sabOP = new SharedArrayBuffer(i * 4/*sizeof int32*/);
/* The retry slot is used by the async part for wait-and-retry
semantics. Though we could hypothetically use the xSleep slot
for that, doing so might lead to undesired side effects. */
state.opIds.retry = i++;
state.sabOP = new SharedArrayBuffer(
i * 4/* ==sizeof int32, noting that Atomics.wait() and friends
can only function on Int32Array views of an SAB. */);
opfsUtil.metrics.reset();
}
/**
@ -338,9 +348,12 @@ const installOpfsVfs = function callee(asyncProxyUri = callee.defaultProxyUri){
state.s11n.serialize(...args);
Atomics.store(state.sabOPView, state.opIds.rc, -1);
Atomics.store(state.sabOPView, state.opIds.whichOp, opNdx);
Atomics.notify(state.sabOPView, state.opIds.whichOp) /* async thread will take over here */;
Atomics.notify(state.sabOPView, state.opIds.whichOp)
/* async thread will take over here */;
const t = performance.now();
Atomics.wait(state.sabOPView, state.opIds.rc, -1);
Atomics.wait(state.sabOPView, state.opIds.rc, -1)
/* When this wait() call returns, the async half will have
completed the operation and reported its results. */;
const rc = Atomics.load(state.sabOPView, state.opIds.rc);
metrics[op].wait += performance.now() - t;
if(rc && state.asyncS11nExceptions){
@ -352,17 +365,22 @@ const installOpfsVfs = function callee(asyncProxyUri = callee.defaultProxyUri){
const initS11n = ()=>{
/**
ACHTUNG: this code is 100% duplicated in the other half of this
proxy! The documentation is maintained in the "synchronous half".
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
ACHTUNG: this code is 100% duplicated in the other half of
this proxy! The documentation is maintained in the
"synchronous half".
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
This proxy de/serializes cross-thread function arguments and
output-pointer values via the state.sabIO SharedArrayBuffer,
using the region defined by (state.sabS11nOffset,
state.sabS11nOffset]. Only one dataset is recorded at a time.
This is not a general-purpose format. It only supports the range
of operations, and data sizes, needed by the sqlite3_vfs and
sqlite3_io_methods operations.
This is not a general-purpose format. It only supports the
range of operations, and data sizes, needed by the
sqlite3_vfs and sqlite3_io_methods operations. Serialized
data are transient and this serialization algorithm may
change at any time.
The data format can be succinctly summarized as:
@ -386,7 +404,8 @@ const installOpfsVfs = function callee(asyncProxyUri = callee.defaultProxyUri){
using their TextEncoder/TextDecoder representations. It would
arguably make more sense to store them as Int16Arrays of
their JS character values, but how best/fastest to get that
in and out of string form us an open point.
in and out of string form is an open point. Initial
experimentation with that approach did not gain us any speed.
Historical note: this impl was initially about 1% this size by
using using JSON.stringify/parse(), but using fit-to-purpose
@ -583,9 +602,11 @@ const installOpfsVfs = function callee(asyncProxyUri = callee.defaultProxyUri){
toss("Member",name," is not a function pointer. Signature =",sigN);
}
const memKey = tgt.memberKey(name);
//log("installMethod",tgt, name, sigN);
const fProxy = 0
// We can remove this proxy middle-man once the VFS is working
/** This middle-man proxy is only for use during development, to
confirm that we always pass the proper number of
arguments. We know that the C-level code will always use the
correct argument count. */
? callee.argcProxy(func, sigN)
: func;
const pFunc = wasm.installFunction(fProxy, tgt.memberSignature(name, true));
@ -606,7 +627,6 @@ const installOpfsVfs = function callee(asyncProxyUri = callee.defaultProxyUri){
const mTimeStart = (op)=>{
opTimer.start = performance.now();
opTimer.op = op;
//metrics[op] || toss("Maintenance required: missing metrics for",op);
++metrics[op].count;
};
const mTimeEnd = ()=>(
@ -619,8 +639,15 @@ const installOpfsVfs = function callee(asyncProxyUri = callee.defaultProxyUri){
*/
const ioSyncWrappers = {
xCheckReservedLock: function(pFile,pOut){
// Exclusive lock is automatically acquired when opened
//warn("xCheckReservedLock(",arguments,") is a no-op");
/**
As of late 2022, only a single lock can be held on an OPFS
file. We have no way of checking whether any _other_ db
connection has a lock except by trying to obtain and (on
success) release a sync-handle for it, but doing so would
involve an inherent race condition. For the time being,
pending a better solution, we simply report whether the
given pFile instance has a lock.
*/
const f = __openFiles[pFile];
wasm.setMemValue(pOut, f.lockMode ? 1 : 0, 'i32');
return 0;
@ -673,14 +700,17 @@ const installOpfsVfs = function callee(asyncProxyUri = callee.defaultProxyUri){
return rc;
},
xRead: function(pFile,pDest,n,offset64){
/* int (*xRead)(sqlite3_file*, void*, int iAmt, sqlite3_int64 iOfst) */
mTimeStart('xRead');
const f = __openFiles[pFile];
let rc;
try {
rc = opRun('xRead',pFile, n, Number(offset64));
if(0===rc || capi.SQLITE_IOERR_SHORT_READ===rc){
// set() seems to be the fastest way to copy this...
if(0===rc || capi.SQLITE_IOERR_SHORT_READ===rc){
/**
Results get written to the SharedArrayBuffer f.sabView.
Because the heap is _not_ a SharedArrayBuffer, we have
to copy the results. TypedArray.set() seems to be the
fastest way to copy this. */
wasm.heap8u().set(f.sabView.subarray(0, n), pDest);
}
}catch(e){
@ -713,7 +743,6 @@ const installOpfsVfs = function callee(asyncProxyUri = callee.defaultProxyUri){
return rc;
},
xWrite: function(pFile,pSrc,n,offset64){
/* int (*xWrite)(sqlite3_file*, const void*, int iAmt, sqlite3_int64 iOfst) */
mTimeStart('xWrite');
const f = __openFiles[pFile];
let rc;
@ -779,28 +808,6 @@ const installOpfsVfs = function callee(asyncProxyUri = callee.defaultProxyUri){
//xSleep is optionally defined below
xOpen: function f(pVfs, zName, pFile, flags, pOutFlags){
mTimeStart('xOpen');
if(!f._){
f._ = {
fileTypes: {
SQLITE_OPEN_MAIN_DB: 'mainDb',
SQLITE_OPEN_MAIN_JOURNAL: 'mainJournal',
SQLITE_OPEN_TEMP_DB: 'tempDb',
SQLITE_OPEN_TEMP_JOURNAL: 'tempJournal',
SQLITE_OPEN_TRANSIENT_DB: 'transientDb',
SQLITE_OPEN_SUBJOURNAL: 'subjournal',
SQLITE_OPEN_SUPER_JOURNAL: 'superJournal',
SQLITE_OPEN_WAL: 'wal'
},
getFileType: function(filename,oflags){
const ft = f._.fileTypes;
for(let k of Object.keys(ft)){
if(oflags & capi[k]) return ft[k];
}
warn("Cannot determine fileType based on xOpen() flags for file",filename);
return '???';
}
};
}
if(0===zName){
zName = randomFilename();
}else if('number'===typeof zName){
@ -1084,16 +1091,13 @@ const installOpfsVfs = function callee(asyncProxyUri = callee.defaultProxyUri){
promiseReject(e);
error("Unexpected message from the async worker:",data);
break;
}
};
}/*switch(data.type)*/
}/*W.onmessage()*/;
})/*thePromise*/;
return thePromise;
}/*installOpfsVfs()*/;
installOpfsVfs.defaultProxyUri =
//self.location.pathname.replace(/[^/]*$/, "sqlite3-opfs-async-proxy.js");
"sqlite3-opfs-async-proxy.js";
//console.warn("sqlite3.installOpfsVfs.defaultProxyUri =",sqlite3.installOpfsVfs.defaultProxyUri);
self.sqlite3ApiBootstrap.initializersAsync.push(async (sqlite3)=>{
if(sqlite3.scriptInfo && !sqlite3.scriptInfo.isWorker){
return;

View File

@ -10,7 +10,7 @@
***********************************************************************
An Worker which manages asynchronous OPFS handles on behalf of a
A Worker which manages asynchronous OPFS handles on behalf of a
synchronous API which controls it via a combination of Worker
messages, SharedArrayBuffer, and Atomics. It is the asynchronous
counterpart of the API defined in sqlite3-api-opfs.js.
@ -25,8 +25,12 @@
access to the sqlite3 JS/WASM bits, so any bits which it needs (most
notably SQLITE_xxx integer codes) have to be imported into it via an
initialization process.
This file represents an implementation detail of a larger piece of
code, and not a public interface. Its details may change at any time
and are not intended to be used by any client-level code.
*/
'use strict';
"use strict";
const toss = function(...args){throw new Error(args.join(' '))};
if(self.window === self){
toss("This code cannot run from the main thread.",
@ -133,8 +137,8 @@ const getDirForFilename = async function f(absFilename, createDirs = false){
/**
Returns the sync access handle associated with the given file
handle object (which must be a valid handle object), lazily opening
it if needed.
handle object (which must be a valid handle object, as created by
xOpen()), lazily opening it if needed.
In order to help alleviate cross-tab contention for a dabase,
if an exception is thrown while acquiring the handle, this routine
@ -150,7 +154,7 @@ const getSyncHandle = async (fh)=>{
let i = 1, ms = 300;
for(; true; ms *= ++i){
try {
//if(1===i) toss("Just testing.");
//if(i<3) toss("Just testing.");
//TODO? A config option which tells it to throw here
//randomly every now and then, for testing purposes.
fh.syncHandle = await fh.fileHandle.createSyncAccessHandle();
@ -163,7 +167,7 @@ const getSyncHandle = async (fh)=>{
}
warn("Error getting sync handle. Waiting",ms,
"ms and trying again.",fh.filenameAbs,e);
Atomics.wait(state.sabOPView, state.opIds.xSleep, 0, ms);
Atomics.wait(state.sabOPView, state.opIds.retry, 0, ms);
}
}
log("Got sync handle for",fh.filenameAbs,'in',performance.now() - t,'ms');
@ -305,7 +309,7 @@ const vfsAsyncImpls = {
storeAndNotify('xAccess', rc);
mTimeEnd();
},
xClose: async function(fid){
xClose: async function(fid/*sqlite3_file pointer*/){
const opName = 'xClose';
mTimeStart(opName);
const fh = __openFiles[fid];
@ -364,7 +368,7 @@ const vfsAsyncImpls = {
wTimeEnd();
return rc;
},
xFileSize: async function(fid){
xFileSize: async function(fid/*sqlite3_file pointer*/){
mTimeStart('xFileSize');
const fh = __openFiles[fid];
let sz;
@ -381,7 +385,8 @@ const vfsAsyncImpls = {
storeAndNotify('xFileSize', sz);
mTimeEnd();
},
xLock: async function(fid,lockType){
xLock: async function(fid/*sqlite3_file pointer*/,
lockType/*SQLITE_LOCK_...*/){
mTimeStart('xLock');
const fh = __openFiles[fid];
let rc = 0;
@ -397,7 +402,8 @@ const vfsAsyncImpls = {
storeAndNotify('xLock',rc);
mTimeEnd();
},
xOpen: async function(fid/*sqlite3_file pointer*/, filename, flags){
xOpen: async function(fid/*sqlite3_file pointer*/, filename,
flags/*SQLITE_OPEN_...*/){
const opName = 'xOpen';
mTimeStart(opName);
const deleteOnClose = (state.sq3Codes.SQLITE_OPEN_DELETEONCLOSE & flags);
@ -440,7 +446,7 @@ const vfsAsyncImpls = {
}
mTimeEnd();
},
xRead: async function(fid,n,offset){
xRead: async function(fid/*sqlite3_file pointer*/,n,offset64){
mTimeStart('xRead');
let rc = 0, nRead;
const fh = __openFiles[fid];
@ -448,7 +454,7 @@ const vfsAsyncImpls = {
wTimeStart('xRead');
nRead = (await getSyncHandle(fh)).read(
fh.sabView.subarray(0, n),
{at: Number(offset)}
{at: Number(offset64)}
);
wTimeEnd();
if(nRead < n){/* Zero-fill remaining bytes */
@ -464,7 +470,7 @@ const vfsAsyncImpls = {
storeAndNotify('xRead',rc);
mTimeEnd();
},
xSync: async function(fid,flags/*ignored*/){
xSync: async function(fid/*sqlite3_file pointer*/,flags/*ignored*/){
mTimeStart('xSync');
const fh = __openFiles[fid];
let rc = 0;
@ -480,7 +486,7 @@ const vfsAsyncImpls = {
storeAndNotify('xSync',rc);
mTimeEnd();
},
xTruncate: async function(fid,size){
xTruncate: async function(fid/*sqlite3_file pointer*/,size){
mTimeStart('xTruncate');
let rc = 0;
const fh = __openFiles[fid];
@ -497,7 +503,8 @@ const vfsAsyncImpls = {
storeAndNotify('xTruncate',rc);
mTimeEnd();
},
xUnlock: async function(fid,lockType){
xUnlock: async function(fid/*sqlite3_file pointer*/,
lockType/*SQLITE_LOCK_...*/){
mTimeStart('xUnlock');
let rc = 0;
const fh = __openFiles[fid];
@ -514,7 +521,7 @@ const vfsAsyncImpls = {
storeAndNotify('xUnlock',rc);
mTimeEnd();
},
xWrite: async function(fid,n,offset){
xWrite: async function(fid/*sqlite3_file pointer*/,n,offset64){
mTimeStart('xWrite');
let rc;
wTimeStart('xWrite');
@ -524,7 +531,7 @@ const vfsAsyncImpls = {
rc = (
n === (await getSyncHandle(fh))
.write(fh.sabView.subarray(0, n),
{at: Number(offset)})
{at: Number(offset64)})
) ? 0 : state.sq3Codes.SQLITE_IOERR_WRITE;
}catch(e){
error("xWrite():",e,fh);

View File

@ -1,5 +1,5 @@
C Fix\stypo\sin\scomment.
D 2022-10-24T18:42:45.118
C Minor\scleanups\sand\sdoc\simprovements\sin\sthe\sOPFS\ssqlite3_vfs\sproxy.
D 2022-10-25T08:06:17.330
F .fossil-settings/empty-dirs dbb81e8fc0401ac46a1491ab34a7f2c7c0452f2f06b54ebb845d024ca8283ef1
F .fossil-settings/ignore-glob 35175cdfcf539b2318cb04a9901442804be81cd677d8b889fcc9149c21f239ea
F LICENSE.md df5091916dbb40e6e9686186587125e1b2ff51f022cc334e886c19a0e9982724
@ -478,7 +478,7 @@ F ext/wasm/README.md 1e5b28158b74ab3ffc9d54fcbc020f0bbeb82c2ff8bbd904214c86c70e8
F ext/wasm/api/EXPORTED_FUNCTIONS.sqlite3-api 36f413ab4dbb057d2dec938fb366ac0a4c5e85ba14660a8d672f0277602c0fc5
F ext/wasm/api/EXPORTED_RUNTIME_METHODS.sqlite3-api 1ec3c73e7d66e95529c3c64ac3de2470b0e9e7fbf7a5b41261c367cf4f1b7287
F ext/wasm/api/README.md 1350088aee90e959ad9a94fab1bb6bcb5e99d4d27f976db389050f54f2640c78
F ext/wasm/api/extern-post-js.js efbed835f290b3741259acc5faf68714a60d38e6834e6cfe172d5354c87566d2
F ext/wasm/api/extern-post-js.js 926d192b72fa808378e5e7843721dc7ba3908c163a0260e06d8aa501c12f5469
F ext/wasm/api/extern-pre-js.js cc61c09c7a24a07dbecb4c352453c3985170cec12b4e7e7e7a4d11d43c5c8f41
F ext/wasm/api/post-js-footer.js cd0a8ec768501d9bd45d325ab0442037fb0e33d1f3b4f08902f15c34720ee4a1
F ext/wasm/api/post-js-header.js 2e5c886398013ba2af88028ecbced1e4b22dc96a86467f1ecc5ba9e64ef90a8b
@ -486,11 +486,11 @@ F ext/wasm/api/pre-js.js 151e0616614a49f3db19ed544fa13b38c87c108959fbcd4029ea839
F ext/wasm/api/sqlite3-api-cleanup.js 4d07a7524dc9b7b050acfde57163e839243ad2383bd7ee0de0178b1b3e988588
F ext/wasm/api/sqlite3-api-glue.js 6e4e472eb5afc732a695cd7c5ded6dee6ef8b480e61aa0d648a3fc9033c84745
F ext/wasm/api/sqlite3-api-oo1.js ca41ffe58bfbbefc98181081fba0b7af58afcc2770e963558f4f6a408c583fc0
F ext/wasm/api/sqlite3-api-opfs.js 22d60ba956e873b65e2e0591e239178082bd53a6d563c3c58db7dc03e562e8f7
F ext/wasm/api/sqlite3-api-opfs.js 62da8b7cac30d4e7bb940762d2ac948b0aeb89704a5a290b74eb268ecbd1a64e
F ext/wasm/api/sqlite3-api-prologue.js fa00d55f927e5a4ec51cf2c80f6f0eaed2f4f5774341ecf3d63a0ea4c738f8f5
F ext/wasm/api/sqlite3-api-worker1.js a7f38f03275d6c27ab2aef3e83215d3c97ce09c43e6904df47c3764d9d4572b4
F ext/wasm/api/sqlite3-license-version-header.js a661182fc93fc2cf212dfd0b987f8e138a3ac98f850b1112e29b5fbdaecc87c3
F ext/wasm/api/sqlite3-opfs-async-proxy.js 206ce6bbc3c30ad51a37d9c25e3a2712e70b586e0f9a2cf8cb0b9619017c2671
F ext/wasm/api/sqlite3-opfs-async-proxy.js f04cb1eb483c92bc61fe02749f7afcf17ec803968171aedd7d96faf428c26bcb
F ext/wasm/api/sqlite3-wasi.h 25356084cfe0d40458a902afb465df8c21fc4152c1d0a59b563a3fba59a068f9
F ext/wasm/api/sqlite3-wasm.c 940d576bda7068ba60492e206d06a3567b8a89a3770700aa88690a6e246a0c78
F ext/wasm/api/sqlite3-worker1-promiser.js 0c7a9826dbf82a5ed4e4f7bf7816e825a52aff253afbf3350431f5773faf0e4b
@ -2037,8 +2037,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 69d704224e9ed022fcec591beff2ffcc4daf3e7fc586debfdcf00b25c1fddd87
R e2a88fcf6a9d4446fa629ea861bc8ca4
U drh
Z ea85a8ca90f8837e6e02f1969966b7f9
P f65c95658fe4d30817da8de7eb88e823ea1cd8be40e347d626870bad3cc13359
R 18bf727aa37df31466f2ea66a532842e
U stephan
Z 9cf62ee935cac4cb5c613433da7a1983
# Remove this line to create a well-formed Fossil manifest.

View File

@ -1 +1 @@
f65c95658fe4d30817da8de7eb88e823ea1cd8be40e347d626870bad3cc13359
48645f7bcacf81c4149f26d20ee1752fbe93a02f96b85bd7e28bfa49322137e5