The Perl Toolchain Summit needs more sponsors. If your company depends on Perl, please support this very important event.
Changes 017
MANIFEST 01
META.json 34
META.yml 34
Makefile.PL 2945
lib/RPC/ExtDirect/API/Hook.pm 1617
lib/RPC/ExtDirect/API/Method.pm 80424
lib/RPC/ExtDirect/API/Method.pod 480
lib/RPC/ExtDirect/API.pod 4145
lib/RPC/ExtDirect/Config.pm 027
lib/RPC/ExtDirect/Config.pod 69
lib/RPC/ExtDirect/Event.pod 65
lib/RPC/ExtDirect/Intro.pod 33155
lib/RPC/ExtDirect/Request.pm 4181
lib/RPC/ExtDirect/Test/Data/API.pm 972
lib/RPC/ExtDirect/Test/Data/Env.pm 011
lib/RPC/ExtDirect/Test/Data/Poll.pm 05
lib/RPC/ExtDirect/Test/Data/Router.pm 0587
lib/RPC/ExtDirect/Test/Pkg/Bar.pm 11
lib/RPC/ExtDirect/Test/Pkg/JuiceBar.pm 22
lib/RPC/ExtDirect/Test/Pkg/Meta.pm 0113
lib/RPC/ExtDirect/Util.pm 332
lib/RPC/ExtDirect.pm 34
lib/RPC/ExtDirect.pod 439
t/002_attr_parser.t 47
t/02_exception.t 11
t/03_event.t 11
t/05_attributes.t 22
t/061_api.t 5596
t/062_api.t 3455
t/065_api.t 3491
t/066_api.t 3434
t/067_api.t 5050
t/068_method.t 15664
t/069_options.t 3030
t/071_request.t 01
t/072_request.t 01
t/08_deserialize.t 01
t/09_router.t 9301
t/10_poll.t 01
t/11_hook.t 134390
t/92_05_attributes.t 11
t/92_061_api.t 66
t/92_062_api.t 33
t/92_063_api.t 33
t/92_09_router.t 11
t/92_11_hook.t 010
47 files changed (This is a version diff) 7013530
@@ -1,3 +1,20 @@
+3.21  Thu May 14 20:38:51 PDT 2015
+    - Fixed a bug when exceptions could be thrown as ARRAY instead of a string
+      in ExtDirect attribute handler
+
+3.20  Tue Mar 31 21:37:10 PDT 2015
+    - Added support for call metadata available in Ext JS 5.1+
+    - Added support for JSON argument decoding in Form handlers
+    - Updated the documentation
+    - Tests, more tests
+    - Version bumped to 3.20 in concert with gateway releases
+
+3.03  Thu Jan 29 22:58:24 PST 2015
+    - Fixed outdated attribute parser check that required at least one
+      name in params arrayref for named Methods
+    - Named Methods with empty params will force !strict in constructor
+    - Misc documentation fixes
+
 3.02  Mon Oct 27 17:36:12 PDT 2014
     - Added timeout and max_retries API config options
     - Some test fixes and improvements
@@ -36,6 +36,7 @@ lib/RPC/ExtDirect/Test/Pkg/Env.pm
 lib/RPC/ExtDirect/Test/Pkg/Foo.pm
 lib/RPC/ExtDirect/Test/Pkg/Hooks.pm
 lib/RPC/ExtDirect/Test/Pkg/JuiceBar.pm
+lib/RPC/ExtDirect/Test/Pkg/Meta.pm
 lib/RPC/ExtDirect/Test/Pkg/PollProvider.pm
 lib/RPC/ExtDirect/Test/Pkg/Qux.pm
 lib/RPC/ExtDirect/Test/Util.pm
@@ -22,6 +22,7 @@
    "prereqs" : {
       "build" : {
          "requires" : {
+            "ExtUtils::MakeMaker" : "0",
             "Test::More" : "0.82"
          }
       },
@@ -41,11 +42,11 @@
    "release_status" : "stable",
    "resources" : {
       "bugtracker" : {
-         "web" : "http://github.com/nohuhu/RPC-ExtDirect/issues"
+         "web" : "https://github.com/nohuhu/RPC-ExtDirect/issues"
       },
       "repository" : {
-         "url" : "http://github.com/nohuhu/RPC-ExtDirect"
+         "url" : "https://github.com/nohuhu/RPC-ExtDirect"
       }
    },
-   "version" : "3.02"
+   "version" : "3.21"
 }
@@ -3,6 +3,7 @@ abstract: 'Core Ext.Direct implementation for Perl'
 author:
   - 'Alex Tokarev <tokarev@cpan.org>'
 build_requires:
+  ExtUtils::MakeMaker: 0
   Test::More: 0.82
 configure_requires:
   ExtUtils::MakeMaker: 0
@@ -22,6 +23,6 @@ requires:
   JSON: 2.0
   perl: 5.006
 resources:
-  bugtracker: http://github.com/nohuhu/RPC-ExtDirect/issues
-  repository: http://github.com/nohuhu/RPC-ExtDirect
-version: 3.02
+  bugtracker: https://github.com/nohuhu/RPC-ExtDirect/issues
+  repository: https://github.com/nohuhu/RPC-ExtDirect
+version: 3.21
@@ -1,7 +1,11 @@
 use 5.006000;
 use ExtUtils::MakeMaker;
 
+use strict;
+use warnings;
+
 # Add the `devtest` target to run regression and POD tests in one go
+# Beware the unexpanded tabs in this block!
 sub MY::postamble {
     return <<'END';
 devtest :
@@ -11,6 +15,7 @@ END
 }
 
 # Override `disttest` so it would behave as `devtest`
+# Beware the unexpanded tabs in this block!
 sub MY::dist_test {
 	return <<'END';
 disttest : distdir
@@ -21,42 +26,53 @@ disttest : distdir
 END
 }
 
-my $MMVersion = $ExtUtils::MakeMaker::VERSION;
+my $MM_VERSION  = $ExtUtils::MakeMaker::VERSION;
+my $github_repo = 'https://github.com/nohuhu/RPC-ExtDirect';
 
