Merge "use vdl generate directly rather than v23 go run .../vdl/main.go"
diff --git a/extension/manifest.json b/extension/manifest.json
index d181dd3..358153a 100644
--- a/extension/manifest.json
+++ b/extension/manifest.json
@@ -1,6 +1,6 @@
 {
   "name": "Vanadium Extension",
-  "version": "0.1.21",
+  "version": "0.1.22",
   "description": "Access and create Vanadium services from JavaScript.",
   "manifest_version": 2,
   "icons": {
diff --git a/src/rpc/client.js b/src/rpc/client.js
index 7f6fdea..1b524c4 100644
--- a/src/rpc/client.js
+++ b/src/rpc/client.js
@@ -452,7 +452,7 @@
 
       // Callback is the last function argument, pull it out of the args
       var lastType = typeof args[args.length - 1];
-      if (lastType === 'function' || lastType === 'undefined') {
+      if (lastType === 'function') {
         callback = args.pop();
       }
 
@@ -482,7 +482,16 @@
 
       ctx = vtrace.withNewSpan(ctx, '<jsclient>"'+name+'".'+method);
 
+      // If the last value was undefined, and there is 1 too many args, the
+      // undefined is an undefined cb, not an undefined arg.
+      if (args.length === methodSig.inArgs.length + 1 &&
+        lastType === 'undefined') {
+
+        args.pop();
+      }
+
       if (args.length !== methodSig.inArgs.length) {
+
         var expectedArgs = methodSig.inArgs.map(function(arg) {
           return arg.name;
         });
@@ -500,7 +509,7 @@
 
         // The given arguments exclude the ctx and (optional) cb.
         var givenArgs = Array.prototype.slice.call(arguments, 1);
-        if (typeof givenArgs[givenArgs.length - 1] === 'function') {
+        if (lastType === 'function') {
           givenArgs.pop();
         }
         err = new IncorrectArgCount(
diff --git a/test/integration/test-js-client-server.js b/test/integration/test-js-client-server.js
index d909e9c..7866b17 100644
--- a/test/integration/test-js-client-server.js
+++ b/test/integration/test-js-client-server.js
@@ -353,6 +353,63 @@
     });
   });
 
+  test(namePrefix + 'typeService.isTyped(...) - promise', function(t) {
+    setup(options, function(err, ctx, typeService, end) {
+      t.error(err, 'should not error on setup');
+
+      typeService.isTyped(ctx, 'glue').then(function(res) {
+        t.error(err, 'should not error on isTyped(...)');
+        // Use equal instead of notOk to ensure that res is not wrapped.
+        t.equal(res, false, '\'glue\' is an untyped string');
+
+        var VomStr = vdl.registry.lookupOrCreateConstructor(vdl.types.STRING);
+        var typedString = new VomStr('glued');
+        typeService.isTyped(ctx, typedString, function(err, res) {
+          t.error(err, 'should not error on isTyped(...)');
+          // Use equal instead of ok to ensure that res is not wrapped.
+          t.equal(res, true, 'VomStr(\'glued\') is a typed string');
+          end(t);
+        });
+      }).catch(function error(err) {
+        t.error(err, 'should not error');
+        end(t);
+      });
+    });
+  });
+
+  test(namePrefix + 'typeService.isTyped(undefined) - promise', function(t) {
+    setup(options, function(err, ctx, typeService, end) {
+      t.error(err, 'should not error on setup');
+
+      // Check that you can leave an optional (or any) inArg as undefined.
+      // It should not be mistaken for a callback.
+      typeService.isTyped(ctx, undefined).then(function(res) {
+        t.error(err, 'should not error on isTyped(undefined)');
+        t.equal(res, false, 'undefined is treated like JSValue null');
+
+        // Lenient: Having both arg and cb as undefined is fine too.
+        typeService.isTyped(ctx, undefined, undefined).then(function(res) {
+          t.error(err, 'should not error on isTyped(undefined, undefined)');
+          t.equal(res, false, 'undefined is treated like JSValue null');
+
+          // Having any more undefined's is an error (too many args).
+          typeService.isTyped(ctx, undefined, undefined, undefined).then(
+            function(res) {
+
+            t.fail(err, 'should have errored');
+            end(t);
+          }).catch(function(err) {
+            t.ok(err, 'should error');
+            end(t);
+          });
+        });
+      }).catch(function error(err) {
+        t.error(err, 'should not error');
+        end(t);
+      });
+    });
+  });
+
   // This test ensures that typed values sent between JS server and client are
   // unwrapped when being processed. Further, the client disallows sending the
   // wrong type to the server.
diff --git a/test/unit/test-namespace-util.js b/test/unit/test-namespace-util.js
index 38b5fbd..09f6608 100644
--- a/test/unit/test-namespace-util.js
+++ b/test/unit/test-namespace-util.js
@@ -194,7 +194,7 @@
     ['/The % rain in /% Spain', '%2FThe %25 rain in %2F%25 Spain'],
     ['/%/%', '%2F%25%2F%25'],
     ['ᚸӲ읔קAل', 'ᚸӲ읔קAل'],
-    ['ᚸ/Ӳ%읔/ק%Aل', 'ᚸ%2FӲ%25읔%2Fק%25Aل'],
+    ['ᚸ/Ӳ%읔/ק%A+ل', 'ᚸ%2FӲ%25읔%2Fק%25A+ل'],
   ];
 
   tests.forEach(function(t) {