Middleware
The Apophis Middleware system is a highly generic and extensible framework to allow third parties to hook into all sorts of procedures.
Registering Middleware
As an Apophis SDK consumer, you will only ever need to add middlewares like so:
import { Apophis, DefaultCosmosMiddlewares } from '@apophis-sdk/cosmos';
Apophis.use(DefaultCosmosMiddlewares);This will register your desired middlewares. You will rarely ever need to interact with the middleware system directly.
De-Duping
When registering the same middleware twice, either directly or inadvertently through bundled middleware systems defined by another library, only the first instance of this middleware will be registered. The order in which middlewares are registered matters. While it generally shouldn't cause any issues, this de-duplication could potentially be a source of confusion. Thus, all bundled middleware should also be exported in isolation for troubleshooting purposes.
Implementing Middlewares
Middlewares are arbitrary multi-level objects that are registered in a global array and executed by some strategy defined at the callsite. If a middleware does not implement a method, it is automatically skipped. The following examples are valid middlewares:
import { type MiddlewareImpl } from '@apophis-sdk/core/middleware.js';
const MyMiddleware1 = {
addresses: {
alias(address: string): string {
return address;
},
},
} satisfies MiddlewareImpl;
const MyMiddleware2: MiddlewareImpl = {
addresses: {
resolve(address: string): string {
return address;
},
},
encoding: {
encode(network: NetworkConfig, encoding: string, value: unknown) {
if (network.ecosystem !== 'cosmos') return;
if (encoding !== 'json') return;
return JSON.stringify(value);
},
},
};
const MyMiddleware3 = {} satisfies MiddlewareImpl;Pipelines
Callsites of middlewares must specify the strategy by which to execute the middleware stack. There are various strategies implemented by the internal MiddlewarePipeline class, which adheres to the following interface:
interface MiddlewarePipeline<KP extends string[]> {
/** Helper to invert the pipeline execution order */
inv(): MiddlewarePipeline<KP>;
/** Send a single value through the entire pipeline, transforming it along
* the way.
*
* If a middleware returns `undefined` it is simply skipped and the value
* remains unchanged in this round.
*/
transform(arg: TransformType<KP>): TransformType<KP>;
/** Call every middleware endpoint in order, passing in the given arguments.
* The first middleware that returns any value other than `undefined` wins
* and its result returned to the callsite.
*
* If no middleware returns a value, throws a MiddlewarePipelineError.
*/
fifo(...args: FifoArgs<KP>): Defined<FifoResult<KP>>;
/** Like `fifo` but returns `undefined` rather than throwing an error. */
fifoMaybe(...args: FifoArgs<KP>): Defined<FifoResult<KP>> | undefined;
/** Like `fifo` but async. Awaits every middleware in order and can thus be slow. */
fifoAsync(...args: FifoArgs<KP>): Promise<Awaited<Defined<FifoResult<KP>>>>;
/** Like `fifoAsync` but returns `undefined` rather than throwing an error. */
fifoAsyncMaybe(...args: FifoArgs<KP>): Promise<Awaited<Defined<FifoResult<KP>>> | undefined>;
/** A way for callsites to notify middlewares of some occurrence.
* The notification handlers may be async. All errors and rejections will
* be caught and logged with `console.error`.
*/
notify(...args: NotifyArgs<KP>): Promise<void>;
/** Like `notify` but the handlers cannot be async. */
notifySync(...args: NotifyArgs<KP>): void;
/** A reducer to iterate over and invoke all middlewares in order,
* aggregating their result by some arbitrary logic.
*
* Admittedly, typing this reducer is tough and needs more work.
*/
reduce(args: any[], reducer: (result: any, response: any) => any, initial: any): any;
}KP is the key path to the middleware and serves to extract the correct function signature. Following is an example of how to use the mw function which produces such a MiddlewarePipeline:
import { mw } from '@apophis-sdk/core';
const addr = mw('addresses', 'compute').inv().fifo(network, pubkey);Middleware Stack
The mw method exposes some additional properties. One of these properties is mw.stack, which is an array of all registered middlewares. If necessary, you can inspect or even alter the stack:
import { mw } from '@apophis-sdk/core/middleware.js';
import { MemoryAddressBook } from '@apophis-sdk/core/address.js';
// Insert a middleware before `MemoryAddressBook` or append to the end if not found
let idx = mw.stack.findIndex(m => m instanceof MemoryAddressBook);
if (idx !== -1) {
mw.stack.splice(idx, 0, MyMiddleware);
} else {
mw.stack.push(MyMiddleware);
}Last updated