---
title: "@unikvs/tap"
description: "Middleware that passes data through while invoking a callback with the context."
---

## Overview [#overview]

`@unikvs/tap` is middleware that passes data through unchanged while invoking a callback with the context for each data hook.Middleware

Like [`@unikvs/passthrough`](/unikvs/en/packages/passthrough), it never changes stored content. Use the callback for logging, validation, runtime-variable updates, and similar side effects.

- `name` is `Tap`.
- `isOpen` is always `true`. `open()` is unnecessary.
- The callback runs before `encode`, `decode`, `encodeStream`, and `decodeStream`.
- The callback's return value is ignored, and data always passes through as input.
- When the callback returns a `Promise`, the middleware waits for it before continuing.
- Exceptions or rejections thrown by the callback propagate as operation failures.

Install with:

```package-install
npm install @unikvs/tap
```

## Usage [#usage]

`Tap` implements IMiddleware. Pass it to UniKvs with use().

```ts
import { Tap } from "@unikvs/tap";
```

Constructor arguments:

| Prop | Type | Default | Description |
| - | - | - | - |
| `callback` | `[#tap-callback] TapCallback` | - | Callback invoked before each data hook. |

The following records the action and key for each read and write:

```ts
import { Memory } from "@unikvs/memory";
import { Tap } from "@unikvs/tap";
import * as unikvs from "unikvs";

const events: string[] = [];

const kvs = new unikvs.Builder<{ k1: unikvs.PlainValue<string> }>()
  .use(
    new Tap((c) => {
      events.push(`${c.action}:${c.key}`);
    }),
  )
  .persist(new Memory())
  .build();

await kvs.open();
await kvs.set("k1", "hello");
console.log(await kvs.get("k1")); // "hello"
console.log(events); // e.g. ["set:k1", "get:k1"]
await kvs.close();
```

The callback receives a [`TapContext`](#tap-context), so it can read `source`, `key`, and `action`, and also get and set runtime variables with `get`, `set`, and `setReadonly`.

## API reference [#api]

### `Tap` [#tap]

Middleware that invokes a callback before each data hook without transforming data.

- `name` is `"Tap"`.
- `isOpen` is always `true`.
- `callback` is the callback passed to the constructor.

### `TapCallback` [#tap-callback]

Callback passed to the `Tap` constructor.

```ts
type TapCallback = (c: TapContext) => void | Promise<void>;
```

When the callback returns a `Promise`, the middleware waits for it before sending data downstream. Exceptions or rejections thrown by the callback propagate to the caller as operation failures.

### `TapContext` [#tap-context]

Context received by the callback. It is one of:

- `IMiddleware.EncodeContext`
- `IMiddleware.DecodeContext`
- `IMiddleware.EncodeStreamContext`
- `IMiddleware.DecodeStreamContext`

## Data types [#data-types]

| Method         | Input    | Output   |
| -------------- | -------- | -------- |
| `encode`       | `TData`  | `TData`  |
| `decode`       | `TData`  | `TData`  |
| `encodeStream` | `TChunk` | `TChunk` |
| `decodeStream` | `TChunk` | `TChunk` |

- `encode` and `decode` output the input data unchanged.
- `encodeStream` and `decodeStream` output the input stream unchanged. Chunk order and references are preserved.
- For streams, the callback runs once before the stream is constructed. It does not run per chunk.
- Even if the callback changes `destination`, the input overwrites it and data passes through unchanged.

## Errors [#errors]

This package has no package-specific errors.

Exceptions or rejections thrown by the callback propagate unchanged as operation failures.