-WriteMakefile(
-    NAME              => 'RPC::ExtDirect',
-    VERSION_FROM      => 'lib/RPC/ExtDirect.pm',
-    ($MMVersion >= 6.55
-        ? ( BUILD_REQUIRES => {
-                'Test::More'            => '0.82',
-            },
-            PREREQ_PM => {
-                'Attribute::Handlers'   => '0.87',
-                'JSON'                  => '2.0',
-            },
-        )
-        : ( PREREQ_PM => {
-                'Test::More'            => '0.82',
-                'Attribute::Handlers'   => '0.87',
-                'JSON'                  => '2.0',
-            },
-        )
-    ),
+my %MAIN_REQ = (
+    'Attribute::Handlers' => '0.87',
+    'JSON'                => '2.0',
+);
 
-    ABSTRACT => 'Core Ext.Direct implementation for Perl',
-    AUTHOR   => 'Alex Tokarev <tokarev@cpan.org>',
-    LICENSE  => 'perl',
+my %TEST_REQ = (
+    'Test::More' => '0.82', # for explain()
+);
 
-    ($MMVersion >= 6.48
-        ? ( MIN_PERL_VERSION => 5.006000, )
-        : (),
+WriteMakefile(
+    NAME         => 'RPC::ExtDirect',
+    VERSION_FROM => 'lib/RPC/ExtDirect.pm',
+    ABSTRACT     => 'Core Ext.Direct implementation for Perl',
+    AUTHOR       => 'Alex Tokarev <tokarev@cpan.org>',
+    LICENSE      => 'perl',
+    
+    ($MM_VERSION >= 6.64
+        ? (
+            TEST_REQUIRES => \%TEST_REQ,
+            PREREQ_PM     => \%MAIN_REQ,
+        )
+        : (
+            ($MM_VERSION >= 6.5503
+                ? (
+                    BUILD_REQUIRES => \%TEST_REQ,
+                    PREREQ_PM      => \%MAIN_REQ,
+                )
+                : (
+                    PREREQ_PM => {
+                        %TEST_REQ,
+                        %MAIN_REQ,
+                    },
+                ),
+            ),
+        ),
     ),
+    
+    ($MM_VERSION >= 6.48 ? ( MIN_PERL_VERSION => 5.006000 ) : () ),
 
-    ($MMVersion >= 6.46
+    ($MM_VERSION >= 6.46
         ? ( META_MERGE => {
             resources   => {
-                bugtracker  => 'http://github.com/nohuhu/RPC-ExtDirect/issues',
-                repository  => 'http://github.com/nohuhu/RPC-ExtDirect',
+                bugtracker  => "$github_repo/issues",
+                repository  => $github_repo,
             },
            },
         )
@@ -64,28 +64,27 @@ sub new {
 sub run {
     my ($self, %args) = @_;
     
-    my ($api, $env, $arg, $result, $exception, $method_ref, $callee)
-        = @args{qw/api env arg result exception method_ref callee/};
-    
-    my $action_name    = $method_ref->action;
-    my $method_name    = $method_ref->name;
-    my $method_pkg     = $method_ref->package;
-    my $method_coderef = $method_ref->code;
+    my $method_ref  = $args{method_ref};
+    my $action_name = $method_ref->action;
+    my $method_name = $method_ref->name;
+    my $method_pkg  = $method_ref->package;
     
     my %hook_arg = $method_ref->get_api_definition_compat();
-
-    $hook_arg{arg}        = $arg;
-    $hook_arg{env}        = $env;
-    $hook_arg{code}       = $method_coderef;
+    
     $hook_arg{method_ref} = $method_ref;
+    $hook_arg{code}       = $method_ref->code;
+
+    @hook_arg{qw/arg env metadata aux_data/}
+      = @args{qw/arg env metadata aux_data/};
 
     # Result and exception are passed to "after" hook only
-    @hook_arg{ qw/result   exception   method_called/ }
-              = ($result, $exception, $callee)
-        if $self->type eq 'after';
+    if ( $self->type eq 'after' ) {
+        @hook_arg{ qw/result exception method_called/ }
+          = @args{ qw/result exception callee/ }
+    }
 
     for my $type ( $self->HOOK_TYPES ) {
-        my $hook = $api->get_hook(
+        my $hook = $args{api}->get_hook(
             action => $action_name,
             method => $method_name,
             type   => $type,
@@ -94,9 +93,11 @@ sub run {
         $hook_arg{ $type.'_ref' } = $hook;
         $hook_arg{ $type }        = $hook ? $hook->code : undef;
     }
+    
+    my $arg = $args{arg};
 
     # A drop of sugar
-    $hook_arg{orig} = sub { $method_coderef->($method_pkg, @$arg) };
+    $hook_arg{orig} = sub { $method_pkg->$method_name(@$arg) };
 
     my $hook_coderef  = $self->code;
     my $hook_sub_name = $self->sub_name;
@@ -4,7 +4,11 @@ use strict;
 use warnings;
 no  warnings 'uninitialized';           ## no critic
 
+use Carp;
+use JSON;
+
 use RPC::ExtDirect::Config;
+use RPC::ExtDirect::Util ();
 use RPC::ExtDirect::Util::Accessor;
 
 ### PUBLIC CLASS METHOD (ACCESSOR) ###
@@ -28,19 +32,39 @@ sub new {
     my $pollHandler = $arg{pollHandler};
     my $formHandler = $arg{formHandler};
     
-    my $is_named
-        = defined $arg{params} && !$pollHandler && !$formHandler;
-    
     my $is_ordered
         = defined $arg{len} && !$pollHandler && !$formHandler;
     
+    my $is_named
+        = !$pollHandler && !$formHandler && !$is_ordered;
+    
     my $processor = $pollHandler ? 'pollHandler'
                   : $formHandler ? 'formHandler'
-                  : $is_named    ? 'named'
                   : $is_ordered  ? 'ordered'
-                  :                'default'
+                  :                'named'
                   ;
     
+    # Need $self to call instance methods
+    my $self = bless {
+        upload_arg        => 'file_uploads',
+        is_named          => $is_named,
+        is_ordered        => $is_ordered,
+        argument_checker  => "check_${processor}_arguments",
+        argument_preparer => "prepare_${processor}_arguments",
+    }, $class;
+    
+    # If the Method is named, and params array is empty, force !strict
+    if ( $is_named ) {
+        $arg{params} = $arg{params} || []; # Better safe than sorry
+        $arg{strict} = !1 unless @{ $arg{params} };
+    }
+    
+    if ( exists $arg{metadata} ) {
+        # This method is coupled too tightly to try untangling it,
+        # so let's pretend that side effects are ok in this case
+        $self->_parse_metadata(\%arg);
+    }
+    
     # We avoid hard binding on the hook class
     eval "require $hook_class";
     
@@ -53,15 +77,10 @@ sub new {
             if $hook;
     }
     
-    return bless {
-        upload_arg        => 'file_uploads',
-        is_named          => $is_named,
-        is_ordered        => $is_ordered,
-        argument_checker  => "check_${processor}_arguments",
-        argument_preparer => "prepare_${processor}_arguments",
-        %arg,
-        %hooks,
-    }, $class;
+    @$self{ keys %arg   } = values %arg;
+    @$self{ keys %hooks } = values %hooks;
+    
+    return $self;
 }
 
 ### PUBLIC INSTANCE METHOD ###
@@ -74,33 +93,57 @@ sub get_api_definition {
     my ($self, $env) = @_;
     
     # By default we're not using the environment object,
-    # but user can override this method to make permission
-    # and/or other kind of checks
+    # but application developers can override this method
+    # to make permission and/or other kind of checks
     
-    # Poll handlers are not declared in the API
+    # Poll handlers are not declared in the remoting API
     return if $self->pollHandler;
     
-    my $name   = $self->name;
-    my $strict = $self->strict;
+    my $name = $self->name;
+
+    my $def;
     
     # Form handlers are defined like this
     # (\1 means JSON::true and doesn't force us to `use JSON`)
-    return { name => $name, len => 0, formHandler => \1 }
-        if $self->formHandler;
-    
+    if ( $self->formHandler ) {
+        $def = { name => $name, formHandler => \1 };
+    }
+
     # Ordinary method with positioned arguments
-    return { name => $name, len => $self->len + 0 },
-        if $self->is_ordered;
-    
+    elsif ( $self->is_ordered ) {
+        $def = { name => $name, len => $self->len + 0 }
+    }
+
     # Ordinary method with named arguments
-    return {
-        name   => $name,
-        params => $self->params,
-        defined $strict ? (strict => $strict) : (),
-    } if $self->params;
-    
-    # No arguments specified means we're not checking them
-    return { name => $name };
+    else {
+        my $strict = $self->strict;
+
+        $def = {
+            name   => $name,
+            params => $self->params || [],
+            defined $strict ? (strict => ($strict ? \1 : \0)) : (),
+        };
+    }
+
+    if ( my $meta = $self->metadata ) {
+        $def->{metadata} = {};
+
+        if ( $meta->{len} ) {
+            $def->{metadata} = {
+                len => $meta->{len},
+            };
+        }
+        else {
+            my $strict = $meta->{strict};
+
+            $def->{metadata} = {
+                params => $meta->{params},
+                defined $strict ? (strict => ($strict ? \1 : \0)) : (),
+            };
+        }
+    }
+
+    return $def;
 }
 
 ### PUBLIC INSTANCE METHOD ###
@@ -195,11 +238,12 @@ sub check_method_arguments {
 #
 # Prepare the arguments to be passed to the called Method,
 # according to the Method's expectations. This works two ways:
-# on the server side, Request will call this method to prepare
-# the arguments that are to be passed to the actual Method code
-# that does things; on the client side, Client will call this
-# method to prepare the arguments that are about to be encoded
-# in JSON and passed over to the client side.
+# on the server side, RPC::ExtDirect::Request will call this method
+# to prepare the arguments that are to be passed to the actual
+# Method code that does things; on the client side,
+# RPC::ExtDirect::Client will call this method to prepare
+# the arguments that are about to be encoded in JSON and passed
+# over to the server side.
 #
 # The difference is that the server side wants an unfolded list,
 # and the client side wants a reference, either hash- or array-.
@@ -216,6 +260,45 @@ sub prepare_method_arguments {
 
 ### PUBLIC INSTANCE METHOD ###
 #
+# Check the metadata that was passed in the Ext.Direct request
+# to make sure it conforms to the API declared by this Method.
+#
+# This method is similar to check_method_arguments and operates
+# the same way; it is kept separate for easier overriding
+# in subclasses.
+#
+
+sub check_method_metadata {
+    my $self = shift;
+    
+    return 1 unless $self->metadata;
+    
+    my $checker = $self->metadata_checker;
+    
+    return $self->$checker(@_);
+}
+
+### PUBLIC INSTANCE METHOD ###
+#
+# Prepare the metadata to be passed to the called Method,
+# in accordance with Method's specification.
+#
+# This method works similarly to prepare_method_arguments
+# and is kept separate for easier overriding in subclasses.
+#
+
+sub prepare_method_metadata {
+    my $self = shift;
+    
+    return unless $self->metadata;
+    
+    my $preparer = $self->metadata_preparer;
+    
+    return $self->$preparer(@_);
+}
+
+### PUBLIC INSTANCE METHOD ###
+#
 # Check the arguments for a pollHandler
 #
 
@@ -235,10 +318,9 @@ sub prepare_pollHandler_arguments {
     my @actual_arg = ();
     
     # When called from the client, env_arg should not be defined
-    my $env_arg = $self->env_arg;
-    
-    no warnings;
-    splice @actual_arg, $env_arg, 0, $arg{env} if defined $env_arg;
+    if ( defined (my $env_arg = +$self->env_arg) ) {
+        push @actual_arg, $arg{env} if defined $arg{env};
+    }
     
     return wantarray ? @actual_arg : [ @actual_arg ];
 }
@@ -249,7 +331,7 @@ sub prepare_pollHandler_arguments {
 #
 
 sub check_formHandler_arguments {
-    my ($self, $arg) = @_;
+    my ($self, $arg, $meta) = @_;
     
     # Nothing to check here really except that it's a hashref
     die sprintf "ExtDirect formHandler Method %s.%s expects named " .
@@ -264,6 +346,9 @@ sub check_formHandler_arguments {
 # Prepare the arguments for a formHandler
 #
 
+my @std_params = qw/action method extAction extMethod
+                    extType extTID extUpload _uploads/;
+
 sub prepare_formHandler_arguments {
     my ($self, %arg) = @_;
     
@@ -275,31 +360,76 @@ sub prepare_formHandler_arguments {
     my %data = %$input;
 
     # Ensure there are no runaway ExtDirect form parameters
-    my @runaway_params = qw(action method extAction extMethod
-                            extTID extUpload _uploads);
-    delete @data{ @runaway_params };
+    delete @data{ @std_params };
     
     my $upload_arg = $self->upload_arg;
 
     # Add uploads if there are any
     $data{ $upload_arg } = $upload if defined $upload;
     
-    my $env_arg = $self->env_arg;
-
-    $data{ $env_arg } = $env if $env_arg;
-
+    if ( defined (my $env_arg = $self->env_arg) ) {
+        $data{ $env_arg } = $env;
+    };
+    
+    my $meta_def = $self->metadata;
+    
+    if ( $meta_def && defined (my $meta_arg = $meta_def->{arg}) ) {
+        my $meta = $self->prepare_method_metadata(%arg);
+        $data{ $meta_arg } = $meta if defined $meta;
+        
+        # Form handlers receive the input hash almost unimpeded;
+        # if $meta_arg value is not default 'metadata' the arguments
+        # will include two copies of metadata. We don't want that.
+        delete $data{metadata} unless $meta_arg eq 'metadata';
+    }
+    
+    # Preparers are called in list context on the server side,
+    # where params can be decoded if configured so; the client
+    # will send all form fields JSON encoded anyway so no special
+    # handling required for it.
+    if ( wantarray ) {
+        for my $param ( @{ $self->decode_params || [] } ) {
+            # This check is necessary because inclusion in decode_params
+            # does not make the field a mandatory argument!
+            if ( exists $data{$param} ) {
+                my $value = delete $data{$param};
+                
+                if ( defined $value ) {
+                    # If JSON throws an exception we will rethrow it
+                    # after cleaning up
+                    $value = eval { JSON::from_json($value) };
+                    
+                    die RPC::ExtDirect::Util::clean_error_message($@)
+                        if $@;
+                }
+                
+                $data{$param} = $value;
+            }
+        }
+        
+        return %data;
+    }
+    else {
+        return { %data };
+    }
     return wantarray ? %data : { %data };
 }
 
 ### PUBLIC INSTANCE METHOD ###
 #
-# Check the arguments for a Method with named parameters
+# Check the arguments for a Method with named parameters.
+#
+# Note that it does not matter if the Method expects its
+# arguments to be strictly conforming to the declaration
+# or not; in case of !strict the listed parameters are
+# still mandatory. In fact !strict only means that
+# non-declared parameters are not dropped.
 #
 
 sub check_named_arguments {
     my ($self, $arg) = @_;
     
-    die sprintf "ExtDirect Method %s.%s expects named arguments " .
+    die sprintf "ExtDirect Method %s.%s expects named arguments ".
                 "in hashref\n", $self->action, $self->name
         unless 'HASH' eq ref $arg;
     
@@ -318,6 +448,32 @@ sub check_named_arguments {
 
 ### PUBLIC INSTANCE METHOD ###
 #
+# Check the metadata for Methods that expect it by-name
+#
+
+sub check_named_metadata {
+    my ($self, $meta) = @_;
+    
+    die sprintf "ExtDirect Method %s.%s expects metadata key/value ".
+                "pairs in hashref\n", $self->action, $self->name
+        unless 'HASH' eq ref $meta;
+    
+    my $meta_def = $self->metadata;
+    my @meta_params = @{ $meta_def->{params} };
+    
+    my @missing = map { !exists $meta->{$_} ? $_ : () } @meta_params;
+    
+    die sprintf "ExtDirect Method %s.%s requires the following ".
+                "metadata keys: '%s'; these are missing: '%s'\n",
+                $self->action, $self->name,
+                join(', ', @meta_params), join(', ', @missing)
+        if @missing;
+    
+    return 1;
+}
+
+### PUBLIC INSTANCE METHOD ###
+#
 # Prepare the arguments for a Method with named parameters
 #
 
@@ -341,27 +497,67 @@ sub prepare_named_arguments {
         %actual_arg = %$input;
     }
     
-    my $env_arg = $self->env_arg;
+    if ( defined (my $env_arg = $self->env_arg) ) {
+        $actual_arg{ $env_arg } = $env;
+    }
+
+    my $meta_def = $self->metadata;
     
-    $actual_arg{ $env_arg } = $env if defined $env_arg;
+    if ( $meta_def && defined (my $meta_arg = $meta_def->{arg}) ) {
+        my $meta = $self->prepare_method_metadata(%arg);
+        $actual_arg{ $meta_arg } = $meta if defined $meta;
+    }
 
     return wantarray ? %actual_arg : { %actual_arg };
 }
 
 ### PUBLIC INSTANCE METHOD ###
 #
+# Prepare the metadata for Methods that expect it by-name
+#
+
+sub prepare_named_metadata {
+    my ($self, %arg) = @_;
+    
+    my $meta_def   = $self->metadata;
+    my $meta_input = $arg{metadata};
+    
+    return unless $meta_input;
+    
+    my %meta;
+    
+    my $strict = $meta_def->{strict};
+    $strict = 1 unless defined $strict;
+    
+    if ( $strict ) {
+        my @params = @{ $meta_def->{params} };
+        
+        @meta{ @params } = @$meta_input{ @params };
+    }
+    else {
+        %meta = %$meta_input;
+    }
+    
+    return \%meta;
+}
+
+### PUBLIC INSTANCE METHOD ###
+#
 # Check the arguments for a Method with ordered parameters
 #
 
 sub check_ordered_arguments {
-    my ($self, $arg) = @_;
-    
-    die sprintf "ExtDirect Method %s.%s expects ordered arguments " .
-                "in arrayref\n"
-        unless 'ARRAY' eq ref $arg;
+    my ($self, $input) = @_;
     
     my $want_len = $self->len;
-    my $have_len = @$arg;
+    
+    # Historically Ext.Direct on the JavaScript client side sent null value
+    # instead of empty array for ordered Methods that accept 0 arguments.
+    die sprintf "ExtDirect Method %s.%s expects ordered arguments " .
+                "in arrayref\n", $self->action, $self->name
+        if $want_len > 0 && 'ARRAY' ne ref $input;
+
+    my $have_len = $want_len > 0 ? @$input : 0;
     
     die sprintf "ExtDirect Method %s.%s requires %d argument(s) ".
                 "but only %d are provided\n",
@@ -373,6 +569,30 @@ sub check_ordered_arguments {
 
 ### PUBLIC INSTANCE METHOD ###
 #
+# Check the metadata for Methods that expect it by-position
+#
+
+sub check_ordered_metadata {
+    my ($self, $meta) = @_;
+    
+    die sprintf "ExtDirect Method %s.%s expects metadata in arrayref\n",
+                $self->action, $self->name
+        unless 'ARRAY' eq ref $meta;
+    
+    my $meta_def = $self->metadata;
+    my $want_len = $meta_def->{len};
+    my $have_len = @$meta;
+    
+    die sprintf "ExtDirect Method %s.%s requires %d metadata ".
+                "value(s) but only %d are provided\n",
+                $self->action, $self->name, $want_len, $have_len
+        unless $have_len >= $want_len;
+    
+    return 1;
+}
+
+### PUBLIC INSTANCE METHOD ###
+#
 # Prepare the arguments for a Method with ordered parameters
 #
 
@@ -382,39 +602,61 @@ sub prepare_ordered_arguments {
     my $env   = $arg{env};
     my $input = $arg{input};
     
-    my @data       = @$input;
-    my @actual_arg = splice @data, 0, $self->len;
+    my @actual_arg;
     
-    my $env_arg = $self->env_arg;
+    # For Methods with 0 accepted arguments, input may be either
+    # an empty array from RPC::ExtDirect::Client, or undef from
+    # the JavaScript client. Hysterical raisins are hysterical,
+    # so we have to account for that.
+    if ( my $want_len = $self->len ) {
+        # Input is by reference! Unpack to avoid changing it.
+        my @data = @$input;
+        @actual_arg = splice @data, 0, $want_len;
+    }
     
-    no warnings;
-    splice @actual_arg, $env_arg, 0, $env if defined $env_arg;
+    no warnings;    ## no critic
     
-    return wantarray ? @actual_arg : [ @actual_arg ];
-}
-
-### PUBLIC INSTANCE METHOD ###
-#
-# Check the arguments when the Method signature is unknown
-#
+    if ( defined (my $env_arg = +$self->env_arg) ) {
+        # Splicing an empty array at negative subscript will result
+        # in a fatal error; we need to guard against that.
+        $env_arg = 0 if $env_arg < 0 && -$env_arg > @actual_arg;
+        
+        splice @actual_arg, $env_arg, 0, $env;
+    }
+    
+    my $meta_def = $self->metadata;
+    
+    if ( $meta_def && defined (my $meta_arg = +$meta_def->{arg}) ) {
+        my $meta = $self->prepare_method_metadata(%arg);
+        
+        if ( defined $meta ) {
+            $meta_arg = 0 if $meta_arg < 0 && -$meta_arg > @actual_arg;
+            
+            splice @actual_arg, $meta_arg, 0, $meta;
+        }
+    }
 
-sub check_default_arguments {
-    # No checking means the arguments are not checked.
-    # Sincerely, C.O.
-    return 1;
+    return wantarray ? @actual_arg : [ @actual_arg ];
 }
 
 ### PUBLIC INSTANCE METHOD ###
 #
-# Prepare the arguments when the Method signature is unknown
+# Prepare the metadata for Methods that expect it by-position
 #
 
-sub prepare_default_arguments {
+sub prepare_ordered_metadata {
     my ($self, %arg) = @_;
     
-    my @actual_arg = ( $arg{input}, $arg{env} );
+    my $meta_def   = $self->metadata;
+    my $meta_input = $arg{metadata};
     
-    return wantarray ? @actual_arg : [ @actual_arg ];
+    return unless $meta_input;
+    
+    # Copy array elements to avoid mutating the arrayref
+    my @meta_data   = @$meta_input;
+    my @meta_output = splice @meta_data, 0, $meta_def->{len};
+    
+    return \@meta_output;
 }
 
 ### PUBLIC INSTANCE METHOD ###
@@ -435,6 +677,7 @@ my $accessors = [qw/
     name
     params
     len
+    metadata
     formHandler
     pollHandler
     is_ordered
@@ -443,8 +686,12 @@ my $accessors = [qw/
     package
     env_arg
     upload_arg
+    meta_arg
     argument_checker
     argument_preparer
+    metadata_checker
+    metadata_preparer
+    decode_params
 /,
     __PACKAGE__->HOOK_TYPES,
 ];
@@ -453,4 +700,101 @@ RPC::ExtDirect::Util::Accessor::mk_accessors(
     simple => $accessors,
 );
 
+############## PRIVATE METHODS BELOW ##############
+
+### PRIVATE INSTANCE METHOD ###
+#
+# Parse metadata definition and run sanity checks.
+#
+# This method has side effects on $arg!
+#
+
+sub _parse_metadata {
+    my ($self, $arg) = @_;
+    
+    my $meta = delete $arg->{metadata};
+    
+    if ( 'HASH' eq ref $meta ) {
+        my $meta_def = {};
+
+        if ( defined (my $len = $meta->{len}) ) {
+            # Metadata is optional so ordered with 0 arguments
+            # does not make any sense
+            die [
+                    sprintf "ExtDirect Method %s.%s cannot accept ".
+                            "0 arguments for ordered metadata",
+                            $arg->{action}, $arg->{name}
+                ]
+                unless $len > 0;
+            
+            $meta_def->{len} = $len;
+            
+            $arg->{metadata_checker}  = 'check_ordered_metadata';
+            $arg->{metadata_preparer} = 'prepare_ordered_metadata';
+        }
+        else {
+            my $params = $meta->{params} || [];
+
+            # Same as with main arguments; force !strict if named metadata
+            # has empty params
+            my $strict = !@$params               ? !1
+                       : defined $meta->{strict} ? $meta->{strict}
+                       :                           undef
+                       ;
+            
+            # !strict with no params might be a typo or something;
+            # worth a warning in that case
+            carp sprintf "ExtDirect Method %s.%s implies strict ".
+                         "argument checking for named metadata, ".
+                         "but no parameter names are specified.",
+                         $arg->{action}, $arg->{name}
+                if !@$params && (!defined $meta->{strict} || $meta->{strict});
+
+            $meta_def->{strict} = $strict;
+            $meta_def->{params} = $params;
+            
+            $arg->{metadata_checker}  = 'check_named_metadata';
+            $arg->{metadata_preparer} = 'prepare_named_metadata';
+        }
+        
+        $meta_def->{arg} = $self->_get_meta_arg($meta, $arg);
+
+        $arg->{metadata} = $meta_def;
+    }
+}
+
+### PRIVATE INSTANCE METHOD ###
+#
+# Check that the metadata has valid argument name or position
+# to be applied to the called Method.
+#
+# This code is split from the method above so that we could
+# override it in the Client which doesn't need to run the same
+# checks as the server side.
+#
+
+sub _get_meta_arg {
+    my ($self, $meta, $arg) = @_;
+    
+    my $meta_arg = $meta->{arg};
+    
+    if ( $self->is_ordered ) {
+        # There is no way to splice new elements at the end of array
+        # without knowing array length. Splicing at negative subscripts
+        # will not do what is expected, and I don't see a sane default
+        # otherwise. So insist on having the arg defined.
+        die [
+                sprintf "ExtDirect Method %s.%s cannot accept ".
+                        "ordered metadata with no arg position specified",
+                        $arg->{action}, $arg->{name}
+            ]
+            unless defined $meta_arg;
+    }
+    else {
+        $meta_arg = defined $meta_arg ? $meta_arg : 'metadata';
+    }
+    
+    return $meta_arg;
+}
+
 1;
@@ -128,6 +128,58 @@ Use this parameter to change the hash key name in which the
 L<file upload array|RPC::ExtDirect/"FILE UPLOADS"> is passed to a
 Form handler Method. Default is C<'file_uploads'>.
 
+=item C<decode_params>
+
+This optional parameter may contain the list of fields that should be
+decoded from JSON for a formHandler Method. Does nothing for Methods
+with other calling conventions.
+
+=item C<metadata>
+
+Use this parameter to declare that this Method supports call
+L<metadata|RPC::ExtDirect::Intro/Metadata>. The value of this key
+should be a hashref with the following properties:
+
+=over 8
+
+=item C<len>
+
+Declares that this Method accepts ordered (by position) metadata
+with this number of mandatory values, with the number > 0.
+The metadata values will be passed to the Method in arrayref.
+
+This parameter is mutually exclusive with "params" below.
+
+=item C<params>
+
+Declares that this Method accepts named metadata values in a hashref.
+The value of C<params> is also arrayref with the names of mandatory
+metadata values. The declaration arrayref may be empty to indicate
+that all metadata values are optional and should not be checked
+for existence.
+
+This parameter is mutually exclusive with "len" above.
+
+=item C<strict>
+
+Set this option to falsy value to have all metadata key-value pairs
+passed to the Method. The default behavior is similar to named main
+arguments, where all undeclared parameters will be discarded and only
+the declared ones will be passed to the Method.
+
+=item C<arg>
+
+Defines the position of the metadata argument for Ordered methods,
+or the name of the metadata argument for Named methods. Note that
+this is about the calling convention of the main arguments, not
+metadata itself!
+
+For Named methods, C<arg> defaults to C<metadata>; for Ordered
+methods there is no default and an error will be raised if C<arg>
+is omitted.
+
+=back
+
 =item other
 
 Any other hash key with the corresponding value will be stored in the
@@ -210,6 +262,14 @@ Instance method. Takes input data and checks that it's an arrayref,
 and it has enough elements to satisfy the L</len> requirement of
 the Method.
 
+=item C<check_ordered_metadata>
+
+Instance method. Takes input metadata and checks that it's an arrayref,
+and it has enough values to satisfy the metadata C<len> requirement
+of the Method.
+
+Metadata will only be checked for Methods that accept it.
+
 =item C<check_named_arguments>
 
 Instance method. Takes input data and checks that it's a hashref,
@@ -217,6 +277,13 @@ and that keys for all mandatory L</params> exist in that hashref.
 If the Method declares empty L</params>, the check will pass and
 effectively all arguments will be passed on to the Method call.
 
+=item C<check_named_metadata>
+
+Instance method. Takes input metadata and performs the checks similar
+to those made for named arguments.
+
+Metadata will only be checked for Methods that accept it.
+
 =item C<check_formHandler_arguments>
 
 Instance method. Takes input data and checks that it's a hashref.
@@ -268,27 +335,36 @@ when uploaded files are present.
 Instance method. Takes C<input> arguments for the Method and returns
 an arrayref conformant to the 
 L<ordered Method's|RPC::ExtDirect::Intro/"Ordered Method"> definition.
-This arrayref will optionally contain C<env> object.
+This arrayref will optionally contain env object if requested,
+and/or metadata.
+
+=item C<prepare_ordered_metadata>
+
+Instance method. Takes input metadata and returns an arrayref conformant
+to the Method's metadata definition.
 
 =item C<prepare_named_arguments>
 
 Instance method. Takes C<input> arguments for the Method and returns
 a hashref conformant to the 
 L<named Method's|RPC::ExtDirect::Intro/"Named Method"> definition.
-This hashref will optionally contain C<env> object.
+This hashref will optionally contain env object if requested,
+and/or metadata.
 
 =item C<prepare_formHandler_arguments>
 
 Instance method. Takes C<input> arguments for the Method and returns
 a hashref conformant to the
 L<Form Handler Method's|RPC::ExtDirect::Intro/"Form Handler Method">
-definition. This hashref will optionally contain C<env> object.
+definition. This hashref will optionally contain env object if requested,
+and/or metadata.
 
 =item C<prepare_pollHandler_arguments>
 
 Instance method. Returns an arrayref conformant to
 L<Poll handler method's|RPC::ExtDirect::Intro/"Poll Handler Method">
-definition. This arrayref will optionally contain C<env> object.
+definition. This arrayref will optionally contain env object
+if requested.
 
 =back
 
@@ -33,8 +33,14 @@ RPC::ExtDirect::API - Ext.Direct service discovery handler
                         len => 1,
                     },
                     bar => {
-                        params => [qw/ foo bar /],
+                        params => ['foo', 'bar'],
                     },
+                    baz => {
+                        params => ['frobbe', 'throbbe'],
+                        metadata => {
+                            params => ['mymse', 'qux'],
+                        }
+                    }
                 }
             }
         }
@@ -131,9 +137,14 @@ declared for this method, an exception will be thrown as well, unless
 strict argument checking is turned off
 (L<see below|/"Lazy parameter checking">).
 
+It is also possible to declare a named Method with I<no> mandatory
+parameters at all; do that by setting L</params> option to an empty
+arrayref: C<[]>. In that case, the calling convention is still honored
+but all parameters are treated as optional and no checks are performed.
+
 =head2 Lazy parameter checking
 
-Starting with Ext JS 4.2.2 and RPC::ExtDirect 3.0+, it is possible to
+Starting with Ext JS 4.2.2 and RPC::ExtDirect 3.00, it is possible to
 perform less strict parameter checking on by-name methods. All parameters
 explicitly declared for a method will still be treated as mandatory, but
 no exception will be thrown if undeclared arguments are passed into the
@@ -149,6 +160,25 @@ it, set the L<strict|/strict> option to falsy value for any given method.
 Lazy parameter checking is not supported in Ext JS below 4.2.2, and in
 Sencha Touch 2.x.
 
+=head2 Call metadata
+
+Starting with Ext JS 5.1.0 and RPC::ExtDirect 3.20, it is possible to
+pass additional set of metadata arguments to any Method that supports it.
+
+Metadata arguments can differ in calling convention from the main arguments;
+e.g. it is possible to have an Ordered method with named metadata,
+and vice versa. The only kind of Methods unable to receive metadata is
+Poll handlers.
+
+Call metadata is optional and will only be passed to the Methods that declare
+their metadata convention. Unlike the main arguments, call metadata is always
+passed by reference, in arrayref or hashref.
+
+In order to support call metadata, the Method declaration should include
+corresponding parameters via the L</metadata> keyword. Note that for Ordered
+Methods, metadata declaration should always include the position for the
+metadata argument.
+
 =head1 COMPILE VS RUN TIME DEFINITION
 
 There are two ways to define Ext.Direct L<API|RPC::ExtDirect::Intro/API>
@@ -171,7 +201,7 @@ more than one active API object at a given time, possibly implementing
 different APIs tailored for usage patterns of a particular application.
 
 Note that these two methods are I<not> mutually exclusive, but it is
-not recommended to mix them unless you really know how to deal with ensuing
+not recommended to mix them unless you are ready to deal with ensuing
 timing issues. You've been warned.
 
 =head1 DEFINING METHODS STATICALLY
@@ -184,7 +214,7 @@ with the sub definition:
 Note that there can be no space between the C<ExtDirect> attribute name and
 the opening parens; also in Perls older than 5.12, the attribute statement
 cannot span multiple lines, i.e. the whole C<ExtDirect(...)> construct
-should fit in one line.
+should fit in one line. This is due to limitations in Perl code parser.
 
 Inside the parentheses, one of the following mutually exclusive option
 keywords is B<mandatory>:
@@ -203,7 +233,7 @@ If C<n> is used, this keyword should always come first in the list:
 
 =item C<len>
 
-A more preferred way to define an Ordered Method. This keyword should be
+The more preferred way to define an Ordered Method. This keyword should be
 followed by the number of the parameters accepted by the Method:
 
     sub foo : ExtDirect(len => 1)      {} # right
@@ -239,34 +269,8 @@ Having more than one calling convention keyword in the Method definition
 is not supported and will lead to undefined behavior.
 
 Besides the mandatory calling convention keyword, there are optional
-Method attributes in hash-like C<key =E<gt> value> form. Currently
-supported attributes are:
-
-=over 4
-
-=item C<strict>
-
-This option, if set to a falsy value with Named parameters, turns on
-L<lazy parameter checking|/"Lazy parameter checking">. Since the checks
-are strict by default, setting this option to truthy value will do nothing.
-
-    sub foo : ExtDirect(params => ['foo'], strict => !1, ...) {}
-
-=item C<before|instead|after>
-
-A corresponding L<Hook|RPC::ExtDirect/HOOKS> slot definition for the
-Method. This keyword should be followed by an argument that defines the
-actual Hook behavior. See L</add_hook> method documentation below.
-
-=item other
-
-Any other keyword with the corresponding value will be passed through
-to the L<Method class|RPC::ExtDirect::Config/api_method_class> constructor
-and will end up as a Method object property. Note that accessors for such
-properties will I<not> be created automatically when the stock
-L<RPC::ExtDirect::API::Method> class is used.
-
-=back
+Method attributes in hash-like C<< key => value >> form. See more in
+L<RPC::ExtDirect::API::Method/new>.
 
 =head1 DEFINING METHODS DYNAMICALLY
 
@@ -297,8 +301,8 @@ containing the API definition:
         },
     });
 
-Keywords and options are the same as with the static method, refer to the
-L<section above|/"DEFINING METHODS STATICALLY"> for details.
+Keywords and options are mostly the same as with the static definition;
+refer to L<RPC::ExtDirect::API::Method/new> for details.
 
 =head1 GLOBAL API TREE INSTANCE
 
@@ -345,7 +349,7 @@ mistake has been made somewhere.
 =head1 API CONFIGURATION
 
 RPC::ExtDirect::API provides two ways to configure Ext.Direct API declaration
-variables to accommodate specific application needs: dynamic via an
+variables to accommodate for specific application needs: dynamic via an
 L<RPC::ExtDirect::Config> instance, and static via C<import> package
 subroutine.
 
@@ -355,7 +359,7 @@ applications; it allows keeping the whole API definition in one place
 instead of distributed among the packages. It is also possible to define
 more than one API this way, for publishing to different clients.
 
-The static configuration was available since version 1.0 and will be
+The static configuration has been available since version 1.0 and will be
 supported going forward. This way it is possible to configure the API
 variables at compile time:
 
@@ -640,8 +644,8 @@ used.
 =item C<get_method_by_name>
 
 Instance method. Return the L<Method|RPC::ExtDirect::Intro/Method> object
-for the corresponding Action and Method name. Accepts two ordered
-arguments:
+for the corresponding Action and Method name, or C<undef>. Accepts two
+ordered arguments:
 
 =over 8
 
@@ -707,8 +711,8 @@ This parameter is mandatory.
 
 =item C<get_hook>
 
-Instance method. Returns the Hook object for a given criteria. Accepts
-named arguments in a hash.
+Instance method. Returns the Hook object for given criteria, or C<undef>.
+Accepts named arguments in a hash.
 
 When looking up Method level hook, C<action> or C<package> parameter is
 mandatory, as well as the C<method> name and hook C<type>. For Action
@@ -365,6 +365,33 @@ my @package_globals = grep { $_->{var} } @$DEFINITIONS;
 
 ### PRIVATE INSTANCE METHOD ###
 #
+# Return the default value for a field.
+#
+
+sub _get_default {
+    my ($self, $field) = @_;
+    
+    my $def = $field_defaults{$field};
+    
+    return $def ? $def->{default} : undef;
+}
+
+### PRIVATE INSTANCE METHOD ###
+#
+# Return true if the current field value is the default.
+#
+
+sub _is_default {
+    my ($self, $field) = @_;
+    
+    my $value   = $self->$field();
+    my $default = $self->_get_default($field);
+    
+    return $value eq $default;
+}
+
+### PRIVATE INSTANCE METHOD ###
+#
 # Parse global package variables
 #
 
@@ -111,8 +111,8 @@ Default: C<'RPC::ExtDirect::API::Hook'>.
 
 When set to truthy value, API L<Action|RPC::ExtDirect::Intro/Action> names
 will default to package name with C<'::'> replaced with dots:
-C<'Foo::Bar::Baz' -> 'Foo.Bar.Baz'>, instead of using only the last chunk
-of the package name: C<'Foo::Bar::Baz' -> 'Baz'>.
+C<< 'Foo::Bar::Baz' -> 'Foo.Bar.Baz' >>, instead of using only the last
+chunk of the package name: C<< 'Foo::Bar::Baz' -> 'Baz' >>.
 
 Default: C<!1> (false).
 
@@ -328,7 +328,7 @@ Default: C<'RPC::ExtDirect::EventProvider'>.
 =item verbose_exceptions
 
 Turn informative exceptions on/off. For whatever reason, Ext.Direct spec
-requires server stack to return detailed exceptions in debugging mode,
+requires server stack to return detailed exceptions in debugging mode only,
 replacing them with generic "An error has occured" in production mode.
 Most probably this was done to increase application security, but as the
 result it hinders development and support greatly.
@@ -387,7 +387,7 @@ Default: C<'Ext.app.POLLING_API'>.
 =item namespace
 
 JavaScript namespace to be declared in the remoting API. See
-L<Ext.direct.RemotingProvider|http://docs-origin.sencha.com/extjs/5.0.0/apidocs/#!/api/Ext.direct.RemotingProvider-cfg-namespace>
+L<Ext.direct.RemotingProvider|http://docs.sencha.com/extjs/5.1/5.1.0-apidocs/#!/api/Ext.direct.RemotingProvider>
 documentation for more detailed information on this option.
 
 Default: C<''> (empty string).
@@ -399,6 +399,9 @@ to automatically set up RemotingProvider and PollingProvider on the
 client side to the Ext.Direct declaration JavaScript chunk, so that
 JavaScript application won't need to do that.
 
+This option is deprecated as of RPC::ExtDirect 3.0, and should not
+be used going forward.
+
 Default: C<!1> (false).
 
 =item no_polling
@@ -412,14 +415,14 @@ Default: C<!1> (false).
 =item max_retries
 
 Number of times for the client side to re-attempt delivery on failure
-of a call. (see Ext.direct.RemotingProvider.maxRetries).
+of a call. (see L<Ext.direct.RemotingProvider.maxRetries|http://docs.sencha.com/extjs/5.1/5.1.0-apidocs/#!/api/Ext.direct.RemotingProvider-cfg-maxRetries>).
 
 Default: C<undef>.
 
 =item timeout
 
 The timeout for the client side to use for each request
-(see Ext.direct.RemotingProvider.timeout).
+(see L<Ext.direct.RemotingProvider.timeout|http://docs.sencha.com/extjs/5.1/5.1.0-apidocs/#!/api/Ext.direct.RemotingProvider-cfg-timeout>).
 
 Default: C<undef>.
 
@@ -44,11 +44,10 @@ to serialize a self-contained object. To avoid this, set a global Config
 option L<json_options|RPC::ExtDirect::Config/json_options> to include
 C<allow_blessed> flag:
 
-    my $config = RPC::ExtDirect::Config->new(
-        json_options => {
-            allow_blessed => 1,
-        },
-    );
+    my $config = RPC::ExtDirect->get_api->config;
+    $config->json_options({
+        allow_blessed => 1,
+    });
 
 =head1 METHODS
 
@@ -80,7 +79,7 @@ typing compatibility with Exception and Request objects.
 
 =item C<result>
 
-Instance method. Returns an Event hashref in format supported by
+Instance method. Returns an Event hashref in format required by
 Ext.Direct client stack. Not intended to be called directly.
 
 =back
@@ -65,19 +65,19 @@ however the particular details can differ significantly.
 
 To deal with the web server differences, RPC::ExtDirect adopted the
 core-periphery architecture. Transport layer core, provided by the
-L<RPC::ExtDirect> CPAN distribution, is complemented by a peripheral
-distribution called a I<gateway> that works with a particular web
-server environment. The gateway is responsible for implementing the
+L<RPC::ExtDirect> CPAN distribution, is complemented by peripheral
+distributions called I<gateways> that work with particular web
+server environments. The gateway is responsible for implementing the
 features missing in a web server interface, if any.
 
 Since the gateway modules implement the "lowest common denominator"
 abstraction layer, it is fairly easy to use an Ext.Direct application
 with several different gateways at the same time. The most common use
-for this is testing; the added benefit is that the application becomes
-independent of the web server environment and can be ported easily if
-such a need arises.
+case for this is unit testing; the added benefit is that the application
+becomes independent of the web server environment and can be ported
+easily if such a need arises.
 
-See L<RPC::ExtDirect/GATEWAYS> for a list of available gateways.
+See L<RPC::ExtDirect/GATEWAYS> for the list of available gateways.
 
 =head1 TERMINOLOGY
 
@@ -93,9 +93,9 @@ of L<remoting|/"Remoting API"> and L<polling|/"Polling API"> parts.
 =item API declaration
 
 JavaScript chunk that encodes L</API>. Usually generated
-by application server and retrieved by client once upon startup.
-Another option is to embed API declaration in client side application
-code.
+by application server and retrieved by client once upon startup;
+another popular option is to embed API declaration in client side
+application code at the build time (using Sencha Cmd).
 
 API declaration is generated by L<RPC::ExtDirect::API> module.
 
@@ -103,12 +103,13 @@ API declaration is generated by L<RPC::ExtDirect::API> module.
 
 The main part of the L</"API declaration">, it declares the L<Actions|/Action>
 and L<Methods|/Method> available to the client, as well as their calling
-patterns, and other parameters.
+conventions, and other parameters.
 
 =item Polling API
 
 Used to declare the existence of L<Event Providers|/"Event Provider"> and
-their credentials, basically the URI to use.
+their credentials, basically the URI to use when polling for
+L<Events|/Event>.
 
 =item Router
 
@@ -139,29 +140,46 @@ signature conforming to Method's declaration in the L</API>.
 
 =item Ordered Method
 
-A L</Method> that accepts zero or more parameters in ordered fashion, or by
-position (in a list). See more in
-L<RPC::ExtDirect/"METHODS AND CALLING CONVENTIONS">.
+A L</Method> that accepts zero or more parameters in ordered fashion, i.e.
+by position (in a list). Ordered Methods support call L</Metadata>.
+
+See more in L<RPC::ExtDirect/"METHODS AND CALLING CONVENTIONS">.
 
 =item Named Method
 
-A L</Method> that accepts parameters by name, in a hash. See more in
-L<RPC::ExtDirect/"METHODS AND CALLING CONVENTIONS">.
+A L</Method> that accepts parameters by name, in a hash. Named Methods
+support call L</Metadata>.
+
+See more in L<RPC::ExtDirect/"METHODS AND CALLING CONVENTIONS">.
 
 =item Form Handler Method
 
 A L</Method> that accepts form submits. All form field values are passed to
-the Form handler in a hash, C<field => value>. The only practical reason to
-use Form handlers is to process file uploads; see
-L<FILE UPLOADS|RPC::ExtDirect/"FILE UPLOADS"> for more information. See also
-L<RPC::ExtDirect/"METHODS AND CALLING CONVENTIONS"> for more information on
-Form handlers calling convention.
+the Form handler in a hash, C<< field => value >>. One practical reason
+to use Form handlers is to process file uploads; see
+L<FILE UPLOADS|RPC::ExtDirect/"FILE UPLOADS"> for more information. Note
+also that Form Handler Methods support call L</Metadata>.
+
+See also L<RPC::ExtDirect/"METHODS AND CALLING CONVENTIONS"> for more
+information on Form handlers calling convention.
 
 =item Poll Handler Method
 
 A L</Method> that is called by an L</"Event Provider"> to return the list of
-L<Events|/Event> to be passed on to the client side. See more in
-L<RPC::ExtDirect/"METHODS AND CALLING CONVENTIONS">.
+L<Events|/Event> to be passed on to the client side. Poll Handler Methods
+B<do not support> call L</Metadata>.
+
+See more in L<RPC::ExtDirect/"METHODS AND CALLING CONVENTIONS">.
+
+=item Metadata
+
+A set of extra arguments associated with the Method. Metadata is sent
+separately from the main arguments and can vary between invocations.
+Metadata can also follow a calling convention that is different from
+the main arguments, e.g. it is possible to have L</"Ordered Method">
+with Named metadata, and vice versa.
+
+See more in L<RPC::ExtDirect/"METHODS AND CALLING CONVENTIONS">.
 
 =item Result
 
@@ -181,7 +199,7 @@ only for catastrophic conditions. Nearest analog is status code 500
 for HTTP responses.
 
 Examples of Exceptions are: request JSON is broken and can't be decoded;
-called Method dies because of internall error; Result cannot be encoded
+called Method dies because of internal error; Result cannot be encoded
 in JSON, etc.
 
 =item Event
@@ -189,7 +207,7 @@ in JSON, etc.
 An asynchronous notification that can be generated by server side and
 passed to client side, resulting in some reaction. Events are useful
 for status updates, progress indicators and other predictably occuring
-conditions and events.
+conditions.
 
 =item Event Provider
 
@@ -243,14 +261,14 @@ over time. The only way to ensure that your server side API keeps
 working exactly as the client side expects it to is via continuous
 testing.
 
-However, testing a server side API with the actual JavaScript code
+However testing a server side API with the actual JavaScript code
 that will consume the API is a daunting task. You would need an
 instance of the Web server you are going to use in production, a
 headless Web browser, a ton of infrastructure to keep all this up
 to date; not even mentioning the time spent maintaining the whole
 setup. If this picture makes you cringe with frustration, that's
 totally understandable. But fear not, RPC::ExtDirect has a truly
-Perlish answer to this question (making hard things possible).
+Perlish answer to this problem (making hard things possible).
 
 L<Test::ExtDirect> is the recommended way to unit test your Ext.Direct
 code. To be precise, it is hardly right to call it unit testing when
@@ -279,7 +297,7 @@ for the C<add> subroutine created above looks in practice:
     
     is $result, 4, "addition result matches";
 
-Now, how hard is that?!
+Now how hard is that?!
 
 =head1 GOING FURTHER
 
@@ -318,6 +336,18 @@ turn off strict checking:
         ...
     }
 
+It is always a good idea to declare at least some mandatory parameters;
+however if you want all named arguments to be passed to your Method with
+no checking, use empty params array:
+
+    sub named_no_checks : ExtDirect(params => []) {
+        my ($class, %arg) = @_;
+        
+        my $foo = $arg{foo};
+        my $bar = $arg{bar};
+        ... # etc
+    }
+
 =head2 Form submits
 
 An Ext.Direct Method can be used to accept form submits in
@@ -335,6 +365,8 @@ non-HTML5 browsers (think IE9 and below):
         }
     }
 
+See more in L<RPC::ExtDirect/"FILE UPLOADS">.
+
 =head2 Handling errors
 
 When your server side code encounters an irrecoverable error, it is
@@ -352,8 +384,9 @@ but having two sets of logic would be unwieldy; RPC::ExtDirect compromises
 by sending generic exceptions "An error has occured" in production mode
 (default).
 
-To turn on global debugging, set the L<debug option|RPC::ExtDirect::Config/debug>
-in the L<global API instance's|RPC::ExtDirect::API/"GLOBAL API TREE INSTANCE">
+To turn on global debugging, set the
+L<debug option|RPC::ExtDirect::Config/debug> in the
+L<global API instance's|RPC::ExtDirect::API/"GLOBAL API TREE INSTANCE">
 Config:
 
     # Place this in the main app server code
@@ -362,7 +395,7 @@ Config:
     RPC::ExtDirect->get_api->config->debug(1);
 
 If you are comfortable with exposing internal details of your app server to
-the outside world even in production mode, turn on
+the outside world even in production mode (and you shouldn't be!), turn on
 L<verbose_exceptions|RPC::ExtDirect::Config/verbose_exceptions>:
 
     # This goes to the main app server, too
@@ -370,6 +403,9 @@ L<verbose_exceptions|RPC::ExtDirect::Config/verbose_exceptions>:
     
     RPC::ExtDirect->get_api->config->verbose_exceptions(1);
 
+Be aware that it is an B<extremely> bad idea because it can help malicious
+parties to find an attack vector to your application!
+
 =head2 Using environment objects
 
 Suppose that you want to restrict some parts of the API to be accessible
@@ -458,7 +494,92 @@ logging:
 
 Set this way, C<log_everything> will be called after every Ext.Direct
 Method invocation, even if a C<before> hook canceled it. See
-L<RPC::ExtDirect::API::Hook> for the gory detail.
+L<RPC::ExtDirect::API::Hook> for the gory details.
+
+=head1 Using call metadata
+
+Suppose you want to implement CRUD operations on a set of similar
+database tables. Instead of declaring a separate set of Methods
+for each table, you can use call metadata:
+
+    package MyApp::Crud;
+    
+    use RPC::ExtDirect Action => 'MyApp.Database';
+    
+    # Client will send an array of records as one positional argument
+    sub create : ExtDirect(1, metadata => {params => ['table'], arg => 9}) {
+        my ($class, $records, $meta) = @_;
+        
+        my $table_name = $meta->{table};
+        
+        # Insert the records
+        ...
+    }
+    
+    # Note that for Named methods the default argument is 'metadata'
+    sub read : ExtDirect(params => [], metadata => {params => ['table']}) {
+        my ($class, %arg) = @_;
+        
+        my $table_name = $arg{metadata}->{table};
+        
+        # Read the records
+        ...
+    }
+    
+    # Like with read, a single main argument contains records
+    sub update : ExtDirect(1, metadata => {params => ['table'], arg => 9}) {
+        my ($class, $records, $meta) = @_;
+        
+        my $table_name = $meta->{table};
+        
+        # Update the records
+        ...
+    }
+    
+    # Again a single main argument with array of records
+    sub delete : ExtDirect(1, metadata => {params => ['table'], arg => 9}) {
+        my ($class, $records, $meta) = @_;
+        
+        my $table_name = $meta->{table};
+        
+        # Delete the records
+        ...
+    }
+
+This can now be used easily on the client side (with Ext JS 5.1+):
+
+    var grid = new Ext.grid.Panel({
+        title: 'Ext.Direct grid',
+        
+        store: {
+            fields: ['frobbe', 'throbbe'],
+            proxy: {
+                type: 'direct',
+                api: {
+                    create:  'MyApp.Database.create',
+                    read:    'MyApp.Database.read',
+                    update:  'MyApp.Database.update',
+                    destroy: 'MyApp.Database.delete'
+                },
+                metadata: {
+                    table: 'foo'
+                }
+            }
+        },
+        
+        columns: [{
+            text: 'Frobbe',
+            dataIndex: 'frobbe'
+        }, {
+            text: 'Throbbe',
+            dataIndex: 'throbbe'
+        }],
+        
+        ...
+    });
+
+It may be also worth noting that L<RPC::ExtDirect::Client> supports
+call metadata fully as of version 1.10.
 
 =head1 SEE ALSO
 
@@ -466,3 +587,4 @@ Live code examples are provided with L<CGI::ExtDirect> module in the
 C<examples> directory.
 
 =cut
+
@@ -47,8 +47,8 @@ sub new {
     my $api    = delete $arg->{api}    || RPC::ExtDirect->get_api();
     my $config = delete $arg->{config} || RPC::ExtDirect::Config->new();
     
-    my $debug = defined $arg->{debug} ? delete $arg->{debug}
-              :                         $config->debug_request
+    my $debug = exists $arg->{debug} ? !!(delete $arg->{debug})
+              :                        $config->debug_request
               ;
 
     # Need blessed object to call private methods
@@ -59,7 +59,7 @@ sub new {
     }, $class;
 
     # Unpack and validate arguments
-    my ($action_name, $method_name, $tid, $data, $type, $upload)
+    my ($action_name, $method_name, $tid, $data, $type, $upload, $meta, $aux)
         = eval { $self->_unpack_arguments($arg) };
     
     return $self->_exception({
@@ -86,13 +86,14 @@ sub new {
         method_ref  => $method_ref,
         tid         => $tid,
         data        => $data,
+        metadata    => $meta,
     );
     
     return $exception if defined $exception;
     
     # Bulk assignment for brevity
-    @$self{ qw/ tid   type   data   upload   method_ref  run_count/ }
-        = (    $tid, $type, $data, $upload, $method_ref, 0 );
+    @$self{ qw/ tid type data metadata upload method_ref run_count aux / }
+        = ($tid, $type, $data, $meta, $upload, $method_ref, 0, $aux);
     
     # Finally, resolve the hooks; it's easier to do that upfront
     # since it involves API lookup
@@ -114,6 +115,13 @@ sub new {
 # Checks if method arguments are in order
 #
 
+my @checkers = qw/ check_method_arguments check_method_metadata /;
+
+my %checker_property = (
+    check_method_arguments => 'data',
+    check_method_metadata  => 'metadata',
+);
+
 sub check_arguments {
     my ($self, %arg) = @_;
     
@@ -121,7 +129,6 @@ sub check_arguments {
     my $method_name = $arg{method_name};
     my $method_ref  = $arg{method_ref};
     my $tid         = $arg{tid};
-    my $data        = $arg{data};
 
     # Event poll handlers return Event objects instead of plain data;
     # there is no sense in calling them directly
@@ -136,38 +143,45 @@ sub check_arguments {
         });
     }
 
-    # There's not much to check for formHandler methods
-    elsif ( $method_ref->formHandler ) {
-        if ( 'HASH' ne ref($data) || !exists $data->{extAction} ||
-             !exists $data->{extMethod} )
-        {
-            return $self->_exception({
-                action  => $action_name,
-                method  => $method_name,
-                tid     => $tid,
-                message => "ExtDirect formHandler method ".
-                           "$action_name.$method_name should only ".
-                           "be called with form submits"
-            })
-        }
-    }
-    
-    # The actual heavy lifting happens in the Method itself
     else {
-        local $@;
-        
-        my $result = eval { $method_ref->check_method_arguments($data) };
+        # One extra check for formHandlers
+        if ( $method_ref->formHandler ) {
+            my $data = $arg{data};
+            
+            if ( 'HASH' ne ref($data) || !exists $data->{extAction} ||
+                 !exists $data->{extMethod} )
+            {
+                return $self->_exception({
+                    action  => $action_name,
+                    method  => $method_name,
+                    tid     => $tid,
+                    message => "ExtDirect formHandler method ".
+                               "$action_name.$method_name should only ".
+                               "be called with form submits"
+                })
+            }
+        }
         
-        if ( my $error = $@ ) {
-            $error =~ s/\n$//;
+        # The actual heavy lifting happens in the Method itself
+        for my $checker ( @checkers ) {
+            my $what = $checker_property{ $checker };
+            my $have = $arg{ $what };
             
-            return $self->_exception({
-                action  => $action_name,
-                method  => $method_name,
-                tid     => $tid,
-                message => $error,
-                where   => ref($method_ref) .'->check_method_arguments',
-            });
+            local $@;
+            
+            eval { $method_ref->$checker($have) };
+            
+            if ( my $error = $@ ) {
+                $error =~ s/\n$//;
+            
+                return $self->_exception({
+                    action  => $action_name,
+                    method  => $method_name,
+                    tid     => $tid,
+                    message => $error,
+                    where   => ref($method_ref) ."->${checker}",
+                });
+            }
         }
     }
 
@@ -195,9 +209,10 @@ sub run {
 
     # Prepare the arguments
     my @method_arg = $method_ref->prepare_method_arguments(
-        env    => $env,
-        input  => $self->{data},
-        upload => $self->upload,
+        env      => $env,
+        input    => $self->{data},
+        upload   => $self->upload,
+        metadata => $self->metadata,
     );
     
     my %params = (
@@ -205,6 +220,8 @@ sub run {
         method_ref => $method_ref,
         env        => $env,
         arg        => \@method_arg,
+        metadata   => $self->metadata,
+        aux_data   => $self->aux,
     );
 
     my ($run_method, $callee, $result, $exception) = (1);
@@ -281,6 +298,8 @@ my $accessors = [qw/
     message
     upload
     run_count
+    metadata
+    aux
 /,
     __PACKAGE__->HOOK_TYPES,
 ];
@@ -362,15 +381,24 @@ sub _set_error {
 # Unpacks arguments into a list and validates them
 #
 
+my @std_keys = qw/
+    extAction action extMethod method extTID tid data metadata
+    extType type extUpload _uploads
+/;
+
 sub _unpack_arguments {
     my ($self, $arg) = @_;
 
     # Unpack and normalize arguments
     my $action = $arg->{extAction} || $arg->{action};
     my $method = $arg->{extMethod} || $arg->{method};
-    my $tid    = $arg->{extTID}    || $arg->{tid};
-    my $data   = $arg->{data}      || $arg;
+    my $tid    = $arg->{extTID}    || $arg->{tid}; # can't be 0
     my $type   = $arg->{type}      || 'rpc';
+    
+    # For a formHandler, the "data" field is the form itself;
+    # the arguments are fields in the form-encoded POST body
+    my $data   = $arg->{data} || $arg;
+    my $meta   = $arg->{metadata};
     my $upload = $arg->{extUpload} eq 'true' ? $arg->{_uploads}
                :                               undef
                ;
@@ -382,7 +410,19 @@ sub _unpack_arguments {
     die [ "ExtDirect method name required" ]
         unless defined $method && length $method > 0;
 
-    return ($action, $method, $tid, $data, $type, $upload);
+    my %arg_keys = map { $_ => 1, } keys %$arg;
+    delete @arg_keys{ @std_keys };
+
+    # Collect ancillary data that might be passed in the packet
+    # and make it available to the Hooks. This might be used e.g.
+    # for passing CSRF protection tokens, etc.
+    my %aux = map { $_ => $arg->{$_} } keys %arg_keys;
+    
+    my $aux_ref = %aux ? { %aux } : undef;
+
+    return (
+        $action, $method, $tid, $data, $type, $upload, $meta, $aux_ref
+    );
 }
 
 ### PRIVATE INSTANCE METHOD ###
@@ -29,13 +29,14 @@ my $tests = [{
     output => {
         status => 200,
         content_type => qr|^application/javascript\b|,
+        content_length => 1312,
         comparator => 'cmp_api',
         content => q~
             Ext.app.REMOTING_API = {
                 "actions": {
                 "Bar": [
                             { "len":5, "name":"bar_bar" },
-                            { "formHandler":true, "len":0, "name":"bar_baz" },
+                            { "formHandler":true, "name":"bar_baz" },
                             { "len":4, "name":"bar_foo" }
                        ],
                 "Foo": [
@@ -43,11 +44,31 @@ my $tests = [{
                             { "name":"foo_baz", "params":["foo","bar","baz"] },
                             { "len":1, "name":"foo_foo" },
                             { "len":0, "name":"foo_zero" },
-                            { "name":"foo_blessed" }
+                            { "name":"foo_blessed", "params":[], "strict":false }
                        ],
+                "Meta": [
+                        { "name":"arg0", "len":0, "metadata":{ "len":2 } },
+                        { "name":"arg1_last", "len":1, "metadata":{ "len":1 } },
+                        { "name":"arg1_first", "len":1, "metadata":{ "len":2 } },
+                        { "name":"arg2_last", "len":2, "metadata":{ "len":1 } },
+                        { "name":"arg2_middle", "len":2, "metadata":{ "len":2 } },
+                        { "name":"form_named", "formHandler": true,
+                          "metadata": { "params": [], "strict": false } },
+                        { "name": "form_ordered", "formHandler": true,
+                          "metadata": { "len": 1 } },
+                        { "name":"named_default", "params": [], "strict":false,
+                          "metadata": { "len": 1 } },
+                        { "name":"named_arg", "params": [], "strict":false,
+                          "metadata": { "len": 1 } },
+                        { "name":"named_strict", "params": [], "strict":false,
+                          "metadata": { "params": ["foo"] } },
+                        { "name":"named_unstrict", "params": [], "strict":false,
+                          "metadata": { "params": [], "strict": false } },
+                        { "name":"aux", "len":0 }
+                ],
                 "Qux": [
                             { "len":5, "name":"bar_bar" },
-                            { "formHandler":true, "len":0, "name":"bar_baz" },
+                            { "formHandler":true, "name":"bar_baz" },
                             { "len":4, "name":"bar_foo" },
                             { "len":2, "name":"foo_bar" },
                             { "name":"foo_baz", "params":["foo","bar","baz"] },
@@ -84,13 +105,14 @@ my $tests = [{
     output => {
         status => 200,
         content_type => qr|^application/javascript\b|,
+        content_length => 1382,
         comparator => 'cmp_api',
         content => q~
             Ext.app.REMOTE_CALL = {
                 "actions": {
                 "Bar": [
                             { "len":5, "name":"bar_bar" },
-                            { "formHandler":true, "len":0, "name":"bar_baz" },
+                            { "formHandler":true, "name":"bar_baz" },
                             { "len":4, "name":"bar_foo" }
                        ],
                 "Foo": [
@@ -98,11 +120,31 @@ my $tests = [{
                             { "name":"foo_baz", "params":["foo","bar","baz"] },
                             { "len":1, "name":"foo_foo" },
                             { "len":0, "name":"foo_zero" },
-                            { "name":"foo_blessed" }
+                            { "name":"foo_blessed", "params":[], "strict":false }
                        ],
+                "Meta": [
+                        { "name":"arg0", "len":0, "metadata":{ "len":2 } },
+                        { "name":"arg1_last", "len":1, "metadata":{ "len":1 } },
+                        { "name":"arg1_first", "len":1, "metadata":{ "len":2 } },
+                        { "name":"arg2_last", "len":2, "metadata":{ "len":1 } },
+                        { "name":"arg2_middle", "len":2, "metadata":{ "len":2 } },
+                        { "name":"form_named", "formHandler": true,
+                          "metadata": { "params": [], "strict": false } },
+                        { "name": "form_ordered", "formHandler": true,
+                          "metadata": { "len": 1 } },
+                        { "name":"named_default", "params": [], "strict":false,
+                          "metadata": { "len": 1 } },
+                        { "name":"named_arg", "params": [], "strict":false,
+                          "metadata": { "len": 1 } },
+                        { "name":"named_strict", "params": [], "strict":false,
+                          "metadata": { "params": ["foo"] } },
+                        { "name":"named_unstrict", "params": [], "strict":false,
+                          "metadata": { "params": [], "strict": false } },
+                        { "name":"aux", "len":0 }
+                ],
                 "Qux": [
                             { "len":5, "name":"bar_bar" },
-                            { "formHandler":true, "len":0, "name":"bar_baz" },
+                            { "formHandler":true, "name":"bar_baz" },
                             { "len":4, "name":"bar_foo" },
                             { "len":2, "name":"foo_bar" },
                             { "name":"foo_baz", "params":["foo","bar","baz"] },
@@ -142,13 +184,14 @@ my $tests = [{
     output => {
         status => 200,
         content_type => qr|^application/javascript\b|,
+        content_length => 1394,
         comparator => 'cmp_api',
         content => q~
             Ext.app.CALL = {
                 "actions": {
                 "Bar": [
                             { "len":5, "name":"bar_bar" },
-                            { "formHandler":true, "len":0, "name":"bar_baz" },
+                            { "formHandler":true, "name":"bar_baz" },
                             { "len":4, "name":"bar_foo" }
                        ],
                 "Foo": [
@@ -156,11 +199,31 @@ my $tests = [{
                             { "name":"foo_baz", "params":["foo","bar","baz"] },
                             { "len":1, "name":"foo_foo" },
                             { "len":0, "name":"foo_zero" },
-                            { "name":"foo_blessed" }
+                            { "name":"foo_blessed", "params":[], "strict":false }
                        ],
+                "Meta": [
+                        { "name":"arg0", "len":0, "metadata":{ "len":2 } },
+                        { "name":"arg1_last", "len":1, "metadata":{ "len":1 } },
+                        { "name":"arg1_first", "len":1, "metadata":{ "len":2 } },
+                        { "name":"arg2_last", "len":2, "metadata":{ "len":1 } },
+                        { "name":"arg2_middle", "len":2, "metadata":{ "len":2 } },
+                        { "name":"form_named", "formHandler": true,
+                          "metadata": { "params": [], "strict": false } },
+                        { "name": "form_ordered", "formHandler": true,
+                          "metadata": { "len": 1 } },
+                        { "name":"named_default", "params": [], "strict":false,
+                          "metadata": { "len": 1 } },
+                        { "name":"named_arg", "params": [], "strict":false,
+                          "metadata": { "len": 1 } },
+                        { "name":"named_strict", "params": [], "strict":false,
+                          "metadata": { "params": ["foo"] } },
+                        { "name":"named_unstrict", "params": [], "strict":false,
+                          "metadata": { "params": [], "strict": false } },
+                        { "name":"aux", "len":0 }
+                ],
                 "Qux": [
                             { "len":5, "name":"bar_bar" },
-                            { "formHandler":true, "len":0, "name":"bar_baz" },
+                            { "formHandler":true, "name":"bar_baz" },
                             { "len":4, "name":"bar_foo" },
                             { "len":2, "name":"foo_bar" },
                             { "name":"foo_baz", "params":["foo","bar","baz"] },
@@ -39,15 +39,18 @@ my $tests = [{
         status => 200,
         content_type => qr|^application/json\b|,
         comparator => 'cmp_json',
+        cgi_content_length => 167,
         cgi_content =>
             q|{"action":"Env","method":"http_list","result":|.
             q|["http_accept","http_accept_charset","http_connection",|.
             q|"http_cookie","http_host","http_user_agent"],|.
             q|"tid":1,"type":"rpc"}|,
+        plack_content_length => 117,
         plack_content =>
             q|{"action":"Env","method":"http_list","result":|.
             q|["content-length","content-type","cookie","host"],|.
             q|"tid":1,"type":"rpc"}|,
+        anyevent_content_length => 110,
         anyevent_content =>
             q|{"action":"Env","method":"http_list","result":|.
             q|["content-length","content-type","cookie"],|.
@@ -95,10 +98,12 @@ my $tests = [{
         status => 200,
         content_type => qr|^application/json\b|,
         comparator => 'cmp_json',
+        cgi_content_length => 81,
         cgi_content => 
             q|{"action":"Env","method":"http_header","result":|.
             q|"CGI::Test",|.
             q|"tid":1,"type":"rpc"}|,
+        content_length => 88,
         content =>
             q|{"action":"Env","method":"http_header","result":|.
             q|"application/json",|.
@@ -137,10 +142,12 @@ my $tests = [{
         status => 200,
         content_type => qr|^application/json\b|,
         comparator => 'cmp_json',
+        anyevent_content_length => 71,
         anyevent_content =>
             q|{"action":"Env","method":"param_list","result":|.
             q|[],|.
             q|"tid":1,"type":"rpc"}|,
+        content_length => 81,
         content =>
             q|{"action":"Env","method":"param_list","result":|.
             q|["postdata"],|.
@@ -179,10 +186,12 @@ my $tests = [{
         status => 200,
         content_type => qr|^application/json\b|,
         comparator => 'cmp_json',
+        anyevent_content_length => 72,
         anyevent_content =>
             q|{"action":"Env","method":"param_get","result":|.
             q|null,|.
             q|"tid":1,"type":"rpc"}|,
+        content_length => 167,
         content =>
             q|{"action":"Env","method":"param_get","result":|.
             q|"{\"type\":\"rpc\",\"tid\":1,\"action\":\"Env\",\"method\":\"param_get\",\"data\":[\"POSTDATA\"]}",|.
@@ -221,6 +230,7 @@ my $tests = [{
         status => 200,
         content_type => qr|^application/json\b|,
         comparator => 'cmp_json',
+        content_length => 77,
         content =>
             q|{"action":"Env","method":"cookie_list","result":|.
             q|["foo"],|.
@@ -259,6 +269,7 @@ my $tests = [{
         status => 200,
         content_type => qr|^application/json\b|,
         comparator => 'cmp_json',
+        content_length => 74,
         content =>
             q|{"action":"Env","method":"cookie_get","result":|.
             q|"bar",|.
@@ -28,6 +28,7 @@ my $tests = [{
     output => {
         status => 200,
         content_type => qr|^application/json\b|,
+        content_length => 109,
         comparator => 'cmp_json',
         content => 
             q|[{"data":["foo"],|.
@@ -59,6 +60,7 @@ my $tests = [{
     output => {
         status => 200,
         content_type => qr|^application/json\b|,
+        content_length => 65,
         comparator => 'cmp_json',
         content =>
             q|{"data":"Uno cappuccino, presto!",|.
@@ -87,6 +89,7 @@ my $tests = [{
     output => {
         status => 200,
         content_type => qr|^application/json\b|,
+        content_length => 44,
         comparator => 'cmp_json',
         content => q|{"data":"","name":"__NONE__","type":"event"}|,
     },
@@ -112,6 +115,7 @@ my $tests = [{
     output => {
         status => 200,
         content_type => qr|^application/json\b|,
+        content_length => 44,
         comparator => 'cmp_json',
         content => q|{"data":"","name":"__NONE__","type":"event"}|,
     },
@@ -137,6 +141,7 @@ my $tests = [{
     output => {
         status => 200,
         content_type => qr|^application/json\b|,
+        content_length => 44,
         comparator => 'cmp_json',
         content => q|{"data":"","name":"__NONE__","type":"event"}|,
     },
@@ -33,6 +33,7 @@ my $tests = [{
     output => {
         status => 200,
         content_type => qr|^application/json\b|,
+        content_length => 249,
         comparator => 'cmp_json',
         content => 
             q|{"action":null,"message":"ExtDirect error decoding POST data: |.
@@ -69,6 +70,7 @@ my $tests = [{
     output => {
         status => 200,
         content_type => qr|^application/json\b|,
+        content_length => 78,
         comparator => 'cmp_json',
         content => 
             q|{"action":"Foo","method":"foo_foo",|.
@@ -107,6 +109,7 @@ my $tests = [{
     output => {
         status => 200,
         content_type => qr|^application/json\b|,
+        content_length => 304,
         comparator => 'cmp_str',
         content => 
             q|[{"action":"Qux","method":"foo_foo",|.
@@ -118,6 +121,445 @@ my $tests = [{
             q|"msg":"foo! bar! baz!"},"tid":3,"type":"rpc"}]|,
     },
 }, {
+    name => 'Valid POST with invalid metadata 1',
+    
+    config => {
+        api_path => '/api',
+        router_path => '/router',
+        poll_path => '/events',
+        debug => 1,
+    },
+    
+    input => {
+        method => 'POST',
+        url => '/router',
+        cgi_url => '/router4',
+        
+        content => {
+            type => 'raw_post',
+            arg => [
+                'http://localhost/router',
+                q|{"type":"rpc","tid":1,"action":"Meta",|.
+                q|"method":"arg0","data":null,|.
+                q|"metadata":[42]}|,
+            ],
+        },
+    },
+    
+    output => {
+        status => 200,
+        content_type => qr|^application/json\b|,
+        content_length => 213,
+        comparator => 'cmp_str',
+        content =>
+            q|{"action":"Meta",|.
+            q|"message":"ExtDirectMethodMeta.arg0requires|.
+            q|2metadatavalue(s)butonly1areprovided",|.
+            q|"method":"arg0","tid":1,"type":"exception",|.
+            q|"where":"RPC::ExtDirect::API::Method->check_method_metadata"}|,
+    },
+}, {
+    name => 'Valid POST with metadata 1',
+    
+    config => {
+        api_path => '/api',
+        router_path => '/router',
+        poll_path => '/events',
+        debug => 1,
+    },
+    
+    input => {
+        method => 'POST',
+        url => '/router',
+        cgi_url => '/router4',
+        
+        content => {
+            type => 'raw_post',
+            arg => [
+                'http://localhost/router',
+                q|{"type":"rpc","tid":1,"action":"Meta",|.
+                q|"method":"arg0","data":null,|.
+                q|"metadata":[42,"foo"]}|,
+            ],
+        },
+    },
+    
+    output => {
+        status => 200,
+        content_type => qr|^application/json\b|,
+        content_length => 83,
+        comparator => 'cmp_str',
+        content =>
+            q|{"action":"Meta","method":"arg0",|.
+            q|"result":{"meta":[42,"foo"]},"tid":1,|.
+            q|"type":"rpc"}|,
+    },
+}, {
+    name => 'Valid POST with metadata 2',
+    
+    config => {
+        api_path => '/api',
+        router_path => '/router',
+        poll_path => '/events',
+        debug => 1,
+    },
+    
+    input => {
+        method => 'POST',
+        url => '/router',
+        cgi_url => '/router4',
+        
+        content => {
+            type => 'raw_post',
+            arg => [
+                'http://localhost/router',
+                q|{"type":"rpc","tid":1,"action":"Meta",|.
+                q|"method":"arg1_last","data":["foo"],|.
+                q|"metadata":[42]}|,
+            ],
+        },
+    },
+    
+    output => {
+        status => 200,
+        content_type => qr|^application/json\b|,
+        content_length => 95,
+        comparator => 'cmp_str',
+        content =>
+            q|{"action":"Meta","method":"arg1_last",|.
+            q|"result":{"arg1":"foo","meta":[42]},|.
+            q|"tid":1,"type":"rpc"}|,
+    },
+}, {
+    name => 'Valid POST with metadata 3',
+    
+    config => {
+        api_path => '/api',
+        router_path => '/router',
+        poll_path => '/events',
+        debug => 1,
+    },
+    
+    input => {
+        method => 'POST',
+        url => '/router',
+        cgi_url => '/router4',
+        
+        content => {
+            type => 'raw_post',
+            arg => [
+                'http://localhost/router',
+                q|{"type":"rpc","tid":1,"action":"Meta",|.
+                q|"method":"arg1_first","data":["foo"],|.
+                q|"metadata":[42,43]}|,
+            ],
+        },
+    },
+    
+    output => {
+        status => 200,
+        content_type => qr|^application/json\b|,
+        content_length => 99,
+        comparator => 'cmp_str',
+        content =>
+            q|{"action":"Meta","method":"arg1_first",|.
+            q|"result":{"arg1":"foo","meta":[42,43]},|.
+            q|"tid":1,"type":"rpc"}|,
+    },
+}, {
+    name => 'Valid POST with metadata 4',
+    
+    config => {
+        api_path => '/api',
+        router_path => '/router',
+        poll_path => '/events',
+        debug => 1,
+    },
+    
+    input => {
+        method => 'POST',
+        url => '/router',
+        cgi_url => '/router4',
+        
+        content => {
+            type => 'raw_post',
+            arg => [
+                'http://localhost/router',
+                q|{"type":"rpc","tid":1,"action":"Meta",|.
+                q|"method":"arg2_last","data":[42,43],|.
+                q|"metadata":["foo","bar"]}|,
+            ],
+        },
+    },
+    
+    output => {
+        status => 200,
+        content_type => qr|^application/json\b|,
+        content_length => 105,
+        comparator => 'cmp_str',
+        content =>
+            q|{"action":"Meta","method":"arg2_last",|.
+            q|"result":{"arg1":42,"arg2":43,"meta":["foo"]},|.
+            q|"tid":1,"type":"rpc"}|,
+    },
+}, {
+    name => 'Valid POST with metadata 5',
+    
+    config => {
+        api_path => '/api',
+        router_path => '/router',
+        poll_path => '/events',
+        debug => 1,
+    },
+    
+    input => {
+        method => 'POST',
+        url => '/router',
+        cgi_url => '/router4',
+        
+        content => {
+            type => 'raw_post',
+            arg => [
+                'http://localhost/router',
+                q|{"type":"rpc","tid":1,"action":"Meta",|.
+                q|"method":"arg2_middle","data":[44,45],|.
+                q|"metadata":["fred","bonzo","qux"]}|,
+            ],
+        },
+    },
+    
+    output => {
+        status => 200,
+        content_type => qr|^application/json\b|,
+        content_length => 116,
+        comparator => 'cmp_str',
+        content =>
+            q|{"action":"Meta","method":"arg2_middle",|.
+            q|"result":{"arg1":44,"arg2":45,"meta":|.
+            q|["fred","bonzo"]},"tid":1,"type":"rpc"}|,
+    },
+}, {
+    name => 'Valid POST with metadata 6',
+    
+    config => {
+        api_path => '/api',
+        router_path => '/router',
+        poll_path => '/events',
+        debug => 1,
+    },
+    
+    input => {
+        method => 'POST',
+        url => '/router',
+        cgi_url => '/router4',
+        
+        content => {
+            type => 'raw_post',
+            arg => [
+                'http://localhost/router',
+                q|{"type":"rpc","tid":1,"action":"Meta",|.
+                q|"method":"named_default",|.
+                q|"data":{"foo":"bar","fred":"bonzo"},|.
+                q|"metadata":[42]}|,
+            ],
+        },
+    },
+    
+    output => {
+        status => 200,
+        content_type => qr|^application/json\b|,
+        content_length => 113,
+        comparator => 'cmp_str',
+        content =>
+            q|{"action":"Meta","method":"named_default",|.
+            q|"result":{"foo":"bar","fred":"bonzo",|.
+            q|"meta":[42]},"tid":1,"type":"rpc"}|,
+    },
+}, {
+    name => 'Valid POST with metadata 7',
+    
+    config => {
+        api_path => '/api',
+        router_path => '/router',
+        poll_path => '/events',
+        debug => 1,
+    },
+    
+    input => {
+        method => 'POST',
+        url => '/router',
+        cgi_url => '/router4',
+        
+        content => {
+            type => 'raw_post',
+            arg => [
+                'http://localhost/router',
+                q|{"type":"rpc","tid":1,"action":"Meta",|.
+                q|"method":"named_arg",|.
+                q|"data":{"qux":"fred"},|.
+                q|"metadata":["blerg"]}|,                
+            ],
+        },
+    },
+    
+    output => {
+        status => 200,
+        content_type => qr|^application/json\b|,
+        content_length => 100,
+        comparator => 'cmp_str',
+        content =>
+            q|{"action":"Meta","method":"named_arg",|.
+            q|"result":{"meta":["blerg"],"qux":"fred"},|.
+            q|"tid":1,"type":"rpc"}|,
+    },
+}, {
+    name => 'Valid POST with metadata 8',
+    
+    config => {
+        api_path => '/api',
+        router_path => '/router',
+        poll_path => '/events',
+        debug => 1,
+    },
+    
+    input => {
+        method => 'POST',
+        url => '/router',
+        cgi_url => '/router4',
+        
+        content => {
+            type => 'raw_post',
+            arg => [
+                'http://localhost/router',
+                q|{"type":"rpc","tid":1,"action":"Meta",|.
+                q|"method":"named_arg",|.
+                q|"data":{"foo":"bar"},|.
+                q|"metadata":["blerg"]}|,
+            ],
+        },
+    },
+    
+    output => {
+        status => 200,
+        content_type => qr|^application/json\b|,
+        content_length => 87,
+        comparator => 'cmp_str',
+        content =>
+            q|{"action":"Meta","method":"named_arg",|.
+            q|"result":{"meta":["blerg"]},|.
+            q|"tid":1,"type":"rpc"}|,
+    },
+}, {
+    name => 'Valid POST with metadata 9',
+    
+    config => {
+        api_path => '/api',
+        router_path => '/router',
+        poll_path => '/events',
+        debug => 1,
+    },
+    
+    input => {
+        method => 'POST',
+        url => '/router',
+        cgi_url => '/router4',
+        
+        content => {
+            type => 'raw_post',
+            arg => [
+                'http://localhost/router',
+                q|{"type":"rpc","tid":1,"action":"Meta",|.
+                q|"method":"named_strict",|.
+                q|"data":{"frob":"dux","frogg":"bonzo"},|.
+                q|"metadata":{"foo":{"bar":{"baz":42}}}}|,
+            ],
+        },
+    },
+    
+    output => {
+        status => 200,
+        content_type => qr|^application/json\b|,
+        content_length => 136,
+        comparator => 'cmp_str',
+        content =>
+            q|{"action":"Meta","method":"named_strict",|.
+            q|"result":{"frob":"dux","frogg":"bonzo",|.
+            q|"meta":{"foo":{"bar":{"baz":42}}}},|.
+            q|"tid":1,"type":"rpc"}|,
+    },
+}, {
+    name => 'Valid POST with metadata 10',
+    
+    config => {
+        api_path => '/api',
+        router_path => '/router',
+        poll_path => '/events',
+        debug => 1,
+    },
+    
+    input => {
+        method => 'POST',
+        url => '/router',
+        cgi_url => '/router4',
+        
+        content => {
+            type => 'raw_post',
+            arg => [
+                'http://localhost/router',
+                q|{"type":"rpc","tid":1,"action":"Meta",|.
+                q|"method":"named_unstrict",|.
+                q|"data":{"qux":null},"metadata":{}}|,
+            ],
+        },
+    },
+    
+    output => {
+        status => 200,
+        content_type => qr|^application/json\b|,
+        content_length => 96,
+        comparator => 'cmp_str',
+        content =>
+            q|{"action":"Meta","method":"named_unstrict",|.
+            q|"result":{"meta":{},"qux":null},|.
+            q|"tid":1,"type":"rpc"}|,
+    },
+}, {
+    name => 'Valid POST with ancillary properties',
+    
+    config => {
+        api_path => '/api',
+        router_path => '/router',
+        poll_path => '/events',
+        debug => 1,
+    },
+    
+    input => {
+        method => 'POST',
+        url => '/router',
+        cgi_url => '/router4',
+        
+        content => {
+            type => 'raw_post',
+            arg => [
+                'http://localhost/router',
+                q|{"type":"rpc","tid":1,"action":"Meta",|.
+                q|"method":"aux","data":null,"foo":"bar",|.
+                q|"token":"kaboom!"}|,
+            ],
+        },
+    },
+    
+    output => {
+        status => 200,
+        content_type => qr|^application/json\b|,
+        content_length => 102,
+        comparator => 'cmp_str',
+        content =>
+            q|{"action":"Meta","method":"aux",|.
+            q|"result":{"aux":{"foo":"bar","token":"kaboom!"}},|.
+            q|"tid":1,"type":"rpc"}|,
+    },
+}, {
     name => 'Form request, no uploads',
     
     config => {
@@ -151,6 +593,7 @@ my $tests = [{
     output => {
         status => 200,
         content_type => qr|^application/json\b|,
+        content_length => 99,
         comparator => 'cmp_str',
         content =>
             q|{"action":"Bar","method":"bar_baz",|.
@@ -158,6 +601,148 @@ my $tests = [{
             q|"tid":123,"type":"rpc"}|,
     },
 }, {
+    name => 'Form request with ordered metadata',
+    
+    config => {
+        api_path => '/api',
+        router_path => '/router',
+        poll_path => '/events',
+        debug => 1,
+    },
+    
+    input => {
+        method => 'POST',
+        url => '/router',
+        cgi_url => '/router4',
+    
+        content => {
+            type => 'form_post',
+            arg  => [
+                'http://localhost/router',
+                action => '/router.cgi',
+                method => 'POST',
+                extAction => 'Meta',
+                extMethod => 'form_ordered',
+                extType => 'rpc',
+                extTID => 42,
+                fred => 'frob',
+                
+                # Client sends JSON encoded metadata with forms
+                metadata => '[42]',
+            ],
+        },
+    },
+    
+    output => {
+        status => 200,
+        content_type => qr|^application/json\b|,
+        content_length => 104,
+        comparator => 'cmp_str',
+        content =>
+            q|{"action":"Meta","method":"form_ordered",|.
+            q|"result":{"fred":"frob","metadata":[42]},|.
+            q|"tid":42,"type":"rpc"}|,
+    },
+}, {
+    name => 'Form request with named metadata',
+    
+    config => {
+        api_path => '/api',
+        router_path => '/router',
+        poll_path => '/events',
+        debug => 1,
+    },
+    
+    input => {
+        method => 'POST',
+        url => '/router',
+        cgi_url => '/router4',
+    
+        content => {
+            type => 'form_post',
+            arg  => [
+                'http://localhost/router',
+                action => '/router.cgi',
+                method => 'POST',
+                extAction => 'Meta',
+                extMethod => 'form_named',
+                extType => 'rpc',
+                extTID => 58,
+                frogg => 'splurge',
+                boogaloo => 1916,
+
+                # Client sends JSON encoded metadata with forms
+                metadata => '{"foo":1,"bar":2,"baz":3}',
+            ],
+        },
+    },
+    
+    output => {
+        status => 200,
+        content_type => qr|^application/json\b|,
+        content_length => 139,
+        comparator => 'cmp_str',
+        
+        # Note that we send a number in boogaloo field above
+        # but expect a string in return. This is due to the nature
+        # of form POST submits; in either URL encoding or form-multipart
+        # the data is sent as 
+        content =>
+            q|{"action":"Meta","method":"form_named",|.
+            q|"result":{"_m":{"bar":2,"baz":3,"foo":1},|.
+            q|"boogaloo":"1916","frogg":"splurge"},|.
+            q|"tid":58,"type":"rpc"}|,
+    },
+}, {
+    name => 'Form request with named metadata, multipart encoded',
+    
+    config => {
+        api_path => '/api',
+        router_path => '/router',
+        poll_path => '/events',
+        debug => 1,
+    },
+    
+    input => {
+        method => 'POST',
+        url => '/router',
+        cgi_url => '/router4',
+    
+        content => {
+            type => 'form_upload',
+            arg  => [
+                'http://localhost/router',
+                [],
+                action => '/router.cgi',
+                method => 'POST',
+                extAction => 'Meta',
+                extMethod => 'form_named',
+                extType => 'rpc',
+                extUpload => 'true',
+                extTID => 63,
+                frogg => 'splurge',
+                boogaloo => 1916,
+
+                # Client sends JSON encoded metadata with forms
+                metadata => '{"foo":1,"bar":2,"baz":3}',
+            ],
+        },
+    },
+    
+    output => {
+        status => 200,
+        content_type => qr|^text/html\b|,
+        content_length => 186,
+        comparator => 'cmp_str',
+        content =>
+            q|<html><body><textarea>|.
+            q|{"action":"Meta","method":"form_named",|.
+            q|"result":{"_m":{"bar":2,"baz":3,"foo":1},|.
+            q|"boogaloo":"1916","frogg":"splurge"},|.
+            q|"tid":63,"type":"rpc"}|.
+            q|</textarea></body></html>|,
+    },
+}, {
     name => 'Form request, one upload',
     
     config => {
@@ -193,6 +778,7 @@ my $tests = [{
     output => {
         status => 200,
         content_type => qr|^text/html\b|,
+        content_length => 253,
         comparator => 'cmp_str',
         content =>
             q|<html><body><textarea>|.
@@ -241,6 +827,7 @@ my $tests = [{
     output => {
         status => 200,
         content_type => qr|^text/html\b|,
+        content_length => 321,
         comparator => 'cmp_str',
         content =>
             q|<html><body><textarea>|.
@@ -31,7 +31,7 @@ sub bar_foo : ExtDirect(4) { croak 'bar foo!' }
 sub bar_bar : ExtDirect(5) { shift; return scalar @_; }
 
 # This is a form handler
-sub bar_baz : ExtDirect( formHandler ) {
+sub bar_baz : ExtDirect(formHandler, decode_params => [qw/frob guzzard/]) {
     my ($class, %param) = @_;
 
     # Simulate uploaded file handling
@@ -32,7 +32,7 @@ sub bar_foo : ExtDirect(4) { croak 'bar foo!' }
 sub bar_bar : ExtDirect(5) { shift; pop; return scalar @_; }
 
 # This is a form handler
-sub bar_baz : ExtDirect( formHandler ) {
+sub bar_baz : ExtDirect(formHandler) {
     my ($class, %param) = @_;
 
     # We don't use the env object here, but have to remove it
@@ -51,7 +51,7 @@ sub bar_baz : ExtDirect( formHandler ) {
         my $size = $upload->{size};
         
         #
-        # CTI::Test somehow uploads files so that
+        # CGI::Test somehow uploads files so that
         # they are 2 bytes shorter than actual size
         # This allows for the same test results to be
         # applied across all gateways and test frameworks
@@ -0,0 +1,113 @@
+#
+# WARNING WARNING WARNING
+#
+# DO NOT CHANGE ANYTHING IN THIS MODULE. OTHERWISE, A LOT OF API 
+# AND OTHER TESTS MAY BREAK.
+#
+# This module is here to test certain behaviors. If you need
+# to test something else, add another test module.
+# It's that simple.
+#
+
+# This does not need to be indexed by PAUSE
+package
+    RPC::ExtDirect::Test::Pkg::Meta;
+
+use strict;
+use warnings;
+
+use RPC::ExtDirect;
+
+sub arg0 : ExtDirect(0, metadata => { len => 2, arg => 0 }) {
+    my ($class, $meta) = @_;
+
+    return { meta => $meta };
+}
+
+sub arg1_last : ExtDirect(1, metadata => { len => 1, arg => 99, }) {
+    my ($class, $arg1, $meta) = @_;
+
+    return { arg1 => $arg1, meta => $meta };
+}
+
+sub arg1_first : ExtDirect(1, metadata => { len => 2, arg => 0 }) {
+    my ($class, $meta, $arg1) = @_;
+    
+    return { arg1 => $arg1, meta => $meta };
+}
+
+sub arg2_last : ExtDirect(2, metadata => { len => 1, arg => 99, }) {
+    my ($class, $arg1, $arg2, $meta) = @_;
+
+    return { arg1 => $arg1, arg2 => $arg2, meta => $meta };
+}
+
+sub arg2_middle : ExtDirect(2, metadata => { len => 2, arg => 1 }) {
+    my ($class, $arg1, $meta, $arg2) = @_;
+
+    return { arg1 => $arg1, arg2 => $arg2, meta => $meta };
+}
+
+sub named_default : ExtDirect(params => [], metadata => { len => 1 }) {
+    my ($class, %arg) = @_;
+
+    my $meta = delete $arg{metadata};
+
+    return { %arg, meta => $meta };
+}
+
+# One line declarations are intentional; Perls below 5.12 have trouble
+# parsing attributes spanning multiple lines
+sub named_arg : ExtDirect(params => [], metadata => { len => 1, arg => 'foo' }) {
+    my ($class, %arg) = @_;
+
+    my $meta = delete $arg{foo};
+
+    return { %arg, meta => $meta };
+}
+
+sub named_strict : ExtDirect(params => [], metadata => { params => ['foo'] }) {
+    my ($class, %arg) = @_;
+    
+    my $meta = delete $arg{metadata};
+
+    return { %arg, meta => $meta };
+}
+
+sub named_unstrict : ExtDirect(params => [], metadata => { params => [], strict => !1, arg => '_meta' }) {
+    my ($class, %arg) = @_;
+
+    my $meta = delete $arg{_meta};
+
+    return { %arg, meta => $meta };
+}
+
+sub form_ordered : ExtDirect(formHandler, metadata => { len => 1 }) {
+    my ($class, %arg) = @_;
+    
+    return { %arg };
+}
+
+sub form_named : ExtDirect(formHandler, metadata => { arg => '_m', strict => !1, }) {
+    my ($class, %arg) = @_;
+    
+    return { %arg };
+}
+
+sub aux_hook {
+    my ($class, %arg) = @_;
+    
+    my $method_arg = $arg{arg};
+    
+    push @$method_arg, $arg{aux_data};
+    
+    return 1;
+}
+
+sub aux : ExtDirect(0, before => \&aux_hook) {
+    my ($class, $aux) = @_;
+    
+    return { aux => $aux };
+}
+
+1;
@@ -5,6 +5,7 @@ use warnings;
 no  warnings 'uninitialized';       ## no critic
 
 use Carp;
+use JSON;
 
 use base 'Exporter';
 
@@ -220,9 +221,8 @@ sub parse_attribute {
             my $arg_names = shift @$data;
 
             croak "ExtDirect attribute 'params' must be followed by ".
-                  "arrayref containing at least one parameter name ".
-                  "at $file line $line"
-                if ref $arg_names ne 'ARRAY' || @$arg_names < 1;
+                  "arrayref at $file line $line"
+                if ref $arg_names ne 'ARRAY';
 
             # Copy the names
             $attr{params} = [ @{ $arg_names } ];
@@ -272,4 +272,33 @@ sub parse_attribute {
     };
 }
 
+### NON-EXPORTED PUBLIC PACKAGE SUBROUTINE ###
+#
+# Decode metadata sent by the client. This function changes
+# the passed hashref in situ (so has side effects).
+#
+
+sub decode_metadata {
+    # This is a bit hacky but will do
+    my ($self, $keywords) = @_;
+
+    my $meta_encoded = $keywords->{metadata};
+
+    if ( defined $meta_encoded ) {
+        # Whoever sends *multiple* metadata fields is going to regret it.
+        my $meta_json = 'ARRAY' eq ref $meta_encoded ? pop @$meta_encoded
+                      :                                $meta_encoded
+                      ;
+
+        local $@;
+        $keywords->{metadata} = eval { JSON::from_json($meta_json) };
+
+        if ( $@ ) {
+            my $error = clean_error_message($@);
+            $self->set_error("Invalid metadata: $error");
+        }
+    }
+}
+
 1;
+
@@ -17,7 +17,7 @@ use RPC::ExtDirect::Util;
 # at the end.
 #
 
-our $VERSION = '3.02';
+our $VERSION = '3.21';
 
 ### PACKAGE GLOBAL VARIABLE ###
 #
@@ -104,8 +104,9 @@ sub import {
     sub UNIVERSAL::ExtDirect : ATTR(CODE,$phase) {
         my \$attr = RPC::ExtDirect::Util::parse_attribute(\@_);
         
-        ${pkg}->add_method(\$attr);
-    }
+        eval { ${pkg}->add_method(\$attr) };
+
+        if (\$@) { die 'ARRAY' eq ref(\$@) ? \$\@->[0] : \$@ }; };
 END
 }
 
@@ -19,13 +19,13 @@ RPC::ExtDirect - Easily integrate Perl server code with JavaScript apps
     
     use RPC::ExtDirect Action => 'FooBar';
     
-    sub sum : ExtDirect( len => 2 ) {
+    sub sum : ExtDirect(len => 2) {
         my ($class, $a, $b) = @_;
         
         return $a + $b;
     }
     
-    sub login : ExtDirect( params => [qw/ user pass /] ) {
+    sub login : ExtDirect(params => ['user, 'pass']) {
         my ($class, %arg) = @_;
         
         if ( $arg{user} eq 'foo' && $arg{pass} eq 'bar' ) {
@@ -85,6 +85,11 @@ API declaration
 
 =item *
 
+Support for call metadata that is handled separately from the arguments
+for method invocation
+
+=item *
+
 Support for request batching with configurable timeout
 
 =item *
@@ -201,12 +206,42 @@ client side.
 =item *
 
 Poll handler methods are called in list context and do not
-receive any arguments except an environment object. Return value
+receive any arguments except the environment object. Return value
 must be a list of instantiated L<Event|RPC::ExtDirect::Intro/Event>
 objects, see L<RPC::ExtDirect::Event> for more detail.
 
 =back
 
+=head1 CALL METADATA
+
+Imagine a Web application with an editable Grid populated with data
+from a database table. To update the database when changes are saved,
+the client will send an array of changed records that will be passed
+to the corresponding Ext.Direct Method as call arguments. But what
+about the table name?
+
+The usual approach is to create a separate set of 4 CRUD methods
+(Create, Read, Update, Delete) for each table, and expose these
+methods as a separate L<Action|RPC::ExtDirect::Intro/Action>. While
+tried and true, this approach leads to bloated Ext.Direct API
+when there are many tables, with lots of packages and code duplication
+just for the sake of editing a database table. This can be especially
+frustrating when the application has many similar tables differing
+only in the name.
+
+Call metadata allows solving this and many other problems by passing
+some information to the called Method along with the main data but not
+as a part of the arguments. In particular, using metadata allows for
+having one set of CRUD methods for all similar data tables and send
+different table name for each call as metadata.
+
+More of it, Ext JS (and RPC::ExtDirect, too) allow having different
+calling convention for Method arguments and metadata, e.g. having a
+L<Named Method|RPC::ExtDirect::Intro/"Named Method"> with ordered
+(by position) metadata, and vice versa.
+
+Call metadata is a new feature in Ext JS 5.1+.
+
 =head1 HOOKS
 
 Hooks provide an option to intercept method calls and modify arguments
@@ -473,7 +508,7 @@ on versions 2.x and 3.x of the RPC::ExtDirect suite of modules.
 
 =head1 LICENSE AND COPYRIGHT
 
-Copyright (c) 2011-2014 by Alex Tokarev E<lt>tokarev@cpan.orgE<gt>.
+Copyright (c) 2011-2015 by Alex Tokarev E<lt>tokarev@cpan.orgE<gt>.
 
 This module is free software; you can redistribute it and/or modify it under
 the same terms as Perl itself. See L<"perlartistic">.
@@ -30,6 +30,12 @@ is $@, '', 'Empty string attribute data';
 eval { p_attr('foo', sub {}, sub {}, 'ExtDirect', []) };
 like $@, qr/^Can't resolve symbol/, 'Symbol name resolution';
 
+# UNIVERSAL::ExtDirect should be able to rethrow aref exceptions (duh!)
+eval {
+    UNIVERSAL::ExtDirect('foo', *foo, \&foo, 'ExtDirect', ['len', 1, 'metadata', {len => 0}])
+};
+like $@, qr/Method.*?cannot accept 0 arguments/, 'Arrayref exceptions';
+
 # The rest is automated
 my $tests = eval do { local $/; <DATA> } or die "Can't eval DATA: '$@'";
 
@@ -62,6 +68,7 @@ for my $test ( @$tests ) {
 }
 
 __DATA__
+#line 65
 [{
     name  => 'Empty arrayref attribute data',
     input => [],
@@ -107,10 +114,6 @@ __DATA__
     input => [ params => {} ],
     xcpt  => qr{attribute 'params' must be followed},
 }, {
-    name  => 'params empty arrayref',
-    input => [ params => [] ],
-    xcpt  => qr{attribute 'params' must be followed},
-}, {
     name  => 'formHandler',
     input => ['formHandler'],
     want  => { formHandler => 1, },
@@ -72,7 +72,7 @@ for my $test ( @$tests ) {
 };
 
 __DATA__
-
+#line 75
 [
     { method  => 'foo',
       ex => { type    => 'exception',
@@ -58,7 +58,7 @@ ok      $real_result,                   "NoEvents result() not empty";
 is_deep $real_result, $expected_result, "NoEvents result() deep";
 
 __DATA__
-
+#line 61
 [
     {
         name => 'ordered',
@@ -103,7 +103,7 @@ for my $test ( @$hook_tests ) {
 };
 
 __DATA__
-
+#line 106
 {
     main_tests => {
         # foo is plain basic package with ExtDirect methods and hooks
@@ -136,7 +136,7 @@ __DATA__
                 foo_blessed => { package => 'RPC::ExtDirect::Test::Pkg::Foo',
                              method => 'foo_blessed', param_no => undef,
                              formHandler => 0, pollHandler => 0,
-                             param_names => undef, },
+                             param_names => [], },
 
             },
         },
@@ -11,6 +11,7 @@ use RPC::ExtDirect::Config;
 use RPC::ExtDirect::Test::Pkg::Foo;
 use RPC::ExtDirect::Test::Pkg::Bar;
 use RPC::ExtDirect::Test::Pkg::Qux;
+use RPC::ExtDirect::Test::Pkg::Meta;
 
 use RPC::ExtDirect::API;
 
@@ -48,65 +49,105 @@ is      $@,    '',    "remoting_api() 2 eval $@";
 cmp_api $have, $want, "remoting_api() 2 result";
 
 __DATA__
-
+#line 52
 [
     q~
-        Ext.app.REMOTING_API = {
-            "actions":{
-                "Bar":[
-                        { "len":5, "name":"bar_bar" },
-                        { "len":4, "name":"bar_foo" },
-                        { "formHandler":true, "len":0, "name":"bar_baz" }
-                      ],
-                "Foo":[
-                        { "len":1, "name":"foo_foo" },
-                        { "len":2, "name":"foo_bar" },
-                        { "name":"foo_blessed" },
-                        { "name":"foo_baz", "params":["foo","bar","baz"] },
-                        { "len":0, "name":"foo_zero" }
-                      ],
-                "Qux":[
-                        { "len":1, "name":"foo_foo" },
-                        { "len":5, "name":"bar_bar" },
-                        { "len":4, "name":"bar_foo" },
-                        { "formHandler":true, "len":0, "name":"bar_baz" },
-                        { "len":2, "name":"foo_bar" },
-                        { "name":"foo_baz", "params":["foo","bar","baz"] }
-                      ]
-            },
-            "type":"remoting",
-            "url":"/extdirectrouter"
-        };
+Ext.app.REMOTING_API = {
+    "actions":{
+        "Bar": [
+                { "len":5, "name":"bar_bar" },
+                { "len":4, "name":"bar_foo" },
+                { "formHandler":true, "name":"bar_baz" }
+        ],
+        "Foo": [
+                { "len":1, "name":"foo_foo" },
+                { "len":2, "name":"foo_bar" },
+                { "name":"foo_blessed", "params":[], "strict":false },
+                { "name":"foo_baz", "params":["foo","bar","baz"] },
+                { "len":0, "name":"foo_zero" }
+        ],
+        "Meta": [
+                { "name":"arg0", "len":0, "metadata":{ "len":2 } },
+                { "name":"arg1_last", "len":1, "metadata":{ "len":1 } },
+                { "name":"arg1_first", "len":1, "metadata":{ "len":2 } },
+                { "name":"arg2_last", "len":2, "metadata":{ "len":1 } },
+                { "name":"arg2_middle", "len":2, "metadata":{ "len":2 } },
+                { "name":"form_named", "formHandler": true,
+                  "metadata": { "params": [], "strict": false } },
+                { "name": "form_ordered", "formHandler": true,
+                  "metadata": { "len": 1 } },
+                { "name":"named_default", "params": [], "strict":false,
+                  "metadata": { "len": 1 } },
+                { "name":"named_arg", "params": [], "strict":false,
+                  "metadata": { "len": 1 } },
+                { "name":"named_strict", "params": [], "strict":false,
+                  "metadata": { "params": ["foo"] } },
+                { "name":"named_unstrict", "params": [], "strict":false,
+                  "metadata": { "params": [], "strict": false } },
+                { "name":"aux", "len":0 }
+        ],
+        "Qux":[
+                { "len":1, "name":"foo_foo" },
+                { "len":5, "name":"bar_bar" },
+                { "len":4, "name":"bar_foo" },
+                { "formHandler":true, "name":"bar_baz" },
+                { "len":2, "name":"foo_bar" },
+                { "name":"foo_baz", "params":["foo","bar","baz"] }
+        ]
+    },
+    "type":"remoting",
+    "url":"/extdirectrouter"
+};
     ~,
     
     q~
-        Ext.app.REMOTE_CALL_API = {
-            "actions":{
-                "Bar":[
-                        { "len":5, "name":"bar_bar" },
-                        { "len":4, "name":"bar_foo" },
-                        { "formHandler":true, "len":0, "name":"bar_baz" }
-                      ],
-                "Foo":[
-                        { "len":1, "name":"foo_foo" },
-                        { "len":2, "name":"foo_bar" },
-                        { "name":"foo_blessed" },
-                        { "name":"foo_baz", "params":["foo","bar","baz"] },
-                        { "len":0, "name":"foo_zero" }
-                      ],
-                "Qux":[
-                        { "len":1, "name":"foo_foo" },
-                        { "len":5, "name":"bar_bar" },
-                        { "len":4, "name":"bar_foo" },
-                        { "formHandler":true, "len":0, "name":"bar_baz" },
-                        { "len":2, "name":"foo_bar" },
-                        { "name":"foo_baz", "params":["foo","bar","baz"] }
-                      ]
-            },
-            "namespace":"myApp.Server",
-            "type":"remoting",
-            "url":"/router.cgi"
-        };
-        Ext.direct.Manager.addProvider(Ext.app.REMOTE_CALL_API);
+Ext.app.REMOTE_CALL_API = {
+    "actions":{
+        "Bar": [
+                { "len":5, "name":"bar_bar" },
+                { "len":4, "name":"bar_foo" },
+                { "formHandler":true, "name":"bar_baz" }
+        ],
+        "Foo": [
+                { "len":1, "name":"foo_foo" },
+                { "len":2, "name":"foo_bar" },
+                { "name":"foo_blessed", "params":[], "strict":false },
+                { "name":"foo_baz", "params":["foo","bar","baz"] },
+                { "len":0, "name":"foo_zero" }
+        ],
+        "Meta": [
+                { "name":"arg0", "len":0, "metadata":{ "len":2 } },
+                { "name":"arg1_last", "len":1, "metadata":{ "len":1 } },
+                { "name":"arg1_first", "len":1, "metadata":{ "len":2 } },
+                { "name":"arg2_last", "len":2, "metadata":{ "len":1 } },
+                { "name":"arg2_middle", "len":2, "metadata":{ "len":2 } },
+                { "name":"form_named", "formHandler": true,
+                  "metadata": { "params": [], "strict": false } },
+                { "name": "form_ordered", "formHandler": true,
+                  "metadata": { "len": 1 } },
+                { "name":"named_default", "params": [], "strict":false,
+                  "metadata": { "len": 1 } },
+                { "name":"named_arg", "params": [], "strict":false,
+                  "metadata": { "len": 1 } },
+                { "name":"named_strict", "params": [], "strict":false,
+                  "metadata": { "params": ["foo"] } },
+                { "name":"named_unstrict", "params": [], "strict":false,
+                  "metadata": { "params": [], "strict": false } },
+                { "name":"aux", "len":0 }
+        ],
+        "Qux": [
+                { "len":1, "name":"foo_foo" },
+                { "len":5, "name":"bar_bar" },
+                { "len":4, "name":"bar_foo" },
+                { "formHandler":true, "name":"bar_baz" },
+                { "len":2, "name":"foo_bar" },
+                { "name":"foo_baz", "params":["foo","bar","baz"] }
+        ]
+    },
+    "namespace":"myApp.Server",
+    "type":"remoting",
+    "url":"/router.cgi"
+};
+Ext.direct.Manager.addProvider(Ext.app.REMOTE_CALL_API);
     ~,
 ]
@@ -10,6 +10,7 @@ use RPC::ExtDirect::Test::Util;
 use RPC::ExtDirect::Test::Pkg::Foo;
 use RPC::ExtDirect::Test::Pkg::Bar;
 use RPC::ExtDirect::Test::Pkg::Qux;
+use RPC::ExtDirect::Test::Pkg::Meta;
 use RPC::ExtDirect::Test::Pkg::PollProvider;
 
 use RPC::ExtDirect::API     namespace    => 'myApp.Server',
@@ -33,41 +34,61 @@ is      $@,    '',    "remoting_api() eval $@";
 cmp_api $have, $want, "remoting_api() result";
 
 __DATA__
-
+#line 37
 [
     q~
-        Ext.app.REMOTE_CALL_API = {
-            "actions":{
-                "Bar":[
-                        { "len":5, "name":"bar_bar" },
-                        { "len":4, "name":"bar_foo" },
-                        { "formHandler":true, "len":0, "name":"bar_baz" }
-                      ],
-                "Foo":[
-                        { "len":1, "name":"foo_foo" },
-                        { "len":2, "name":"foo_bar" },
-                        { "name":"foo_blessed" },
-                        { "name":"foo_baz", "params":["foo","bar","baz"] },
-                        { "len":0, "name":"foo_zero" }
-                      ],
-                "Qux":[
-                        { "len":1, "name":"foo_foo" },
-                        { "len":5, "name":"bar_bar" },
-                        { "len":4, "name":"bar_foo" },
-                        { "formHandler":true, "len":0, "name":"bar_baz" },
-                        { "len":2, "name":"foo_bar" },
-                        { "name":"foo_baz", "params":["foo","bar","baz"] }
-                      ]
-            },
-            "namespace":"myApp.Server",
-            "type":"remoting",
-            "url":"/router.cgi"
-        };
-        Ext.direct.Manager.addProvider(Ext.app.REMOTE_CALL_API);
-        Ext.app.REMOTE_EVENT_API = {
-            "type":"polling",
-            "url":"/poll.cgi"
-        };
-        Ext.direct.Manager.addProvider(Ext.app.REMOTE_EVENT_API);
+Ext.app.REMOTE_CALL_API = {
+    "actions":{
+        "Bar": [
+                { "len":5, "name":"bar_bar" },
+                { "len":4, "name":"bar_foo" },
+                { "formHandler":true, "name":"bar_baz" }
+        ],
+        "Foo": [
+                { "len":1, "name":"foo_foo" },
+                { "len":2, "name":"foo_bar" },
+                { "name":"foo_blessed", "params":[], "strict":false },
+                { "name":"foo_baz", "params":["foo","bar","baz"] },
+                { "len":0, "name":"foo_zero" }
+        ],
+        "Meta": [
+                { "name":"arg0", "len":0, "metadata":{ "len":2 } },
+                { "name":"arg1_last", "len":1, "metadata":{ "len":1 } },
+                { "name":"arg1_first", "len":1, "metadata":{ "len":2 } },
+                { "name":"arg2_last", "len":2, "metadata":{ "len":1 } },
+                { "name":"arg2_middle", "len":2, "metadata":{ "len":2 } },
+                { "name":"form_named", "formHandler": true,
+                  "metadata": { "params": [], "strict": false } },
+                { "name": "form_ordered", "formHandler": true,
+                  "metadata": { "len": 1 } },
+                { "name":"named_default", "params": [], "strict":false,
+                  "metadata": { "len": 1 } },
+                { "name":"named_arg", "params": [], "strict":false,
+                  "metadata": { "len": 1 } },
+                { "name":"named_strict", "params": [], "strict":false,
+                  "metadata": { "params": ["foo"] } },
+                { "name":"named_unstrict", "params": [], "strict":false,
+                  "metadata": { "params": [], "strict": false } },
+                { "name":"aux", "len":0 }
+        ],
+        "Qux": [
+                { "len":1, "name":"foo_foo" },
+                { "len":5, "name":"bar_bar" },
+                { "len":4, "name":"bar_foo" },
+                { "formHandler":true, "name":"bar_baz" },
+                { "len":2, "name":"foo_bar" },
+                { "name":"foo_baz", "params":["foo","bar","baz"] }
+        ]
+    },
+    "namespace":"myApp.Server",
+    "type":"remoting",
+    "url":"/router.cgi"
+};
+Ext.direct.Manager.addProvider(Ext.app.REMOTE_CALL_API);
+Ext.app.REMOTE_EVENT_API = {
+    "type":"polling",
+    "url":"/poll.cgi"
+};
+Ext.direct.Manager.addProvider(Ext.app.REMOTE_EVENT_API);
     ~,
 ]
@@ -43,7 +43,7 @@ is      $@,    '',    "remoting_api() eval $@";
 cmp_api $have, $want, "remoting_api() result";
 
 __DATA__
-
+#line 46
 {
     api_def => {
         'RPC::ExtDirect::Test::Foo' => {
@@ -62,6 +62,47 @@ __DATA__
                 bar_baz => { formHandler => 1 },
             },
         },
+        'RPC::ExtDirect::Test::Meta' => {
+            methods => {
+                meta0_default => {
+                    len => 0,
+                    metadata => { len => 1, arg => -1 },
+                },
+                meta0_arg => {
+                    len => 0,
+                    metadata => { len => 2, arg => 0, },
+                },
+                meta1_default => {
+                    len => 1,
+                    metadata => { len => 1, arg => 99, },
+                },
+                meta1_arg => {
+                    len => 1,
+                    metadata => { len => 2, arg => 0, },
+                },
+                meta2_default => {
+                    len => 2,
+                    metadata => { len => 1, arg => -1 },
+                },
+                meta2_arg => {
+                    len => 2,
+                    metadata => { len => 2, arg => 1, },
+                },
+                meta_named_default => {
+                    params => [],
+                    metadata => { len => 1, },
+                },
+                meta_named_arg => {
+                    metadata => { len => 1, arg => 'foo' },
+                },
+                meta_named_strict => {
+                    metadata => { params => ['foo'], },
+                },
+                meta_named_unstrict => {
+                    metadata => { arg => '_meta', strict => !1, },
+                },
+            },
+        },
         'RPC::ExtDirect::Test::Qux' => {
             methods => {
                 foo_foo => { len => 1 },
@@ -81,39 +122,55 @@ __DATA__
 
     tests => [
         q~
-            Ext.app.REMOTE_CALL_API = {
-                "actions":{
-                    "Bar":[
-                            { "len":5, "name":"bar_bar" },
-                            { "len":4, "name":"bar_foo" },
-                            { "formHandler":true, "len":0, "name":"bar_baz" }
-                          ],
-                    "Foo":[
-                            { "len":1, "name":"foo_foo" },
-                            { "len":2, "name":"foo_bar" },
-                            { "name":"foo_blessed" },
-                            { "name":"foo_baz", "params":["foo","bar","baz"] },
-                            { "len":0, "name":"foo_zero" }
-                          ],
-                    "Qux":[
-                            { "len":1, "name":"foo_foo" },
-                            { "len":5, "name":"bar_bar" },
-                            { "len":4, "name":"bar_foo" },
-                            { "formHandler":true, "len":0, "name":"bar_baz" },
-                            { "len":2, "name":"foo_bar" },
-                            { "name":"foo_baz", "params":["foo","bar","baz"] }
-                          ]
-                },
-                "namespace":"myApp.Server",
-                "type":"remoting",
-                "url":"/router.cgi"
-            };
-            Ext.direct.Manager.addProvider(Ext.app.REMOTE_CALL_API);
-            Ext.app.REMOTE_EVENT_API = {
-                "type":"polling",
-                "url":"/poll.cgi"
-            };
-            Ext.direct.Manager.addProvider(Ext.app.REMOTE_EVENT_API);
+Ext.app.REMOTE_CALL_API = {
+    "actions": {
+        "Bar": [
+                { "len":5, "name":"bar_bar" },
+                { "len":4, "name":"bar_foo" },
+                { "formHandler":true, "name":"bar_baz" }
+        ],
+        "Foo": [
+                { "len":1, "name":"foo_foo" },
+                { "len":2, "name":"foo_bar" },
+                { "name":"foo_blessed", "params":[], "strict":false },
+                { "name":"foo_baz", "params":["foo","bar","baz"] },
+                { "len":0, "name":"foo_zero" }
+        ],
+        "Meta": [
+                { "name":"meta0_default", "len":0, "metadata":{ "len":1 } },
+                { "name":"meta0_arg", "len":0, "metadata":{ "len":2 } },
+                { "name":"meta1_default", "len":1, "metadata":{ "len":1 } },
+                { "name":"meta1_arg", "len":1, "metadata":{ "len":2 } },
+                { "name":"meta2_default", "len":2, "metadata":{ "len":1 } },
+                { "name":"meta2_arg", "len":2, "metadata":{ "len":2 } },
+                { "name":"meta_named_default", "params": [], "strict":false,
+                  "metadata": { "len": 1 } },
+                { "name":"meta_named_arg", "params": [], "strict":false,
+                  "metadata": { "len": 1 } },
+                { "name":"meta_named_strict", "params": [], "strict":false,
+                  "metadata": { "params": ["foo"] } },
+                { "name":"meta_named_unstrict", "params": [], "strict":false,
+                  "metadata": { "params": [], "strict": false } }
+        ],
+        "Qux": [
+                { "len":1, "name":"foo_foo" },
+                { "len":5, "name":"bar_bar" },
+                { "len":4, "name":"bar_foo" },
+                { "formHandler":true, "name":"bar_baz" },
+                { "len":2, "name":"foo_bar" },
+                { "name":"foo_baz", "params":["foo","bar","baz"] }
+        ]
+    },
+    "namespace":"myApp.Server",
+    "type":"remoting",
+    "url":"/router.cgi"
+};
+Ext.direct.Manager.addProvider(Ext.app.REMOTE_CALL_API);
+Ext.app.REMOTE_EVENT_API = {
+    "type":"polling",
+    "url":"/poll.cgi"
+};
+Ext.direct.Manager.addProvider(Ext.app.REMOTE_EVENT_API);
         ~,
     ],
 }
@@ -43,7 +43,7 @@ is      $@,    '',    "remoting_api() eval $@";
 cmp_api $have, $want, "remoting_api() result";
 
 __DATA__
-
+#line 46
 {
     api_def => {
         'Foo' => {
@@ -85,39 +85,39 @@ __DATA__
 
     tests => [
         q~
-            Ext.app.REMOTE_CALL_API = {
-                "actions":{
-                    "Bar":[
-                            { "len":5, "name":"bar_bar" },
-                            { "len":4, "name":"bar_foo" },
-                            { "formHandler":true, "len":0, "name":"bar_baz" }
-                          ],
-                    "Foo":[
-                            { "len":1, "name":"foo_foo" },
-                            { "len":2, "name":"foo_bar" },
-                            { "name":"foo_blessed" },
-                            { "name":"foo_baz", "params":["foo","bar","baz"] },
-                            { "len":0, "name":"foo_zero" }
-                          ],
-                    "Qux":[
-                            { "len":1, "name":"foo_foo" },
-                            { "len":5, "name":"bar_bar" },
-                            { "len":4, "name":"bar_foo" },
-                            { "formHandler":true, "len":0, "name":"bar_baz" },
-                            { "len":2, "name":"foo_bar" },
-                            { "name":"foo_baz", "params":["foo","bar","baz"] }
-                          ]
-                },
-                "namespace":"myApp.Server",
-                "type":"remoting",
-                "url":"/router.cgi"
-            };
-            Ext.direct.Manager.addProvider(Ext.app.REMOTE_CALL_API);
-            Ext.app.REMOTE_EVENT_API = {
-                "type":"polling",
-                "url":"/poll.cgi"
-            };
-            Ext.direct.Manager.addProvider(Ext.app.REMOTE_EVENT_API);
+Ext.app.REMOTE_CALL_API = {
+    "actions": {
+        "Bar": [
+                { "len":5, "name":"bar_bar" },
+                { "len":4, "name":"bar_foo" },
+                { "formHandler":true, "name":"bar_baz" }
+        ],
+        "Foo": [
+                { "len":1, "name":"foo_foo" },
+                { "len":2, "name":"foo_bar" },
+                { "name":"foo_blessed", "params":[], "strict":false },
+                { "name":"foo_baz", "params":["foo","bar","baz"] },
+                { "len":0, "name":"foo_zero" }
+        ],
+        "Qux": [
+                { "len":1, "name":"foo_foo" },
+                { "len":5, "name":"bar_bar" },
+                { "len":4, "name":"bar_foo" },
+                { "formHandler":true, "name":"bar_baz" },
+                { "len":2, "name":"foo_bar" },
+                { "name":"foo_baz", "params":["foo","bar","baz"] }
+        ]
+    },
+    "namespace":"myApp.Server",
+    "type":"remoting",
+    "url":"/router.cgi"
+};
+Ext.direct.Manager.addProvider(Ext.app.REMOTE_CALL_API);
+Ext.app.REMOTE_EVENT_API = {
+    "type":"polling",
+    "url":"/poll.cgi"
+};
+Ext.direct.Manager.addProvider(Ext.app.REMOTE_EVENT_API);
         ~,
     ],
 }
@@ -66,7 +66,7 @@ is      $@,    '',    "authz remoting_api eval $@";
 cmp_api $have, $want, "authz remoting_api result";
 
 __DATA__
-
+#line 69
 {
     api_def => {
         'Foo' => {
@@ -108,58 +108,58 @@ __DATA__
     
     tests => [
         q~
-            Ext.app.REMOTE_CALL_API = {
-                "actions":{
-                    "Foo":[
-                            { "len":1, "name":"foo_foo" },
-                            { "len":2, "name":"foo_bar" },
-                            { "name":"foo_blessed" },
-                            { "name":"foo_baz", "params":["foo","bar","baz"] },
-                            { "len":0, "name":"foo_zero" }
-                          ]
-                },
-                "namespace":"myApp.Server",
-                "type":"remoting",
-                "url":"/router.cgi"
-            };
-            Ext.app.REMOTE_EVENT_API = {
-                "type":"polling",
-                "url":"/poll.cgi"
-            };
+Ext.app.REMOTE_CALL_API = {
+    "actions": {
+        "Foo": [
+                { "len":1, "name":"foo_foo" },
+                { "len":2, "name":"foo_bar" },
+                { "name":"foo_blessed", "params":[], "strict":false },
+                { "name":"foo_baz", "params":["foo","bar","baz"] },
+                { "len":0, "name":"foo_zero" }
+        ]
+    },
+    "namespace":"myApp.Server",
+    "type":"remoting",
+    "url":"/router.cgi"
+};
+Ext.app.REMOTE_EVENT_API = {
+    "type":"polling",
+    "url":"/poll.cgi"
+};
         ~,
         
         q~
-            Ext.app.REMOTE_CALL_API = {
-                "actions":{
-                    "Bar":[
-                            { "len":5, "name":"bar_bar" },
-                            { "len":4, "name":"bar_foo" },
-                            { "formHandler":true, "len":0, "name":"bar_baz" }
-                          ],
-                    "Foo":[
-                            { "len":1, "name":"foo_foo" },
-                            { "len":2, "name":"foo_bar" },
-                            { "name":"foo_blessed" },
-                            { "name":"foo_baz", "params":["foo","bar","baz"] },
-                            { "len":0, "name":"foo_zero" }
-                          ],
-                    "Qux":[
-                            { "len":1, "name":"foo_foo" },
-                            { "len":5, "name":"bar_bar" },
-                            { "len":4, "name":"bar_foo" },
-                            { "formHandler":true, "len":0, "name":"bar_baz" },
-                            { "len":2, "name":"foo_bar" },
-                            { "name":"foo_baz", "params":["foo","bar","baz"] }
-                          ]
-                },
-                "namespace":"myApp.Server",
-                "type":"remoting",
-                "url":"/router.cgi"
-            };
-            Ext.app.REMOTE_EVENT_API = {
-                "type":"polling",
-                "url":"/poll.cgi"
-            };
+Ext.app.REMOTE_CALL_API = {
+    "actions": {
+        "Bar": [
+                { "len":5, "name":"bar_bar" },
+                { "len":4, "name":"bar_foo" },
+                { "formHandler":true, "name":"bar_baz" }
+        ],
+        "Foo": [
+                { "len":1, "name":"foo_foo" },
+                { "len":2, "name":"foo_bar" },
+                { "name":"foo_blessed", "params":[], "strict":false },
+                { "name":"foo_baz", "params":["foo","bar","baz"] },
+                { "len":0, "name":"foo_zero" }
+        ],
+        "Qux": [
+                { "len":1, "name":"foo_foo" },
+                { "len":5, "name":"bar_bar" },
+                { "len":4, "name":"bar_foo" },
+                { "formHandler":true, "name":"bar_baz" },
+                { "len":2, "name":"foo_bar" },
+                { "name":"foo_baz", "params":["foo","bar","baz"] }
+        ]
+    },
+    "namespace":"myApp.Server",
+    "type":"remoting",
+    "url":"/router.cgi"
+};
+Ext.app.REMOTE_EVENT_API = {
+    "type":"polling",
+    "url":"/poll.cgi"
+};
         ~,
     ],
 }
@@ -3,7 +3,7 @@
 use strict;
 use warnings;
 
-use Test::More tests => 59;
+use Test::More tests => 166;
 
 use RPC::ExtDirect::Test::Util;
 use RPC::ExtDirect::Config;
@@ -22,44 +22,108 @@ for my $test ( @$tests ) {
     my $type       = $test->{type};
     my $method_arg = $test->{method};
     my $input      = $test->{input};
-    my $out_type   = $test->{out_type};
+    my $out_type   = $test->{out_type} || 'array';
+    my $out_ctx    = $test->{out_context} || { list => 1, scalar => 1 },
     my $output     = $test->{output};
     my $exception  = $test->{exception};
+    my $warning    = $test->{warning};
     
     next TEST if @run_only && !grep { lc $name eq lc $_ } @run_only;
     
-    my $method = RPC::ExtDirect::API::Method->new(
-        config => $config,
-        %$method_arg,
-    );
+    my $have_warning;
+    
+    my $method = eval {
+        local $SIG{__WARN__} = sub { $have_warning = shift };
+        
+        RPC::ExtDirect::API::Method->new(
+            config => $config,
+            %$method_arg,
+        );
+    };
+    
+    if ( $@ ) {
+        if ( $exception ) {
+            my $xcpt = 'ARRAY' eq ref $@ ? $@->[0] : $@;
+            
+            like $xcpt, $exception, "$name: new exception";
+        }
+        else {
+            fail "$name: uncaught new exception: $@";
+        }
+        
+        next TEST;
+    }
+    
+    if ( $warning ) {
+        like $have_warning, $warning, "$name: new warning";
+    }
     
     if ( $type eq 'check' ) {
         my $result = eval { $method->check_method_arguments($input) };
         
         if ( $exception ) {
-            like $@, $exception, "$name: check exception";
+            my $xcpt = 'ARRAY' eq ref $@ ? $@->[0] : $@;
+            
+            like $xcpt, $exception, "$name: check exception";
         }
         else {
             is_deep $result, $output, "$name: check result";
         }
     }
+    elsif ( $type eq 'prepare' ) {
+        if ( $out_ctx->{list} ) {
+            if ( $out_type eq 'hash' ) {
+                my %have = $method->prepare_method_arguments(%$input);
+                
+                is_deep \%have, $output, "$name: prepare list output";
+            }
+            else {
+                my @have = $method->prepare_method_arguments(%$input);
+                
+                is_deep \@have, $output, "$name: prepare list output";
+            }
+        }
+        
+        if ( $out_ctx->{scalar} ) {
+            my $have = $method->prepare_method_arguments(%$input);
+            
+            if ( $out_type ) {
+                is ref($have), uc $out_type, "$name: prepare ref type";
+            }
+            
+            is_deep $have, $output, "$name: prepare scalar output";
+        }
+    }
+    elsif ( $type eq 'check_meta' ) {
+        my $result = eval { $method->check_method_metadata($input) };
+        
+        if ( $exception ) {
+            my $xcpt = 'ARRAY' eq ref $@ ? $@->[0] : $@;
+            
+            like $xcpt, $exception, "$name: check_meta exception";
+        }
+        else {
+            is_deep $result, $output, "$name: check_meta result";
+        }
+    }
+    elsif ( $type eq 'prepare_meta' ) {
+        my $prep_out = $method->prepare_method_metadata(%$input);
+        
+        is_deep $prep_out, $output, "$name: prepare_meta output";
+    }
     else {
-        my @prep_out = $method->prepare_method_arguments(%$input);
-        my $prep_out = $method->prepare_method_arguments(%$input);
-
-        is      ref($prep_out), uc $out_type, "$name: scalar context ref";
-        is_deep $prep_out,      $output,      "$name: prepare output";
+        BAIL_OUT "Unknown test type: $type";
     }
 }
 
 __DATA__
-
+#line 120
 [
     {
         name => 'Ordered passed {}',
         type => 'check',
         method => {
-            len => 0,
+            len => 1,
         },
         input => { foo => 'bar' },
         exception => qr/expects ordered arguments in arrayref/,
@@ -405,6 +469,32 @@ __DATA__
         output => [1, 'env', 2],
     },
     {
+        name => 'Named empty params strict no env',
+        type => 'prepare',
+        method => {
+            params => [],
+        },
+        input => {
+            env => 'env',
+            input => { foo => 1, bar => 2, },
+        },
+        out_type => 'hash',
+        output => { foo => 1, bar => 2, },
+    },
+    {
+        name => 'Named empty params !strict no env',
+        type => 'prepare',
+        method => {
+            params => [], strict => !1,
+        },
+        input => {
+            env => 'env',
+            input => { foo => 1, bar => 2, },
+        },
+        out_type => 'hash',
+        output => { foo => 1, bar => 2, },
+    },
+    {
         name => 'Named strict no env',
         type => 'prepare',
         method => {
@@ -485,6 +575,22 @@ __DATA__
         output => { foo => 'bar', _env => 'env' },
     },
     {
+        name => 'formHandler decode_params',
+        type => 'prepare',
+        method => {
+            formHandler => 1,
+            decode_params => ['frobbe', 'guzzard'],
+        },
+        input => {
+            input => { frobbe => '{"throbbe":["vita","voom"]}', },
+        },
+        out_type => 'hash',
+        out_context => { list => 1 },
+        output => {
+            frobbe => { throbbe => ['vita', 'voom'] },
+        },
+    },
+    {
         name => 'formHandler w/def uploads w/ env',
         type => 'prepare',
         method => {
@@ -545,5 +651,548 @@ __DATA__
         out_type => 'array',
         output => ['env'],
     },
+    {
+        name => 'Ordered meta passed {}',
+        type => 'check_meta',
+        method => {
+            len => 0,
+            metadata => { len => 1, arg => -1, },
+        },
+        input => {},
+        exception => qr/expects metadata in arrayref/,
+    },
+    {
+        name => 'Ordered meta passed [0]',
+        type => 'check_meta',
+        method => {
+            len => 0,
+            metadata => { len => 1, arg => -1, },
+        },
+        input => [],
+        exception => qr/requires 1 metadata value/,
+    },
+    {
+        name => 'Ordered meta passed [1]',
+        type => 'check_meta',
+        method => {
+            len => 0,
+            metadata => { len => 1, arg => -1, },
+        },
+        input => [42],
+        output => 1,
+    },
+    {
+        name => 'Ordered meta passed [2]',
+        type => 'check_meta',
+        method => {
+            len => 0,
+            metadata => { len => 1, arg => -1, },
+        },
+        input => [42, 43],
+        output => 1,
+    },
+    {
+        name => 'Named meta passed []',
+        type => 'check_meta',
+        method => {
+            metadata => { params => ['foo'] },
+        },
+        input => [],
+        exception => qr/expects metadata key\/value/,
+    },
+    {
+        name => 'Named meta strict passed {}',
+        type => 'check_meta',
+        method => {
+            metadata => { params => [] },
+        },
+        input => {},
+        output => 1,
+    },
+    {
+        name => 'Named meta default passed []',
+        type => 'check_meta',
+        method => {
+            metadata => {},
+        },
+        input => [],
+        exception => qr/expects metadata key\/value/,
+    },
+    {
+        name => 'Named meta !strict passed {}',
+        type => 'check_meta',
+        method => {
+            metadata => { params => [], },
+        },
+        input => {},
+        output => 1,
+        warning => qr/implies strict argument checking/,
+    },
+    {
+        name => 'Named meta default passed {}',
+        type => 'check_meta',
+        method => {
+            metadata => {},
+        },
+        input => {},
+        output => 1,
+    },
+    {
+        name => 'Named meta strict missing params',
+        type => 'check_meta',
+        method => {
+            metadata => { params => ['foo'] },
+        },
+        input => {},
+        exception => qr/requires.*?metadata keys: 'foo'/,
+    },
+    {
+        name => 'Named meta !strict missing params',
+        type => 'check_meta',
+        method => {
+            metadata => { params => ['foo'], strict => !1 },
+        },
+        input => {},
+        exception => qr/requires.*?metadata keys: 'foo'/,
+    },
+    {
+        name => 'Named meta strict enough params',
+        type => 'check_meta',
+        method => {
+            metadata => { params => ['foo'] },
+        },
+        input => { foo => 'bar' },
+        output => 1,
+    },
+    {
+        name => 'Named meta !strict enough params',
+        type => 'check_meta',
+        method => {
+            metadata => { params => ['foo'], strict => !1 },
+        },
+        input => { foo => 'bar' },
+        output => 1,
+    },
+    {
+        name => 'Named meta default passed params',
+        type => 'check_meta',
+        method => {
+            metadata => {},
+        },
+        input => { foo => 'bar' },
+        output => 1,
+    },
+    {
+        name => 'Ordered meta 0 arguments',
+        type => 'check',
+        method => {
+            len => 0, metadata => { len => 0 },
+        },
+        exception => qr/cannot accept 0 arguments/,
+    },
+    {
+        name => 'Ordered meta missing arg position',
+        type => 'check',
+        method => {
+            len => 0, metadata => { len => 1 },
+        },
+        exception => qr/metadata with no arg position/,
+    },
+    {
+        name => 'Ordered meta [1] passed [1]',
+        type => 'prepare_meta',
+        method => {
+            len => 0,
+            metadata => { len => 1, arg => -1, },
+        },
+        input => { metadata => [42] },
+        output => [42],
+    },
+    {
+        name => 'Ordered meta [1] passed [2]',
+        type => 'prepare_meta',
+        method => {
+            len => 0,
+            metadata => { len => 1, arg => -1, },
+        },
+        input => { metadata => [42, 43] },
+        output => [42],
+    },
+    {
+        name => 'Ordered meta [1] passed [3]',
+        type => 'prepare_meta',
+        method => {
+            len => 0,
+            metadata => { len => 1, arg => -1, },
+        },
+        input => { metadata => [42, 43, 44] },
+        output => [42],
+    },
+    {
+        name => 'Ordered meta [2] passed [2]',
+        type => 'prepare_meta',
+        method => {
+            len => 0,
+            metadata => { len => 2, arg => -1, },
+        },
+        input => { metadata => [42, 43] },
+        output => [42, 43],
+    },
+    {
+        name => 'Ordered meta [2] passed [3]',
+        type => 'prepare_meta',
+        method => {
+            len => 0,
+            metadata => { len => 2, arg => -1, },
+        },
+        input => { metadata => [42, 43, 44] },
+        output => [42, 43],
+    },
+    {
+        name => 'Named meta strict 1',
+        type => 'prepare_meta',
+        method => {
+            metadata => { params => ['foo'] },
+        },
+        input => { metadata => { foo => 'bar' } },
+        output => { foo => 'bar' },
+    },
+    {
+        name => 'Named meta strict 2',
+        type => 'prepare_meta',
+        method => {
+            metadata => { params => [qw/ foo bar /] },
+        },
+        input => { metadata => { foo => 42, bar => 43, baz => 44 } },
+        output => { foo => 42, bar => 43, },
+    },
+    {
+        name => 'Named meta !strict 1',
+        type => 'prepare_meta',
+        method => {
+            metadata => { params => ['foo'], strict => !1, },
+        },
+        input => { metadata => { foo => 42, bar => 43, baz => 44 } },
+        output => { foo => 42, bar => 43, baz => 44 },
+    },
+    {
+        name => 'Named meta !strict 2',
+        type => 'prepare_meta',
+        method => {
+            metadata => { params => [], strict => !1, },
+        },
+        input => { metadata => { foo => 42, bar => 43, baz => 44 } },
+        output => { foo => 42, bar => 43, baz => 44 },
+    },
+    {
+        name => 'Named meta default empty',
+        type => 'prepare_meta',
+        method => {
+            metadata => {},
+        },
+        input => {},
+        output => undef,
+    },
+    {
+        name => 'Named meta default !empty',
+        type => 'prepare_meta',
+        method => {
+            metadata => {},
+        },
+        input => { metadata => { foo => 'bar', baz => 'qux' }, },
+        output => { foo => 'bar', baz => 'qux' },
+    },
+    {
+        name => 'Ordered meta input 1',
+        type => 'prepare',
+        method => {
+            len => 0,
+            metadata => { len => 1, arg => -1, },
+        },
+        input => {
+            env => 'env', input => [42], metadata => [43]
+        },
+        output => [ [43] ],
+    },
+    {
+        name => 'Ordered meta input 2',
+        type => 'prepare',
+        method => {
+            len => 0,
+            metadata => { len => 1, arg => -1, },
+        },
+        input => {
+            env => 'env', input => [42], metadata => [43]
+        },
+        output => [ [43] ],
+    },
+    {
+        name => 'Ordered meta input 3',
+        type => 'prepare',
+        method => {
+            len => 0,
+            env_arg => -1,
+            metadata => { len => 1, arg => -1, },
+        },
+        input => {
+            env => 'env', input => [42], metadata => [43, 44]
+        },
+        output => [ [43], 'env' ],
+    },
+    {
+        name => 'Ordered meta input 4',
+        type => 'prepare',
+        method => {
+            len => 0,
+            env_arg => 99,
+            metadata => { len => 1, arg => 99, },
+        },
+        input => {
+            env => 'env', input => [42], metadata => [43, 44]
+        },
+        output => [ 'env', [43] ],
+    },
+    {
+        name => 'Ordered meta input 5',
+        type => 'prepare',
+        method => {
+            len => 0,
+            env_arg => -1,
+            metadata => { len => 1, arg => -2 },
+        },
+        input => {
+            env => 'env', input => [42], metadata => [43]
+        },
+        output => [ [43], 'env' ],
+    },
+    {
+        name => 'Ordered meta input 6',
+        type => 'prepare',
+        method => {
+            len => 0,
+            env_arg => -1,
+            metadata => { len => 1, arg => 99, },
+        },
+        input => {
+            env => 'env', input => [42], metadata => [43]
+        },
+        output => [ 'env', [43] ],
+    },
+    {
+        name => 'Ordered meta input 7',
+        type => 'prepare',
+        method => {
+            len => 0,
+            env_arg => 99,
+            metadata => { len => 1, arg => -1, },
+        },
+        input => {
+            env => 'env', input => [42], metadata => [43]
+        },
+        output => [ [43], 'env' ],
+    },
+    {
+        name => 'Ordered meta input 8',
+        type => 'prepare',
+        method => {
+            len => 1,
+            env_arg => -1,
+            metadata => { len => 1, arg => -1 },
+        },
+        input => {
+            env => 'env', input => [42], metadata => [43]
+        },
+        output => [ 'env', [43], 42 ],
+    },
+    {
+        name => 'Ordered meta input 9',
+        type => 'prepare',
+        method => {
+            len => 1,
+            env_arg => 99,
+            metadata => { len => 1, arg => -1 },
+        },
+        input => {
+            env => 'env', input => [42], metadata => [43]
+        },
+        output => [ 42, [43], 'env' ],
+    },
+    {
+        name => 'Ordered meta input 10',
+        type => 'prepare',
+        method => {
+            len => 1,
+            env_arg => 99,
+            metadata => { len => 1, arg => 99 },
+        },
+        input => {
+            env => 'env', input => [42], metadata => [43]
+        },
+        output => [ 42, 'env', [43] ],
+    },
+    {
+        name => 'Ordered meta input 11',
+        type => 'prepare',
+        method => {
+            len => 2,
+            env_arg => 99,
+            metadata => { len => 2, arg => -1 },
+        },
+        input => {
+            env => 'env',
+            input => ['foo', 'bar', 'baz'],
+            metadata => [42, 43, 44],
+        },
+        output => [ 'foo', 'bar', [42, 43], 'env' ],
+    },
+    {
+        name => 'Ordered meta input 12',
+        type => 'prepare',
+        method => {
+            params => [],
+            env_arg => '_env',
+            metadata => { len => 2 },
+        },
+        input => {
+            env => 'env',
+            input => { foo => 'bar', baz => 'qux' },
+            metadata => [42, 43, 44],
+        },
+        out_type => 'hash',
+        output => {
+            _env => 'env',
+            foo => 'bar',
+            baz => 'qux',
+            metadata => [42, 43],
+        },
+    },
+    {
+        name => 'Ordered meta input 13',
+        type => 'prepare',
+        method => {
+            formHandler => 1,
+            metadata => { len => 1, },
+        },
+        input => {
+            env => 'env',
+            input => { fred => 'moo', blurg => 'frob' },
+            metadata => [42, 43],
+        },
+        out_type => 'hash',
+        output => {
+            fred => 'moo',
+            blurg => 'frob',
+            metadata => [42],
+        },
+    },
+    {
+        name => 'Ordered meta input 14',
+        type => 'prepare',
+        method => {
+            formHandler => 1,
+            env_arg => '_e',
+            metadata => { len => 1, arg => '_m', },
+        },
+        input => {
+            env => 'env',
+            input => { fred => 'moo', blurg => 'frob' },
+            metadata => [42, 43],
+        },
+        out_type => 'hash',
+        output => {
+            _e => 'env',
+            fred => 'moo',
+            blurg => 'frob',
+            _m => [42],
+        },
+    },
+    {
+        name => 'Named meta input 1', 
+        type => 'prepare',
+        method => {
+            len => 1,
+            env_arg => 99,
+            metadata => { arg => 99, },
+        },
+        input => {
+            env => 'env',
+            input => [42, 43],
+            metadata => { foo => 'bar', baz => 'qux' },
+        },
+        out_type => 'array',
+        output => [42, 'env', { foo => 'bar', baz => 'qux' }, ],
+    },
+    {
+        name => 'Named meta input 2',
+        type => 'prepare',
+        method => {
+            len => 2,
+            env_arg => 99,
+            metadata => { params => ['foo'], arg => 99, },
+        },
+        input => {
+            env => 'env',
+            input => [42, 43],
+            metadata => { foo => 'bar', baz => 'qux' },
+        },
+        out_type => 'array',
+        output => [42, 43, 'env', { foo => 'bar' }, ],
+    },
+    {
+        name => 'Named meta input 3',
+        type => 'prepare',
+        method => {
+            len => 0,
+            metadata => { params => ['foo'], strict => !1, arg => 99, },
+        },
+        input => {
+            env => 'env',
+            input => [42, 43],
+            metadata => { foo => 'bar', baz => 'qux' },
+        },
+        out_type => 'array',
+        output => [{ foo => 'bar', baz => 'qux' }]
+    },
+    {
+        name => 'Named meta input 4',
+        type => 'prepare',
+        method => {
+            params => ['fred', 'frob'],
+            env_arg => '_env',
+            metadata => { params => ['foo'], arg => '_m' },
+        },
+        input => {
+            env => 'env',
+            input => { fred => 'blerg', frob => 'blam', },
+            metadata => { foo => 'bar', baz => 'qux' },
+        },
+        out_type => 'hash',
+        output => {
+            _env => 'env',
+            fred => 'blerg',
+            frob => 'blam',
+            _m => { foo => 'bar' },
+        },
+    },
+    {
+        name => 'Named meta input 5',
+        type => 'prepare',
+        method => {
+            formHandler => 1,
+            env_arg => '_e',
+            metadata => { params => ['foo'], arg => '_m', },
+        },
+        input => {
+            env => 'env',
+            input => { fred => 'moo', blurg => 'frob' },
+            metadata => { foo => 'bar', baz => 'qux' },
+        },
+        out_type => 'hash',
+        output => {
+            _e => 'env',
+            fred => 'moo',
+            blurg => 'frob',
+            _m => { foo => 'bar' },
+        },
+    },
 ];
-
@@ -37,37 +37,37 @@ is      $@,    '',    "API options eval $@";
 cmp_api $have, $want, "API options result";
 
 __DATA__
-
+#line 40
 [
     q~
-        Ext.app.REMOTE_CALL_API = {
-            "actions":{
-                "Bar":[
-                        { "len":5, "name":"bar_bar" },
-                        { "len":4, "name":"bar_foo" },
-                        { "formHandler":true, "len":0, "name":"bar_baz" }
-                      ],
-                "Foo":[
-                        { "len":1, "name":"foo_foo" },
-                        { "len":2, "name":"foo_bar" },
-                        { "name":"foo_blessed" },
-                        { "name":"foo_baz", "params":["foo","bar","baz"] },
-                        { "len":0, "name":"foo_zero" }
-                      ],
-                "Qux":[
-                        { "len":1, "name":"foo_foo" },
-                        { "len":5, "name":"bar_bar" },
-                        { "len":4, "name":"bar_foo" },
-                        { "formHandler":true, "len":0, "name":"bar_baz" },
-                        { "len":2, "name":"foo_bar" },
-                        { "name":"foo_baz", "params":["foo","bar","baz"] }
-                      ]
-            },
-            "namespace":"myApp.Server",
-            "type":"remoting",
-            "url":"/router.cgi",
-            "timeout":42,
-            "maxRetries":1
-        };
+Ext.app.REMOTE_CALL_API = {
+    "actions": {
+        "Bar": [
+                { "len":5, "name":"bar_bar" },
+                { "len":4, "name":"bar_foo" },
+                { "formHandler":true, "name":"bar_baz" }
+        ],
+        "Foo": [
+                { "len":1, "name":"foo_foo" },
+                { "len":2, "name":"foo_bar" },
+                { "name":"foo_blessed", "params":[], "strict":false },
+                { "name":"foo_baz", "params":["foo","bar","baz"] },
+                { "len":0, "name":"foo_zero" }
+        ],
+        "Qux": [
+                { "len":1, "name":"foo_foo" },
+                { "len":5, "name":"bar_bar" },
+                { "len":4, "name":"bar_foo" },
+                { "formHandler":true, "name":"bar_baz" },
+                { "len":2, "name":"foo_bar" },
+                { "name":"foo_baz", "params":["foo","bar","baz"] }
+        ]
+    },
+    "namespace":"myApp.Server",
+    "type":"remoting",
+    "url":"/router.cgi",
+    "timeout":42,
+    "maxRetries":1
+};
     ~,
 ]
@@ -54,6 +54,7 @@ for my $test ( @$tests ) {
 };
 
 __DATA__
+#line 57
 [
     # Numbered one argument with scalar result
     {
@@ -63,6 +63,7 @@ for my $test ( @$tests ) {
 };
 
 __DATA__
+#line 66
 [
     # Null input, debug off
     {
@@ -62,6 +62,7 @@ for my $test ( @$tests ) {
 };
 
 __DATA__
+#line 65
 [
     { name   => 'Invalid post data, debug off', debug => 0,
       method => 'decode_post',
@@ -1,24 +1,30 @@
 use strict;
 use warnings;
 
-use Test::More tests => 24;
+use Test::More tests => 92;
 
-use RPC::ExtDirect::Test::Util;
+use RPC::ExtDirect::Test::Util qw/ cmp_json is_deep /;
 use RPC::ExtDirect::Config;
 
 use RPC::ExtDirect::Router;
 
 # Test modules are simple
 use RPC::ExtDirect::Test::Pkg::Qux;
+use RPC::ExtDirect::Test::Pkg::Meta;
 
 my $tests = eval do { local $/; <DATA>; }           ## no critic
     or die "Can't eval DATA: $@";
 
+my %only_tests = map { $_ => 1 } @ARGV;
+
+TEST:
 for my $test ( @$tests ) {
     my $name   = $test->{name};
     my $debug  = $test->{debug};
     my $input  = $test->{input};
     my $expect = $test->{output};
+    
+    next TEST if %only_tests && !$only_tests{$name};
 
     my $config = RPC::ExtDirect::Config->new(
         debug_router => $debug,
@@ -30,9 +36,6 @@ for my $test ( @$tests ) {
 
     my $result = eval { $router->route($input) };
 
-    # Remove whitespace
-    s/\s//g for ( $expect->[2]->[0], $result->[2]->[0] );
-
     # Remove reference addresses. On different platforms
     # stringified reference has different length so we're
     # trying to compensate for that here.
@@ -45,16 +48,33 @@ for my $test ( @$tests ) {
         $result->[1]->[3] = $expect->[1]->[3] = length $expect->[2]->[0];
     };
 
-    is      $@,      '',      "$name eval $@";
-    is ref  $result, 'ARRAY', "$name result ARRAY";
-    is_deep $result, $expect, "$name result deep";
+    my $want_response = (pop @$expect)->[0];
+    my $have_response = (pop @$result)->[0];
+
+    my ($want_json, $have_json);
+
+    # It was a form request; extract JSON
+    if ( $want_response =~ /<textarea>/i ) {
+        ($want_json) = $want_response =~ /<textarea>(.*)<\/textarea>/i;
+        ($have_json) = $have_response =~ /<textarea>(.*)<\/textarea>/i;
+    }
+    else {
+        $want_json = $want_response;
+        $have_json = $have_response;
+    }
+
+    is       $@,         '',      "$name eval $@";
+    is ref   $result,    'ARRAY', "$name result ARRAY";
+    is_deep  $result,    $expect, "$name result headers";
+    cmp_json $have_json, $want_json, "$name result body";
 };
 
 __DATA__
+#line 60
 [
     { name   => 'Invalid result', debug => 1,
       input  => '{"type":"rpc","tid":1,"action":"Foo","method":"foo_blessed",'.
-                ' "data":[]}',
+                ' "data":{}}',
       output => [
                     200,
                     [
@@ -121,6 +141,219 @@ __DATA__
                   ],
                 ],
     },
+    {
+        name   => 'Valid POST with invalid metadata 1', debug => 1,
+        input  => q|{"type":"rpc","tid":1,"action":"Meta",|.
+                  q|"method":"arg0","data":null,|.
+                  q|"metadata":[42]}|,
+        output => [ 200,
+                    [
+                        'Content-Type', 'application/json',
+                        'Content-Length', 213,
+                    ],
+                    [
+                        q|{"action":"Meta",|.
+                        q|"message":"ExtDirectMethodMeta.arg0requires|.
+                        q|2metadatavalue(s)butonly1areprovided",|.
+                        q|"method":"arg0","tid":1,"type":"exception",|.
+                        q|"where":"RPC::ExtDirect::API::Method->check_method_metadata"}|,
+                    ],
+                  ],
+            
+    },
+    {
+        name   => 'Valid POST with metadata 1', debug => 1,
+        input  => q|{"type":"rpc","tid":1,"action":"Meta",|.
+                  q|"method":"arg0","data":null,|.
+                  q|"metadata":[42,"foo"]}|,
+        output => [ 200,
+                    [
+                        'Content-Type', 'application/json',
+                        'Content-Length', 83,
+                    ],
+                    [
+                        q|{"action":"Meta","method":"arg0",|.
+                        q|"result":{"meta":[42,"foo"]},"tid":1,|.
+                        q|"type":"rpc"}|,
+                    ],
+                  ],
+            
+    },
+    {
+        name   => 'Valid POST with metadata 2', debug => 1,
+        input  => q|{"type":"rpc","tid":1,"action":"Meta",|.
+                  q|"method":"arg1_last","data":["foo"],|.
+                  q|"metadata":[42]}|,
+        output => [ 200,
+                    [
+                        'Content-Type', 'application/json',
+                        'Content-Length', 95,
+                    ],
+                    [
+                        q|{"action":"Meta","method":"arg1_last",|.
+                        q|"result":{"arg1":"foo","meta":[42]},|.
+                        q|"tid":1,"type":"rpc"}|,
+                    ],
+                  ],
+    },
+    {
+        name   => 'Valid POST with metadata 3', debug => 1,
+        input  => q|{"type":"rpc","tid":1,"action":"Meta",|.
+                  q|"method":"arg1_first","data":["foo"],|.
+                  q|"metadata":[42,43]}|,
+        output => [ 200,
+                    [
+                        'Content-Type', 'application/json',
+                        'Content-Length', 99,
+                    ],
+                    [
+                        q|{"action":"Meta","method":"arg1_first",|.
+                        q|"result":{"arg1":"foo","meta":[42,43]},|.
+                        q|"tid":1,"type":"rpc"}|,
+                    ],
+                  ],
+    },
+    {
+        name   => 'Valid POST with metadata 4', debug => 1,
+        input  => q|{"type":"rpc","tid":1,"action":"Meta",|.
+                  q|"method":"arg2_last","data":[42,43],|.
+                  q|"metadata":["foo","bar"]}|,
+        output => [ 200,
+                    [
+                        'Content-Type', 'application/json',
+                        'Content-Length', 105,
+                    ],
+                    [
+                        q|{"action":"Meta","method":"arg2_last",|.
+                        q|"result":{"arg1":42,"arg2":43,"meta":["foo"]},|.
+                        q|"tid":1,"type":"rpc"}|,
+                    ],
+                  ],
+    },
+    {
+        name   => 'Valid POST with metadata 5', debug => 1,
+        input  => q|{"type":"rpc","tid":1,"action":"Meta",|.
+                  q|"method":"arg2_middle","data":[44,45],|.
+                  q|"metadata":["fred","bonzo","qux"]}|,
+        output => [ 200,
+                    [
+                        'Content-Type', 'application/json',
+                        'Content-Length', 116,
+                    ],
+                    [
+                        q|{"action":"Meta","method":"arg2_middle",|.
+                        q|"result":{"arg1":44,"arg2":45,"meta":|.
+                        q|["fred","bonzo"]},"tid":1,"type":"rpc"}|,
+                    ],
+                  ],
+    },
+    {
+        name   => 'Valid POST with metadata 6', debug => 1,
+        input  => q|{"type":"rpc","tid":1,"action":"Meta",|.
+                  q|"method":"named_default",|.
+                  q|"data":{"foo":"bar","fred":"bonzo"},|.
+                  q|"metadata":[42]}|,
+        output => [ 200,
+                    [
+                        'Content-Type', 'application/json',
+                        'Content-Length', 113,
+                    ],
+                    [
+                        q|{"action":"Meta","method":"named_default",|.
+                        q|"result":{"foo":"bar","fred":"bonzo",|.
+                        q|"meta":[42]},"tid":1,"type":"rpc"}|,
+                    ],
+                  ],
+    },
+    {
+        name   => 'Valid POST with metadata 7', debug => 1,
+        input  => q|{"type":"rpc","tid":1,"action":"Meta",|.
+                  q|"method":"named_arg",|.
+                  q|"data":{"qux":"fred"},|.
+                  q|"metadata":["blerg"]}|,
+        output => [ 200,
+                    [
+                        'Content-Type', 'application/json',
+                        'Content-Length', 100,
+                    ],
+                    [
+                        q|{"action":"Meta","method":"named_arg",|.
+                        q|"result":{"meta":["blerg"],"qux":"fred"},|.
+                        q|"tid":1,"type":"rpc"}|,
+                    ],
+                  ],
+    },
+    {
+        name   => 'Valid POST with metadata 8', debug => 1,
+        input  => q|{"type":"rpc","tid":1,"action":"Meta",|.
+                  q|"method":"named_arg",|.
+                  q|"data":{"foo":"bar"},|.
+                  q|"metadata":["blerg"]}|,
+        output => [ 200,
+                    [
+                        'Content-Type', 'application/json',
+                        'Content-Length', 87,
+                    ],
+                    [
+                        q|{"action":"Meta","method":"named_arg",|.
+                        q|"result":{"meta":["blerg"]},|.
+                        q|"tid":1,"type":"rpc"}|,
+                    ],
+                  ],
+    },
+    {
+        name   => 'Valid POST with metadata 9', debug => 1,
+        input  => q|{"type":"rpc","tid":1,"action":"Meta",|.
+                  q|"method":"named_strict",|.
+                  q|"data":{"frob":"dux","frogg":"bonzo"},|.
+                  q|"metadata":{"foo":{"bar":{"baz":42}}}}|,
+        output => [ 200,
+                    [
+                        'Content-Type', 'application/json',
+                        'Content-Length', 136,
+                    ],
+                    [
+                        q|{"action":"Meta","method":"named_strict",|.
+                        q|"result":{"frob":"dux","frogg":"bonzo",|.
+                        q|"meta":{"foo":{"bar":{"baz":42}}}},|.
+                        q|"tid":1,"type":"rpc"}|,
+                    ],
+                  ],
+    },
+    {
+        name   => 'Valid POST with metadata 10', debug => 1,
+        input  => q|{"type":"rpc","tid":1,"action":"Meta",|.
+                  q|"method":"named_unstrict",|.
+                  q|"data":{"qux":null},"metadata":{}}|,
+        output => [ 200,
+                    [
+                        'Content-Type', 'application/json',
+                        'Content-Length', 96,
+                    ],
+                    [
+                        q|{"action":"Meta","method":"named_unstrict",|.
+                        q|"result":{"meta":{},"qux":null},|.
+                        q|"tid":1,"type":"rpc"}|,
+                    ],
+                  ],
+    },
+    {
+        name   => 'Valid POST with ancillary properties', debug => 1,
+        input  => q|{"type":"rpc","tid":1,"action":"Meta",|.
+                  q|"method":"aux","data":null,"foo":"bar",|.
+                  q|"token":"kaboom!"}|,
+        output => [ 200,
+                    [
+                        'Content-Type', 'application/json',
+                        'Content-Length', 102,
+                    ],
+                    [
+                        q|{"action":"Meta","method":"aux",|.
+                        q|"result":{"aux":{"foo":"bar","token":"kaboom!"}},|.
+                        q|"tid":1,"type":"rpc"}|,
+                    ],
+                  ],
+    },
     { name   => 'Invalid form request', debug => 1,
       input  => { extTID => 100, action => 'Bar', method => 'bar_baz',
                   type => 'rpc', data => undef, },
@@ -149,6 +382,65 @@ __DATA__
                   ],
                 ],
     },
+    {
+        name   => 'Form request with decode_params',
+        input  => { action => '/router_action', method => 'POST',
+                    extAction => 'Bar', extType => 'rpc', extTID => 432,
+                    extAction => 'Bar', extMethod => 'bar_baz',
+                    blerg => '["bar","baz"]',
+                    frob => '{"throbbe":["vita","voom"]}', },
+        output => [ 200, [ 'Content-Type', 'application/json',
+                           'Content-Length', 132, ],
+                    [
+                        q|{"action":"Bar","method":"bar_baz","type":"rpc",|.
+                        q|"result":{"blerg":"[\"bar\",\"baz\"]",|.
+                        q|"frob":{"throbbe":["vita","voom"]}},|.
+                        q|"tid":432}|,
+                    ],
+                  ],
+    },
+    {
+        name   => 'Form request with ordered metadata', debug => 1,
+        input  => {
+                    action => '/router_action', method => 'POST',
+                    extAction => 'Meta', extMethod => 'form_ordered',
+                    extTID => 42, extType => 'rpc',
+                    fred => 'frob', metadata => [42],
+                  },
+        output => [ 200,
+                    [
+                        'Content-Type', 'application/json',
+                        'Content-Length', 104,
+                    ],
+                    [
+                        q|{"action":"Meta","method":"form_ordered",|.
+                        q|"result":{"fred":"frob","metadata":[42]},|.
+                        q|"tid":42,"type":"rpc"}|,
+                    ],
+                  ],
+    },
+    {
+        name   => 'Form request with named metadata', debug => 1,
+        input  => {
+                    action => '/router_action', method => 'POST',
+                    extAction => 'Meta', extMethod => 'form_named',
+                    extTID => 58, extType => 'rpc',
+                    boogaloo => 1916, frogg => 'splurge',
+                    metadata => { foo => 1, bar => 2, baz => 3 },
+                  },
+        output => [ 200,
+                    [
+                        'Content-Type', 'application/json',
+                        'Content-Length', 137,
+                    ],
+                    [
+                        q|{"action":"Meta","method":"form_named",|.
+                        q|"result":{"_m":{"bar":2,"baz":3,"foo":1},|.
+                        q|"boogaloo":1916,"frogg":"splurge"},|.
+                        q|"tid":58,"type":"rpc"}|,
+                    ],
+                  ],
+    },
     { name   => 'Form request, upload one file', debug => 1,
       input  => { action => '/router.cgi', method => 'POST',
                     extAction => 'Bar', extMethod => 'bar_baz',
@@ -36,6 +36,7 @@ for my $test ( @$tests ) {
 };
 
 __DATA__
+#line 39
 [
     { name   => 'Two events', password => 'Usual, please',
       result => q|[{"data":["foo"],|.
@@ -1,11 +1,12 @@
 use strict;
 use warnings;
 
-use Test::More tests => 16;
+use Test::More tests => 22;
 
 use RPC::ExtDirect::Test::Util;
 
 use RPC::ExtDirect::Test::Pkg::Hooks;
+use RPC::ExtDirect::Test::Pkg::Meta;
 use RPC::ExtDirect::Test::Pkg::PollProvider;
 
 use RPC::ExtDirect::Router;
@@ -53,7 +54,7 @@ use RPC::ExtDirect::API before => \&before_hook, after => \&after_hook;
 use RPC::ExtDirect::API::Hook;
 
 # These variables get set when hooks are called
-my ($before, $after, $modify, $throw_up, $cancel);
+my ($before, $after, $modify, $modify_meta, $throw_up, $cancel);
 
 sub before_hook {
     my ($class, %params) = @_;
@@ -61,6 +62,8 @@ sub before_hook {
     $before = [ $class, { %params } ];
 
     $params{arg}->[0] = 'bar' if $modify;
+    
+    $modify_meta->(%params) if $modify_meta;
 
     die "Exception\n" if $throw_up;
 
@@ -76,6 +79,9 @@ sub after_hook {
 my $tests = eval do { local $/; <DATA> };
 die "Can't read DATA: $@\n" if $@;
 
+my %run_only = map { $_ => 1 } @ARGV;
+
+TEST:
 for my $test ( @$tests ) {
     my $name       = $test->{name};
     my $input      = $test->{input};
@@ -83,12 +89,17 @@ for my $test ( @$tests ) {
     my $env        = $test->{env};
     my $exp_before = $test->{expected_before};
     my $exp_after  = $test->{expected_after};
+    my $arg_type   = $test->{arg_type} || 'array';
     
-    $before = $after = $modify = $throw_up = $cancel = undef;
+    next TEST if %run_only && !$run_only{$name};
     
-    $modify   = $test->{modify};
-    $throw_up = $test->{throw_up};
-    $cancel   = $test->{cancel};
+    $before = $after = $modify = $modify_meta
+            = $throw_up = $cancel = undef;
+    
+    $modify      = $test->{modify};
+    $modify_meta = $test->{modify_meta};
+    $throw_up    = $test->{throw_up};
+    $cancel      = $test->{cancel};
     
     if ( $type eq 'router' ) {
         RPC::ExtDirect::Router->route($input, $env);
@@ -100,6 +111,33 @@ for my $test ( @$tests ) {
     # Orig is a closure in RPC::ExtDirect::Hook, impossible to take ref of
     eval { delete $before->[1]->{orig}; delete $after->[1]->{orig}; };
     $@ = undef;
+    
+    # Argument output is a list that would be coerced into a hash in
+    # real world Methods with no issues; however in tests it is captured
+    # in arrayref that is compared to the expected values.
+    # Hash randomization feature may change the order, screwing up
+    # some tests.
+    if ( $arg_type =~ /hash/i ) {
+        my $have_before = $before->[1];
+        
+        $have_before->{arg} = { @{ $have_before->{arg} } }
+            if ref $have_before->{arg};
+        
+        my $want_before = $exp_before->[1];
+        
+        $want_before->{arg} = { @{ $want_before->{arg} } }
+            if ref $want_before->{arg};
+        
+        my $have_after = $after->[1];
+        
+        $have_after->{arg} = { @{ $have_after->{arg} } }
+            if ref $have_after->{arg};
+        
+        my $want_after = $exp_after->[1];
+        
+        $want_after->{arg} = { @{ $want_after->{arg} } }
+            if ref $want_after->{arg};
+    }
 
     is_deep $before, $exp_before, "$name: before data";
     is_deep $after,  $exp_after,  "$name: after data";
@@ -126,6 +164,7 @@ sub get_hook_ref {
 }
 
 __DATA__
+#line 139
 [
     # Cancel Method call by throwing error
     {
@@ -138,22 +177,24 @@ __DATA__
         expected_before => [
             'main',
             {
-                before      => \&before_hook,
-                before_ref  => get_hook_ref(qw/ Hooks foo_hook before /),
-                instead     => undef,
-                instead_ref => undef,
-                after       => \&after_hook,
-                after_ref   => get_hook_ref(qw/ Hooks foo_hook after /),
-                package     => 'RPC::ExtDirect::Test::Pkg::Hooks',
-                method      => 'foo_hook',
-                code        => \&RPC::ExtDirect::Test::Pkg::Hooks::foo_hook,
-                arg         => [ 'foo' ],
-                env         => 'env',
-                param_names => undef,
-                param_no    => 1,
-                pollHandler => 0,
-                formHandler => 0,
-                method_ref  => get_method_ref(qw/ Hooks foo_hook /),
+                before        => \&before_hook,
+                before_ref    => get_hook_ref(qw/ Hooks foo_hook before /),
+                instead       => undef,
+                instead_ref   => undef,
+                after         => \&after_hook,
+                after_ref     => get_hook_ref(qw/ Hooks foo_hook after /),
+                package       => 'RPC::ExtDirect::Test::Pkg::Hooks',
+                method        => 'foo_hook',
+                code          => \&RPC::ExtDirect::Test::Pkg::Hooks::foo_hook,
+                arg           => [ 'foo' ],
+                env           => 'env',
+                param_names   => undef,
+                param_no      => 1,
+                pollHandler   => 0,
+                formHandler   => 0,
+                method_ref    => get_method_ref(qw/ Hooks foo_hook /),
+                metadata      => undef,
+                aux_data      => undef,
             },
         ],
         expected_after => [
@@ -178,6 +219,8 @@ __DATA__
                 formHandler   => 0,
                 method_called => undef,
                 method_ref    => get_method_ref(qw/ Hooks foo_hook /),
+                metadata      => undef,
+                aux_data      => undef,
             },
         ],
     },
@@ -193,22 +236,24 @@ __DATA__
         expected_before => [
             'main',
             {
-                before      => \&before_hook,
-                before_ref  => get_hook_ref(qw/ Hooks foo_hook before /),
-                instead     => undef,
-                instead_ref => undef,
-                after       => \&after_hook,
-                after_ref   => get_hook_ref(qw/ Hooks foo_hook after /),
-                package     => 'RPC::ExtDirect::Test::Pkg::Hooks',
-                method      => 'foo_hook',
-                code        => \&RPC::ExtDirect::Test::Pkg::Hooks::foo_hook,
-                arg         => [ 'foo' ],
-                env         => 'env',
-                param_names => undef,
-                param_no    => 1,
-                pollHandler => 0,
-                formHandler => 0,
-                method_ref  => get_method_ref(qw/ Hooks foo_hook /),
+                before        => \&before_hook,
+                before_ref    => get_hook_ref(qw/ Hooks foo_hook before /),
+                instead       => undef,
+                instead_ref   => undef,
+                after         => \&after_hook,
+                after_ref     => get_hook_ref(qw/ Hooks foo_hook after /),
+                package       => 'RPC::ExtDirect::Test::Pkg::Hooks',
+                method        => 'foo_hook',
+                code          => \&RPC::ExtDirect::Test::Pkg::Hooks::foo_hook,
+                arg           => [ 'foo' ],
+                env           => 'env',
+                param_names   => undef,
+                param_no      => 1,
+                pollHandler   => 0,
+                formHandler   => 0,
+                method_ref    => get_method_ref(qw/ Hooks foo_hook /),
+                metadata      => undef,
+                aux_data      => undef,
             },
         ],
         expected_after => [
@@ -233,6 +278,8 @@ __DATA__
                 formHandler   => 0,
                 method_called => undef,
                 method_ref    => get_method_ref(qw/ Hooks foo_hook /),
+                metadata      => undef,
+                aux_data      => undef,
             },
         ],
     },
@@ -247,22 +294,24 @@ __DATA__
         expected_before => [
             'main',
             {
-                before      => \&before_hook,
-                before_ref  => get_hook_ref(qw/ Hooks foo_hook before /),
-                instead     => undef,
-                instead_ref => undef,
-                after       => \&after_hook,
-                after_ref   => get_hook_ref(qw/ Hooks foo_hook after /),
-                package     => 'RPC::ExtDirect::Test::Pkg::Hooks',
-                method      => 'foo_hook',
-                code        => \&RPC::ExtDirect::Test::Pkg::Hooks::foo_hook,
-                arg         => [ 'foo' ],
-                env         => 'env',
-                param_names => undef,
-                param_no    => 1,
-                pollHandler => 0,
-                formHandler => 0,
-                method_ref  => get_method_ref(qw/ Hooks foo_hook /),
+                before        => \&before_hook,
+                before_ref    => get_hook_ref(qw/ Hooks foo_hook before /),
+                instead       => undef,
+                instead_ref   => undef,
+                after         => \&after_hook,
+                after_ref     => get_hook_ref(qw/ Hooks foo_hook after /),
+                package       => 'RPC::ExtDirect::Test::Pkg::Hooks',
+                method        => 'foo_hook',
+                code          => \&RPC::ExtDirect::Test::Pkg::Hooks::foo_hook,
+                arg           => [ 'foo' ],
+                env           => 'env',
+                param_names   => undef,
+                param_no      => 1,
+                pollHandler   => 0,
+                formHandler   => 0,
+                method_ref    => get_method_ref(qw/ Hooks foo_hook /),
+                metadata      => undef,
+                aux_data      => undef,
             },
         ],
         expected_after => [
@@ -287,6 +336,8 @@ __DATA__
                 formHandler   => 0,
                 method_called => \&RPC::ExtDirect::Test::Pkg::Hooks::foo_hook,
                 method_ref    => get_method_ref(qw/ Hooks foo_hook /),
+                metadata      => undef,
+                aux_data      => undef,
             },
         ],
     },
@@ -302,22 +353,24 @@ __DATA__
         expected_before => [
             'main',
             {
-                before      => \&before_hook,
-                before_ref  => get_hook_ref(qw/ Hooks foo_hook before /),
-                instead     => undef,
-                instead_ref => undef,
-                after       => \&after_hook,
-                after_ref   => get_hook_ref(qw/ Hooks foo_hook after /),
-                package     => 'RPC::ExtDirect::Test::Pkg::Hooks',
-                method      => 'foo_hook',
-                code        => \&RPC::ExtDirect::Test::Pkg::Hooks::foo_hook,
-                arg         => [ 'bar' ],
-                env         => 'env',
-                param_names => undef,
-                param_no    => 1,
-                pollHandler => 0,
-                formHandler => 0,
-                method_ref  => get_method_ref(qw/ Hooks foo_hook /),
+                before        => \&before_hook,
+                before_ref    => get_hook_ref(qw/ Hooks foo_hook before /),
+                instead       => undef,
+                instead_ref   => undef,
+                after         => \&after_hook,
+                after_ref     => get_hook_ref(qw/ Hooks foo_hook after /),
+                package       => 'RPC::ExtDirect::Test::Pkg::Hooks',
+                method        => 'foo_hook',
+                code          => \&RPC::ExtDirect::Test::Pkg::Hooks::foo_hook,
+                arg           => [ 'bar' ],
+                env           => 'env',
+                param_names   => undef,
+                param_no      => 1,
+                pollHandler   => 0,
+                formHandler   => 0,
+                method_ref    => get_method_ref(qw/ Hooks foo_hook /),
+                metadata      => undef,
+                aux_data      => undef,
             },
         ],
         expected_after => [
@@ -342,6 +395,193 @@ __DATA__
                 formHandler   => 0,
                 method_called => \&RPC::ExtDirect::Test::Pkg::Hooks::foo_hook,
                 method_ref    => get_method_ref(qw/ Hooks foo_hook /),
+                metadata      => undef,
+                aux_data      => undef,
+            },
+        ],
+    },
+    
+    # Passing metadata to hooks
+    {
+        name  => 'Passing metadata to hooks',
+        input => q|{"type":"rpc","tid":1,"action":"Meta",|.
+                 q|"method":"arg0","data":null,|.
+                 q|"metadata":[42,43]}|,
+        env   => 'env',
+        type  => 'router',
+        expected_before => [
+            'main',
+            {
+                before        => \&before_hook,
+                before_ref    => get_hook_ref(qw/ Meta arg0 before /),
+                instead       => undef,
+                instead_ref   => undef,
+                after         => \&after_hook,
+                after_ref     => get_hook_ref(qw/ Meta arg0 after /),
+                package       => 'RPC::ExtDirect::Test::Pkg::Meta',
+                method        => 'arg0',
+                code          => \&RPC::ExtDirect::Test::Pkg::Meta::arg0,
+                arg           => [ [42, 43] ],
+                env           => 'env',
+                param_names   => undef,
+                param_no      => 0,
+                pollHandler   => 0,
+                formHandler   => 0,
+                method_ref    => get_method_ref(qw/ Meta arg0 /),
+                metadata      => [42, 43],
+                aux_data      => undef,
+            },
+        ],
+        expected_after => [
+            'main',
+            {
+                before        => \&before_hook,
+                before_ref    => get_hook_ref(qw/ Meta arg0 before /),
+                instead       => undef,
+                instead_ref   => undef,
+                after         => \&after_hook,
+                after_ref     => get_hook_ref(qw/ Meta arg0 after /),
+                package       => 'RPC::ExtDirect::Test::Pkg::Meta',
+                method        => 'arg0',
+                code          => \&RPC::ExtDirect::Test::Pkg::Meta::arg0,
+                arg           => [ [42, 43] ],
+                env           => 'env',
+                result        => { meta => [42, 43] },
+                exception     => '',
+                param_names   => undef,
+                param_no      => 0,
+                pollHandler   => 0,
+                formHandler   => 0,
+                method_called => \&RPC::ExtDirect::Test::Pkg::Meta::arg0,
+                method_ref    => get_method_ref(qw/ Meta arg0 /),
+                metadata      => [42, 43],
+                aux_data      => undef,
+            },
+        ],
+    },
+    
+    {
+        name  => 'Munging metadata in a hook',
+        input => q|{"type":"rpc","tid":1,"action":"Meta",|.
+                 q|"method":"named_arg","data":{"fred":"frob"},|.
+                 q|"metadata":["quack"]}|,
+        env   => 'env',
+        type  => 'router',
+        arg_type => 'hash',
+        modify_meta => sub {
+            my (%arg) = @_;
+            
+            my @meta = @{ delete $arg{metadata} };
+            
+            s/a/i/g for @meta;
+            
+            push @{ $arg{arg} }, 'bar', \@meta;
+        },
+        expected_before => [
+            'main',
+            {
+                before        => \&before_hook,
+                before_ref    => get_hook_ref(qw/ Meta named_arg before /),
+                instead       => undef,
+                instead_ref   => undef,
+                after         => \&after_hook,
+                after_ref     => get_hook_ref(qw/ Meta named_arg after /),
+                package       => 'RPC::ExtDirect::Test::Pkg::Meta',
+                method        => 'named_arg',
+                code          => \&RPC::ExtDirect::Test::Pkg::Meta::named_arg,
+                arg           => ['foo', ['quack'], 'fred', 'frob', 'bar', ['quick']],
+                env           => 'env',
+                param_names   => [],
+                param_no      => undef,
+                pollHandler   => 0,
+                formHandler   => 0,
+                method_ref    => get_method_ref(qw/ Meta named_arg /),
+                metadata      => ['quack'],
+                aux_data      => undef,
+            },
+        ],
+        expected_after => [
+            'main',
+            {
+                before        => \&before_hook,
+                before_ref    => get_hook_ref(qw/ Meta named_arg before /),
+                instead       => undef,
+                instead_ref   => undef,
+                after         => \&after_hook,
+                after_ref     => get_hook_ref(qw/ Meta named_arg after /),
+                package       => 'RPC::ExtDirect::Test::Pkg::Meta',
+                method        => 'named_arg',
+                code          => \&RPC::ExtDirect::Test::Pkg::Meta::named_arg,
+                arg           => ['foo', ['quack'], 'fred', 'frob', 'bar', ['quick']],
+                env           => 'env',
+                result        => { fred => 'frob', bar => ['quick'], meta => ['quack'] },
+                exception     => '',
+                param_names   => [],
+                param_no      => undef,
+                pollHandler   => 0,
+                formHandler   => 0,
+                method_called => \&RPC::ExtDirect::Test::Pkg::Meta::named_arg,
+                method_ref    => get_method_ref(qw/ Meta named_arg /),
+                metadata      => ['quack'],
+                aux_data      => undef,
+            },
+        ],
+    },
+    
+    {
+        name  => 'Ancillary data in hooks',
+        input => q|{"type":"rpc","tid":1,"action":"Hooks",|.
+                 q|"method":"foo_hook","data":[42],|.
+                 q|"token":"kaboom!","frob":"bazoo"}|,
+        env   => 'env',
+        type  => 'router',
+        expected_before => [
+            'main',
+            {
+                before        => \&before_hook,
+                before_ref    => get_hook_ref(qw/ Hooks foo_hook before /),
+                instead       => undef,
+                instead_ref   => undef,
+                after         => \&after_hook,
+                after_ref     => get_hook_ref(qw/ Hooks foo_hook after /),
+                package       => 'RPC::ExtDirect::Test::Pkg::Hooks',
+                method        => 'foo_hook',
+                code          => \&RPC::ExtDirect::Test::Pkg::Hooks::foo_hook,
+                arg           => [42],
+                env           => 'env',
+                param_names   => undef,
+                param_no      => 1,
+                pollHandler   => 0,
+                formHandler   => 0,
+                method_ref    => get_method_ref(qw/ Hooks foo_hook /),
+                metadata      => undef,
+                aux_data      => { frob => 'bazoo', token => 'kaboom!' },
+            },
+        ],
+        expected_after => [
+            'main',
+            {
+                before        => \&before_hook,
+                before_ref    => get_hook_ref(qw/ Hooks foo_hook before /),
+                instead       => undef,
+                instead_ref   => undef,
+                after         => \&after_hook,
+                after_ref     => get_hook_ref(qw/ Hooks foo_hook after /),
+                package       => 'RPC::ExtDirect::Test::Pkg::Hooks',
+                method        => 'foo_hook',
+                code          => \&RPC::ExtDirect::Test::Pkg::Hooks::foo_hook,
+                arg           => [42],
+                env           => 'env',
+                result        => [ 'RPC::ExtDirect::Test::Pkg::Hooks', 42 ],
+                exception     => '',
+                param_names   => undef,
+                param_no      => 1,
+                pollHandler   => 0,
+                formHandler   => 0,
+                method_called => \&RPC::ExtDirect::Test::Pkg::Hooks::foo_hook,
+                method_ref    => get_method_ref(qw/ Hooks foo_hook /),
+                metadata      => undef,
+                aux_data      => { frob => 'bazoo', token => 'kaboom!' },
             },
         ],
     },
@@ -355,22 +595,24 @@ __DATA__
         expected_before => [
             'main',
             {
-                before      => \&before_hook,
-                before_ref  => get_hook_ref(qw/ PollProvider foo before /),
-                instead     => undef,
-                instead_ref => undef,
-                after       => \&after_hook,
-                after_ref   => get_hook_ref(qw/ PollProvider foo after /),
-                package     => 'RPC::ExtDirect::Test::Pkg::PollProvider',
-                method      => 'foo',
-                code        => \&RPC::ExtDirect::Test::Pkg::PollProvider::foo,
-                arg         => [ ],
-                env         => 'env',
-                param_names => undef,
-                param_no    => undef,
-                pollHandler => 1,
-                formHandler => 0,
-                method_ref  => get_method_ref(qw/ PollProvider foo /),
+                before        => \&before_hook,
+                before_ref    => get_hook_ref(qw/ PollProvider foo before /),
+                instead       => undef,
+                instead_ref   => undef,
+                after         => \&after_hook,
+                after_ref     => get_hook_ref(qw/ PollProvider foo after /),
+                package       => 'RPC::ExtDirect::Test::Pkg::PollProvider',
+                method        => 'foo',
+                code          => \&RPC::ExtDirect::Test::Pkg::PollProvider::foo,
+                arg           => [ ],
+                env           => 'env',
+                param_names   => undef,
+                param_no      => undef,
+                pollHandler   => 1,
+                formHandler   => 0,
+                method_ref    => get_method_ref(qw/ PollProvider foo /),
+                metadata      => undef,
+                aux_data      => undef,
             },
         ],
         expected_after => [
@@ -395,6 +637,8 @@ __DATA__
                 formHandler   => 0,
                 method_called => undef,
                 method_ref    => get_method_ref(qw/ PollProvider foo /),
+                metadata      => undef,
+                aux_data      => undef,
             },
         ],
     },
@@ -408,22 +652,24 @@ __DATA__
         expected_before => [
             'main',
             {
-                before      => \&before_hook,
-                before_ref  => get_hook_ref(qw/ PollProvider foo before /),
-                instead     => undef,
-                instead_ref => undef,
-                after       => \&after_hook,
-                after_ref   => get_hook_ref(qw/ PollProvider foo after /),
-                package     => 'RPC::ExtDirect::Test::Pkg::PollProvider',
-                method      => 'foo',
-                code        => \&RPC::ExtDirect::Test::Pkg::PollProvider::foo,
-                arg         => [ ],
-                env         => 'env',
-                param_names => undef,
-                param_no    => undef,
-                pollHandler => 1,
-                formHandler => 0,
-                method_ref  => get_method_ref(qw/ PollProvider foo /),
+                before        => \&before_hook,
+                before_ref    => get_hook_ref(qw/ PollProvider foo before /),
+                instead       => undef,
+                instead_ref   => undef,
+                after         => \&after_hook,
+                after_ref     => get_hook_ref(qw/ PollProvider foo after /),
+                package       => 'RPC::ExtDirect::Test::Pkg::PollProvider',
+                method        => 'foo',
+                code          => \&RPC::ExtDirect::Test::Pkg::PollProvider::foo,
+                arg           => [ ],
+                env           => 'env',
+                param_names   => undef,
+                param_no      => undef,
+                pollHandler   => 1,
+                formHandler   => 0,
+                method_ref    => get_method_ref(qw/ PollProvider foo /),
+                metadata      => undef,
+                aux_data      => undef,
             },
         ],
         expected_after => [
@@ -448,6 +694,8 @@ __DATA__
                 formHandler   => 0,
                 method_called => undef,
                 method_ref    => get_method_ref(qw/ PollProvider foo /),
+                metadata      => undef,
+                aux_data      => undef,
             },
         ],
     },
@@ -461,22 +709,24 @@ __DATA__
         expected_before => [
             'main',
             {
-                before      => \&before_hook,
-                before_ref  => get_hook_ref(qw/ PollProvider foo before /),
-                instead     => undef,
-                instead_ref => undef,
-                after       => \&after_hook,
-                after_ref   => get_hook_ref(qw/ PollProvider foo after /),
-                package     => 'RPC::ExtDirect::Test::Pkg::PollProvider',
-                method      => 'foo',
-                code        => \&RPC::ExtDirect::Test::Pkg::PollProvider::foo,
-                arg         => [ 'bar' ],
-                env         => 'env',
-                param_names => undef,
-                param_no    => undef,
-                pollHandler => 1,
-                formHandler => 0,
-                method_ref  => get_method_ref(qw/ PollProvider foo /),
+                before        => \&before_hook,
+                before_ref    => get_hook_ref(qw/ PollProvider foo before /),
+                instead       => undef,
+                instead_ref   => undef,
+                after         => \&after_hook,
+                after_ref     => get_hook_ref(qw/ PollProvider foo after /),
+                package       => 'RPC::ExtDirect::Test::Pkg::PollProvider',
+                method        => 'foo',
+                code          => \&RPC::ExtDirect::Test::Pkg::PollProvider::foo,
+                arg           => [ 'bar' ],
+                env           => 'env',
+                param_names   => undef,
+                param_no      => undef,
+                pollHandler   => 1,
+                formHandler   => 0,
+                method_ref    => get_method_ref(qw/ PollProvider foo /),
+                metadata      => undef,
+                aux_data      => undef,
             },
         ],
         expected_after => [
@@ -501,6 +751,8 @@ __DATA__
                 formHandler   => 0,
                 method_called => \&RPC::ExtDirect::Test::Pkg::PollProvider::foo,
                 method_ref    => get_method_ref(qw/ PollProvider foo /),
+                metadata      => undef,
+                aux_data      => undef,
             },
         ],
     },
@@ -513,22 +765,24 @@ __DATA__
         expected_before => [
             'main',
             {
-                before      => \&before_hook,
-                before_ref  => get_hook_ref(qw/ PollProvider foo before /),
-                instead     => undef,
-                instead_ref => undef,
-                after       => \&after_hook,
-                after_ref   => get_hook_ref(qw/ PollProvider foo after /),
-                package     => 'RPC::ExtDirect::Test::Pkg::PollProvider',
-                method      => 'foo',
-                code        => \&RPC::ExtDirect::Test::Pkg::PollProvider::foo,
-                arg         => [ ],
-                env         => 'env',
-                param_names => undef,
-                param_no    => undef,
-                pollHandler => 1,
-                formHandler => 0,
-                method_ref  => get_method_ref(qw/ PollProvider foo /),
+                before        => \&before_hook,
+                before_ref    => get_hook_ref(qw/ PollProvider foo before /),
+                instead       => undef,
+                instead_ref   => undef,
+                after         => \&after_hook,
+                after_ref     => get_hook_ref(qw/ PollProvider foo after /),
+                package       => 'RPC::ExtDirect::Test::Pkg::PollProvider',
+                method        => 'foo',
+                code          => \&RPC::ExtDirect::Test::Pkg::PollProvider::foo,
+                arg           => [ ],
+                env           => 'env',
+                param_names   => undef,
+                param_no      => undef,
+                pollHandler   => 1,
+                formHandler   => 0,
+                method_ref    => get_method_ref(qw/ PollProvider foo /),
+                metadata      => undef,
+                aux_data      => undef,
             },
         ],
         expected_after => [
@@ -553,6 +807,8 @@ __DATA__
                 formHandler   => 0,
                 method_called => \&RPC::ExtDirect::Test::Pkg::PollProvider::foo,
                 method_ref    => get_method_ref(qw/ PollProvider foo /),
+                metadata      => undef,
+                aux_data      => undef,
             },
         ],
     },
@@ -64,7 +64,7 @@ my %test_for = (
             foo_blessed => { package => 'RPC::ExtDirect::Test::Foo',
                          method => 'foo_blessed', param_no => undef,
                          formHandler => 0, pollHandler => 0,
-                         param_names => undef, },
+                         param_names => [], },
 
         },
     },
@@ -32,19 +32,19 @@ Ext.app.REMOTING_API = {
     "actions":{
         "Bar":[
                 { "len":5, "name":"bar_bar" },
-                { "formHandler":true, "len":0, "name":"bar_baz" },
+                { "formHandler":true, "name":"bar_baz" },
                 { "len":4, "name":"bar_foo" }
               ],
         "Foo":[
                 { "len":2, "name":"foo_bar" },
                 { "name":"foo_baz", "params":["foo","bar","baz"] },
-                { "name":"foo_blessed" },
+                { "name":"foo_blessed", "params":[], "strict":false },
                 { "len":1, "name":"foo_foo" },
                 { "len":0, "name":"foo_zero" }
               ],
         "Qux":[
                 { "len":5, "name":"bar_bar" },
-                { "formHandler":true, "len":0, "name":"bar_baz" },
+                { "formHandler":true, "name":"bar_baz" },
                 { "len":4, "name":"bar_foo" },
                 { "len":2, "name":"foo_bar" },
                 { "name":"foo_baz", "params":["foo","bar","baz"] },
@@ -77,19 +77,19 @@ Ext.app.REMOTE_CALL_API = {
     "actions":{
         "Bar":[
                 { "len":5, "name":"bar_bar" },
-                { "formHandler":true, "len":0, "name":"bar_baz" },
+                { "formHandler":true, "name":"bar_baz" },
                 { "len":4, "name":"bar_foo" }
               ],
         "Foo":[
                 { "len":2, "name":"foo_bar" },
                 { "name":"foo_baz", "params":["foo","bar","baz"] },
-                { "name":"foo_blessed" },
+                { "name":"foo_blessed", "params":[], "strict":false },
                 { "len":1, "name":"foo_foo" },
                 { "len":0, "name":"foo_zero" }
               ],
         "Qux":[
                 { "len":5, "name":"bar_bar" },
-                { "formHandler":true, "len":0, "name":"bar_baz" },
+                { "formHandler":true, "name":"bar_baz" },
                 { "len":4, "name":"bar_foo" },
                 { "len":2, "name":"foo_bar" },
                 { "name":"foo_baz", "params":["foo","bar","baz"] },
@@ -37,19 +37,19 @@ Ext.app.REMOTE_CALL_API = {
     "actions":{
         "Bar":[
                 { "len":5, "name":"bar_bar" },
-                { "formHandler":true, "len":0, "name":"bar_baz" },
+                { "formHandler":true, "name":"bar_baz" },
                 { "len":4, "name":"bar_foo" }
               ],
         "Foo":[
                 { "len":2, "name":"foo_bar" },
                 { "name":"foo_baz", "params":["foo","bar","baz"] },
-                { "name":"foo_blessed" },
+                { "name":"foo_blessed", "params":[], "strict":false },
                 { "len":1, "name":"foo_foo" },
                 { "len":0, "name":"foo_zero" }
               ],
         "Qux":[
                 { "len":5, "name":"bar_bar" },
-                { "formHandler":true, "len":0, "name":"bar_baz" },
+                { "formHandler":true, "name":"bar_baz" },
                 { "len":4, "name":"bar_foo" },
                 { "len":2, "name":"foo_bar" },
                 { "name":"foo_baz", "params":["foo","bar","baz"] },
@@ -37,19 +37,19 @@ Ext.app.REMOTE_CALL_API = {
     "actions":{
         "Bar":[
                 { "len":5, "name":"bar_bar" },
-                { "formHandler":true, "len":0, "name":"bar_baz" },
+                { "formHandler":true, "name":"bar_baz" },
                 { "len":4, "name":"bar_foo" }
               ],
         "Foo":[
                 { "len":2, "name":"foo_bar" },
                 { "name":"foo_baz", "params":["foo","bar","baz"] },
-                { "name":"foo_blessed" },
+                { "name":"foo_blessed", "params":[], "strict":false },
                 { "len":1, "name":"foo_foo" },
                 { "len":0, "name":"foo_zero" }
               ],
         "Qux":[
                 { "len":5, "name":"bar_bar" },
-                { "formHandler":true, "len":0, "name":"bar_baz" },
+                { "formHandler":true, "name":"bar_baz" },
                 { "len":4, "name":"bar_foo" },
                 { "len":2, "name":"foo_bar" },
                 { "name":"foo_baz", "params":["foo","bar","baz"] },
@@ -61,7 +61,7 @@ __DATA__
 [
     { name   => 'Invalid result', debug => 1,
       input  => '{"type":"rpc","tid":1,"action":"Foo","method":"foo_blessed",'.
-                ' "data":[]}',
+                ' "data":{}}',
       output => [
                     200,
                     [
@@ -125,6 +125,16 @@ for my $test ( @$tests ) {
         };
     }
 
+    # Same thing with metadata and aux_data as above; RPC::ExtDirect code
+    # earlier than 3.1 is not aware of it and so no harm if it's deleted
+    {
+        local $@;
+        eval {
+            delete $before->[1]->{$_}, delete $after->[1]->{$_}
+                for qw/ metadata aux_data /;
+        };
+    }
+
     is_deep $before, $exp_before, "$name: before data";
     is_deep $after,  $exp_after,  "$name: after data";
 };