Bot API Transformers
Middleware is a function that handles a context object, i.e. incoming data.
grammY also provides you with the inverse. A transformer function is a function that handles outgoing data, i.e.
- a method name of the Bot API to call, and
- a payload object that matches the method.
Instead of having next
as the last argument to invoke downstream middleware, you receive prev
as the first argument to utilize upstream transformer functions. Looking at the type signature of Transformer
(grammY API Reference), we can see how it reflects that. Note that Payload<M
refers to the payload object that has to match the given method, and that ApiResponse<ApiCallResult<M
is the return type of the invoked method.
The last invoked transformer function is a built-in caller that does things like JSON serialization of certain fields, and eventually calling fetch
.
There is no equivalent of a Composer
class for transformer functions because that’s probably overkill, but if you need it, you can write your own. PR welcome! 😉
Installing a Transformer Function
Transformer functions can be installed on bot
. Here is an example for a transformer function that does nothing:
// Pass-through transformer function
bot.api.config.use((prev, method, payload, signal) =>
prev(method, payload, signal)
);
// Comparison with pass-through middleware
bot.use((ctx, next) => next());
Here is an example of a transformer function that prevents all API calls from happening:
// Incorrectly return undefined instead of the respective object types.
bot.api.config.use((prev, method, payload) => undefined as any);
You can also install transformer functions on the context object’s API object. The transformer function will then only be used temporarily for the API requests that are performed on that specific context object. Calls to bot
are left unaffected. Calls via context objects of concurrently running middleware are left unaffected, as well. As soon as the respective middleware completes, the transformer function is discarded.
bot.on("message", (ctx) => {
// Install on all context objects that process messages.
ctx.api.config.use((prev, method, payload, signal) =>
prev(method, payload, signal)
);
});
The
signal
parameter should be always passed toprev
. It allows canceling requests and is important forbot
to work..stop
Transformer functions installed on bot
will be pre-installed on every ctx
object. Thus, calls to ctx
will be transformed by both those transformers on ctx
, as well as those transformers installed on bot
.
Use Cases of Transformer Functions
Transformer functions are as flexible as middleware, and they have just as many different applications.
For instance, the grammY menu plugin installs a transformer function to turn outgoing menu instances into the correct payload. You can also use them to
- implement flood control,
- mock API requests during testing,
- add retry behavior, or
- more.
Note, however, that retrying an API call can have odd side-effects: if you call sendDocument
and pass a readable stream instance to InputFile
, then the stream will be read the first time the request is tried. If you invoke prev
again, the stream may already be (partially) consumed, hence leading to broken files. It is therefore a more reliable way to pass file paths to InputFile
, so grammY can recreate the stream as necessary.
API Flavoring
grammY features context flavors that can be used to adjust the context type. This includes API methods—both those that are directly on the context object such as ctx
, and all methods in ctx
and ctx
. However, you cannot adjust the types of bot
and bot
via context flavors.
This is why grammY supports API flavors. They solve this problem:
import { Api, Bot, Context } from "grammy";
import { SomeApiFlavor, someContextFlavor, somePlugin } from "some-plugin";
// Context flavoring
type MyContext = Context & SomeContextFlavor;
// API flavoring
type MyApi = Api & SomeApiFlavor;
// Use both flavors.
const bot = new Bot<MyContext, MyApi>("my-token");
// Use a plugin.
bot.api.config.use(somePlugin());
// Now call `bot.api` with adjusted types from API flavor.
bot.api.somePluginMethod();
// Also, use adjusted context type from context flavor.
bot.on("message", (ctx) => ctx.api.somePluginMethod());
API flavors work exactly anaolgously to context flavors. There are both additive and transformative API flavors, and multiple API flavors can be combined the same way as you would do with context flavors. If you are unsure how this works, head back to the section about context flavors in the guide.