diff --git a/ext/wasm/api/sqlite3-api-oo1.js b/ext/wasm/api/sqlite3-api-oo1.js
index 9efc38b915..0e04b63ebf 100644
--- a/ext/wasm/api/sqlite3-api-oo1.js
+++ b/ext/wasm/api/sqlite3-api-oo1.js
@@ -59,12 +59,16 @@
      enabling clients to unambiguously identify such exceptions.
   */
   class SQLite3Error extends Error {
+    /**
+       Constructs this object with a message equal to all arguments
+       concatenated with a space between each one.
+    */
     constructor(...args){
-      super(...args);
+      super(args.join(' '));
       this.name = 'SQLite3Error';
     }
   };
-  const toss3 = (...args)=>{throw new SQLite3Error(args)};
+  const toss3 = (...args)=>{throw new SQLite3Error(...args)};
   sqlite3.SQLite3Error = SQLite3Error;
 
   /**
@@ -195,7 +199,7 @@
   };
 
   /**
-     Expects to be passed (arguments) from DB.exec() and
+     Expects to be passed the `arguments` object from DB.exec() and
      DB.execMulti(). Does the argument processing/validation, throws
      on error, and returns a new object on success:
 
@@ -842,26 +846,47 @@
 
     /**
        Starts a transaction, calls the given callback, and then either
-       rolls back or commits the transaction, depending on whether the
-       callback throw. The callback is pass this db object as its only
-       argument.  On success, returns the result of the callback.
-       Throws on error.
+       rolls back or commits the savepoint, depending on whether the
+       callback throws. The callback is passed this db object as its
+       only argument. On success, returns the result of the
+       callback. Throws on error.
+
+       Note that transactions may not be nested, so this will throw if
+       it is called recursively. For nested transactions, use the
+       savepoint() method or manually manage SAVEPOINTs using exec().
      */
