Skip to content

Commit

Permalink
add plugins & hooks
Browse files Browse the repository at this point in the history
  • Loading branch information
fracek committed Dec 4, 2024
1 parent 6cf8127 commit cf6afa0
Show file tree
Hide file tree
Showing 2 changed files with 189 additions and 1 deletion.
183 changes: 183 additions & 0 deletions docs/v2/getting-started/plugins/index.mdx
Original file line number Diff line number Diff line change
@@ -0,0 +1,183 @@
---
title: Plugins & Hooks
description: "Learn how to use plugins to extend the functionality of your indexers."
diataxis: explanation
updatedAt: 2024-12-04
---

# Plugins & Hooks

Indexers are extensible through hooks and plugins. Hooks are functions that are
called at specific points in the indexer's lifecycle. Plugins are components
that contain reusable hooks callbacks.

## Hooks

The following hooks are available in all indexers.

- `run:before`: Called before the indexer starts running.
- `run:after`: Called after the indexer has finished running.
- `connect:before`: Called before the indexer connects to the DNA stream. Can be used to change the request or stream options.
- `connect:after`: Called after the indexer has connected to the DNA stream.
- `connect:factory`: Called before the indexer reconnects to the DNA stream with a new filter (in factory mode).
- `message` and `message:*`: Called for each message received from the DNA stream.
- `handler:before`: Called before the indexer handles a block.
- `handler:after`: Called after the indexer has handled a block successfully.
- `handler:error`: Called if the indexer encounters an error while handling a block.
- `transaction:commit`: Called after the indexer's sink transaction has been committed.

## Using plugins

You can register plugins in the indexer's configuration, under the `plugins` key.

```ts [my-indexer.indexer.ts]
import { BeaconChainStream } from "@apibara/beaconchain";
import { defineIndexer } from "@apibara/indexer";

import { myAwesomePlugin } from "@/lib/my-plugin.ts";

export default defineIndexer(BeaconChainStream)({
streamUrl: "https://beaconchain.preview.apibara.org",
filter: { /* ... */ },
plugins: [myAwesomePlugin()],
async transform({ block: { header, validators } }) {
/* ... */
},
});
```

## Building plugins

Developers can create new plugins to be shared across multiple indexers or projects. Plugins use the available hooks
to extend the functionality of indexers.

The main way to define a plugin is by using the `defineIndexerPlugin` function. This function takes a callback with
the indexer as parameter, the plugin should register itself with the indexer's hooks.
When the runner runs the indexer, all the relevant hooks are called.

```ts [my-plugin.ts]
import type { Cursor } from "@apibara/protocol";
import { defineIndexerPlugin } from "@apibara/indexer/plugins";

export function myAwesomePlugin<TFilter, TBlock, TTxnParams>() {
return defineIndexerPlugin<TFilter, TBlock, TTxnParams>((indexer) => {
let lastCursor: Cursor | undefined;

indexer.hooks.hook("transaction:commit", ({ endCursor }) => {
if (endCursor) {
lastCursor = endCursor;
}
});

indexer.hooks.hook("run:after", () => {
console.log(`Last cursor: ${lastCursor}`);
});
});
}
```

## Inline hooks

For all cases where you want to use a hook without creating a plugin, you can use the `hooks` property of the indexer.

```ts [my-indexer.indexer.ts]
import { BeaconChainStream } from "@apibara/beaconchain";
import { defineIndexer } from "@apibara/indexer";

export default defineIndexer(BeaconChainStream)({
streamUrl: "https://beaconchain.preview.apibara.org",
filter: { /* ... */ },
async transform({ block: { header, validators } }) {
/* ... */
},
hooks: {
async "connect:before"({ request, options }) {
// Do something before the indexer connects to the DNA stream.
},
},
});
```

## Indexer lifecycle

The following Javascript pseudocode shows the indexer's lifecycle. This should give you a good understand of
when hooks are called.

```js
function run(indexer) {
indexer.callHook("run:before");

const sink = indexer.sink;

// Create the request based on the indexer's configuration.
const request = Request.create({
filter: indexer.filter,
startingCursor: indexer.startingCursor,
finality: indexer.finality,
});

// Stream options.
const options = {};

indexer.callHook("connect:before", { request, options });

let stream = indexer.streamData(request, options);

indexer.callHook("connect:after");

while (true) {
const { message, done } = stream.next();

if (done) {
break;
}

indexer.callHook("message", { message });

switch (message._tag) {
case "data": {
const { block, endCursor, finality } = message.data
sink.transaction(() => {
if (indexer.isFactoryMode()) {
// Handle the factory portion of the indexer data.
// Implementation detail is not important here.
const newFilter = indexer.factory();
const request = Request.create(/* ... */);

indexer.callHook("connect:factory", { request, endCursor });
stream = indexer.streamData(request, options);
}

indexer.callHook("handler:before", { block, endCursor, finality });
try {
indexer.transform({ block, endCursor, finality });
indexer.callHook("handler:after", { block, endCursor, finality });
} catch (error) {
indexer.callHook("handler:error", { error });
}
});
indexer.callHook("transaction:commit", { finality, endCursor });
break;
}
case "invalidate": {
indexer.callHook("message:invalidate", { message });
break;
}
case "finalize": {
indexer.callHook("message:finalize", { message });
break;
}
case "heartbeat": {
indexer.callHook("message:heartbeat", { message });
break;
}
case "systemMessage": {
indexer.callHook("message:systemMessage", { message });
break;
}
}
}

indexer.callHook("run:after");
}
```
7 changes: 6 additions & 1 deletion docs/v2/sidebar.json
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,11 @@
"name": "Installation",
"type": "page",
"path": "/getting-started/installation"
},
{
"name": "Plugins & Hooks",
"type": "page",
"path": "/getting-started/plugins"
}
]
},
Expand Down Expand Up @@ -75,4 +80,4 @@
]
}
]
}
}

0 comments on commit cf6afa0

Please sign in to comment.