-    callInTransaction: function(callback){
-      affirmDbOpen(this);
-      let err, rc;
-      this.exec("BEGIN");
-      try { rc = callback(this); }
-      catch(e){
-        err = e;
+    transaction: function(callback){
+      affirmDbOpen(this).exec("BEGIN");
+      try {
+        const rc = callback(this);
+        this.exec("COMMIT");
+        return rc;
+      }catch(e){
+        this.exec("ROLLBACK");
         throw e;
-      }finally{
-        if(err) this.exec("ROLLBACK");
-        else this.exec("COMMIT");
       }
-      return rc;
     },
 
+    /**
+       This works similarly to transaction() but uses sqlite3's SAVEPOINT
+       feature. This function starts a savepoint (with an unspecified name)
+       and calls the given callback function, passing it this db object.
+       If the callback returns, the savepoint is released (committed). If
+       the callback throws, the savepoint is rolled back. If it does not
+       throw, it returns the result of the callback.
+    */
+    savepoint: function(callback){
+      affirmDbOpen(this).exec("SAVEPOINT oo1");
+      try {
+        const rc = callback(this);
+        this.exec("RELEASE oo1");
+        return rc;
+      }catch(e){
+        this.execMulti("ROLLBACK to SAVEPOINT oo1; RELEASE SAVEPOINT oo1");
+        throw e;
+      }
+    },
+    
     /**
        This function currently does nothing and always throws.  It
        WILL BE REMOVED pending other refactoring, to eliminate a hard
diff --git a/ext/wasm/demo-oo1.js b/ext/wasm/demo-oo1.js
index 480ea4938f..1ea7e05c64 100644
--- a/ext/wasm/demo-oo1.js
+++ b/ext/wasm/demo-oo1.js
@@ -26,13 +26,9 @@
           oo = sqlite3.oo1,
           wasm = capi.wasm;
 
-    // If we have persistent storage, maybe init and mount it:
-    const dbDir = true
-          ? "" // this demo works better without persistent storage.
-          : capi.sqlite3_web_persistent_dir();
-            // ^^^ returns name of persistent mount point or "" if we have none
-
+    const dbDir = 1 ? "" : capi.sqlite3_web_persistent_dir();
     const db = new oo.DB(dbDir+"/mydb.sqlite3");
+    log("db =",db.filename);
     /**
        Never(!) rely on garbage collection to clean up DBs and
        (especially) statements. Always wrap their lifetimes in
@@ -164,14 +160,39 @@
       }
 
       try {
-        db.callInTransaction( function(D) {
+        db.transaction( function(D) {
           D.exec("delete from t");
           log("In transaction: count(*) from t =",db.selectValue("select count(*) from t"));
-          throw new sqlite3.SQLite3Error("Demonstrating callInTransaction() rollback");
+          throw new sqlite3.SQLite3Error("Demonstrating transaction() rollback");
         });
       }catch(e){
         if(e instanceof sqlite3.SQLite3Error){
-          log("Got expected exception:",e.message);
+          log("Got expected exception from db.transaction():",e.message);
+          log("count(*) from t =",db.selectValue("select count(*) from t"));
+        }else{
+          throw e;
+        }
+      }
+
+      try {
+        db.savepoint( function(D) {
+          D.exec("delete from t");
+          log("In savepoint: count(*) from t =",db.selectValue("select count(*) from t"));
+          D.savepoint(function(DD){
+            const rows = [];
+            D.execMulti({
+              sql: ["insert into t(a,b) values(99,100);",
+                    "select count(*) from t"],
+              rowMode: 0,
+              resultRows: rows
+            });
+            log("In nested savepoint. Row count =",rows[0]);
+            throw new sqlite3.SQLite3Error("Demonstrating nested savepoint() rollback");
+          })
+        });
+      }catch(e){
+        if(e instanceof sqlite3.SQLite3Error){
+          log("Got expected exception from nested db.savepoint():",e.message);
           log("count(*) from t =",db.selectValue("select count(*) from t"));
         }else{
           throw e;
@@ -205,13 +226,11 @@
   const runDemos = function(Module){
     //log("Module",Module);
     const sqlite3 = Module.sqlite3,
-          capi = sqlite3.capi,
-          oo = sqlite3.oo1,
-          wasm = capi.wasm;
+          capi = sqlite3.capi;
     log("Loaded module:",capi.sqlite3_libversion(), capi.sqlite3_sourceid());
     log("sqlite3 namespace:",sqlite3);
     try {
-      [ demo1 ].forEach((f)=>f(sqlite3, Module))
+      demo1(sqlite3);
     }catch(e){
       error("Exception:",e.message);
       throw e;
diff --git a/manifest b/manifest
index 9601cb65da..c93efa4806 100644
--- a/manifest
+++ b/manifest
@@ -1,5 +1,5 @@
-C Improve\san\sexception\scheck\sin\sdemo-oo1.js.
-D 2022-08-18T11:16:27.283
+C javascript:\srename\sand\ssimplify\sDB.callInTransaction()\sas\sDB.transaction().\sAdd\sDB.savepoint(),\swhich\sworks\sthe\ssame\sas\stransaction()\sbut\suses\ssavepoints.\sCorrect\sconcatenation\sof\sarguments\sto\sSQLite3Error\sto\suse\sspaces\sinstead\sof\scommas.\sTest\sthat\sdemo-oo1.js\sworks\swith\spersistent\sOPFS\sstorage\s(output\sdiffers\sdue\sto\spersistent\srows,\sbut\sthe\sdemo\sworks).
+D 2022-08-18T12:21:58.613
 F .fossil-settings/empty-dirs dbb81e8fc0401ac46a1491ab34a7f2c7c0452f2f06b54ebb845d024ca8283ef1
 F .fossil-settings/ignore-glob 35175cdfcf539b2318cb04a9901442804be81cd677d8b889fcc9149c21f239ea
 F LICENSE.md df5091916dbb40e6e9686186587125e1b2ff51f022cc334e886c19a0e9982724
@@ -483,7 +483,7 @@ 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 149fd63a0400cd1d69548887ffde2ed89c13283384a63c2e9fcfc695e38a9e11
 F ext/wasm/api/sqlite3-api-glue.js 4a09dd1153874f7a716cf953329bc1e78bf24e0870a9aad15214ffd51ac913bc
-F ext/wasm/api/sqlite3-api-oo1.js d68dc7a692bc54385d9b851e914b386a146dc6c86624bfeb4672280a2d822082
+F ext/wasm/api/sqlite3-api-oo1.js 6c14e97987fafdd5f63ffa8a086e5316d49c115743add4dabfacb302a7c76b45
 F ext/wasm/api/sqlite3-api-opfs.js c93cdd14f81a26b3a64990515ee05c7e29827fbc8fba4e4c2fef3a37a984db89
 F ext/wasm/api/sqlite3-api-prologue.js 96997e411b41ff3d4024c2ad625c5cdb7b6a619ba8beece4662ad190191b1119
 F ext/wasm/api/sqlite3-api-worker1.js 74130ec4979baeaf3e909c7619b99e9f466e2d816cd07370987ff639d168ef45
@@ -494,7 +494,7 @@ F ext/wasm/common/emscripten.css 3d253a6fdb8983a2ac983855bfbdd4b6fa1ff267c28d695
 F ext/wasm/common/testing.css 572cf1ffae0b6eb7ca63684d3392bf350217a07b90e7a896e4fa850700c989b0
 F ext/wasm/common/whwasmutil.js 41b8e097e0a9cb07c24c0ede3c81b72470a63f4a4efb07f75586dc131569f5ae
 F ext/wasm/demo-oo1.html 75646855b38405d82781246fd08c852a2b3bee05dd9f0fe10ab655a8cffb79aa
-F ext/wasm/demo-oo1.js 09c2e3f8dab87308d945da51eef4dae206f1e0a4edc6d2207096b5617a64b651
+F ext/wasm/demo-oo1.js 8be9c6be3c8e579eab4e7a5ee720ed122e38275a1f105169c6826193e42cf102
 F ext/wasm/fiddle/emscripten.css 3d253a6fdb8983a2ac983855bfbdd4b6fa1ff267c28d69513dd6ef1f289ada3f
 F ext/wasm/fiddle/fiddle-worker.js bccf46045be8824752876f3eec01c223be0616ccac184bffd0024cfe7a3262b8
 F ext/wasm/fiddle/fiddle.html 550c5aafce40bd218de9bf26192749f69f9b10bc379423ecd2e162bcef885c08
@@ -2006,8 +2006,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 f5059ee6f9fc55a381cbf08a30dfb9a5636c0b44341e42f4e9f12a3b109b5507
-R 26de05b21625601bc9d1934cb0717538
+P 55e1b775fa7ec492817cc847036df7794c3c22b78558e9e9c513f24c775dedab
+R c7dd436c51b79816d21badfd7965a0c6
 U stephan
-Z 631a9c2307319184f6094a1100613815
+Z 56fa2cd60f3999351a57836a87a9c295
 # Remove this line to create a well-formed Fossil manifest.
diff --git a/manifest.uuid b/manifest.uuid
index 9d14975f11..6c09520662 100644
--- a/manifest.uuid
+++ b/manifest.uuid
@@ -1 +1 @@
-55e1b775fa7ec492817cc847036df7794c3c22b78558e9e9c513f24c775dedab
\ No newline at end of file
+e8c323f12b6223bc9dcbbec40698969c7917128aa50cf599c247df23f607ae61
\ No newline at end of file