---
title: "@unikvs/ttl"
description: "相対秒数や絶対時刻で TTL を付与し、期限切れを欠落として扱うミドルウェアです。"
---

## 概要 [#overview]

`Ttl` は書き込んだ値に相対秒数や絶対時刻の有効期限を付与し、期限切れの読み取りを欠落として扱います。Middleware

短命なキャッシュやセッション的なデータの保持に使います。プレーン値とストリームで失効の管理方式が異なります。

- `name` は `Ttl` です。
- `isOpen` は常に `true` です。
- 期限の指定は操作ごとの変数 `@unikvs/ttl:ttl`・`@unikvs/ttl:expiresAt` (非名前空間名 `ttl`・`expiresAt` も後方互換で受付) と、既定値 `defaultTtl` で行います。
- 期限切れの `get`・`has`・`stream`・`copy`・`move` は `KeyNotFoundError` 相当として扱います。
- `copy` と `move` は、このミドルウェアが適用された分岐で TTL の対象となるすべての宛先が `committed` になったときに限り、コピー元・移動元の有効期限をコピー先・移動先へ引き継ぎます。

インストールは次のコマンドで行います。

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

## 使い方 [#usage]

Ttl は IMiddleware を実装しています。UniKvs には use() で渡します。

```ts
import { Ttl } from "@unikvs/ttl";
```

コンストラクターの引数は次のとおりです。

| Prop | Type | Default | Description |
| - | - | - | - |
| `defaultTtl?` | `[#ttl-options-default-ttl] TtlInput` | - | 書き込みごとの指定がないときに使う既定の TTL です。相対秒数、絶対時刻、オブジェクト形式のいずれも指定できます。 |
| `now?` | `[#ttl-options-now] () => number` | `Date.now` | 現在時刻 (epoch ミリ秒) を返す関数です。テストで時刻を固定するときに使います。 |

以下は、既定の TTL を 60 秒に設定し、書き込みごとに上書きする例です。

```ts
import { Memory } from "@unikvs/memory";
import { Ttl } from "@unikvs/ttl";
import * as unikvs from "unikvs";

const kvs = new unikvs.Builder<{ report: unikvs.PlainValue<string> }>()
  .use(new Ttl({ defaultTtl: 60 }))
  .persist(new Memory())
  .build();

await kvs.open();
await kvs.set("report", "hello");
await kvs.set("short", "bye", { vars: { "@unikvs/ttl:ttl": 10 } });
console.log(await kvs.get("report")); // "hello"
await kvs.close();
```

### `Ttl` [#ttl]

有効期限エンベロープと操作内記録で失効を管理するミドルウェアです。読み取り前に失効を判定し、存在確認では存在しないものとして扱います。

### `TtlOptions` [#ttl-options]

`Ttl` のコンストラクターが受け取るオプションです。既定の TTL と時刻取得を指定します。

### `TtlInput` [#ttl-input]

書き込みごとに指定できる TTL 値です。`number` は相対秒数 (小数も可、0 以下は即時失効)、`Date` は絶対失効時刻、オブジェクト形式では `ttl` に相対秒数、`expiresAt` に epoch ミリ秒または `Date` を指定します。

## データ型 [#data-types]

| メソッド       | 入力                 | 出力                       |
| -------------- | -------------------- | -------------------------- |
| `encode`       | `unknown`            | エンベロープか透過         |
| `decode`       | エンベロープか任意値 | `unknown`                  |
| `encodeStream` | `unknown` (チャンク) | `unknown` (チャンク、透過) |
| `decodeStream` | `unknown` (チャンク) | `unknown` (チャンク、透過) |

- `encode` は TTL があるときに `{ "@unikvs/ttl": 1, expiresAt: number, data: unknown }` の形に包みます。TTL がない上書きではデータを透過させます。
- `decode` は期限内であれば包みを剥がして元の値を返し、期限切れでは `KeyNotFoundError` を投げます。再起動後などで記録が欠けているときはエンベロープから記録を復元します。
- ストリームはチャンクの型制約に合わせるため帯域内に期限を埋め込まず、記録だけで管理します。`encodeStream` と `decodeStream` はデータを透過します。
- TTL の解決は `ttl` 変数を優先し、次に `expiresAt` 変数、最後に `defaultTtl` を使います。名前空間付き変数が非名前空間名より優先されます。

### 記録の確定タイミング [#transaction]

このミドルウェアはストレージの成否を [`afterUse`](/unikvs/packages/core/middleware#hooks-after-use) で受け取り、記録を確定します。`use`・`encode`・`encodeStream` の時点では記録を変更せず、操作内の変数に確定待ちの値を保存します。そのため、書き込みに失敗したときに、存在しない値へ期限だけが付くことはありません。`afterUse` に渡される宛先は、このミドルウェアが適用された分岐のものだけです。

- `set` はすべての宛先が確定したときに、保存した期限を記録へ反映します。TTL なしの上書きでは記録を消します。失敗したときは以前の記録を残します。
- `copy` は、このミドルウェアが適用された分岐で TTL の対象となるすべての宛先が `committed` になったときに、コピー元の期限をコピー先へ引き継ぎます。コピー元に記録がなければコピー先の記録を消します。一部でも `notFound` や `failed` がある場合は、コピー先の記録を変更しません。
- `move` は、このミドルウェアが適用された分岐で TTL の対象となるすべての宛先が `committed` になったときに、移動先の記録を更新して移動元の記録を消します。一部でも `notFound` や `failed` がある場合は、移動元の記録を残し、移動先の記録は変更しません。
- ストレージの確定後に操作が中断された場合でも、このミドルウェアが適用された分岐で TTL の対象となるすべての宛先が `committed` なら、その結果を記録へ反映します。未確定の宛先があれば、失敗時と同じく記録を変更しません。
- コピー元・移動元の期限は `use` の時点で固定せず、転送完了後の `afterUse` (キーロック保持中) に記録から読み直します。`use` の後に並行する `set` が期限を更新した場合も、その現在の期限を引き継ぎます。
- `delete` と `clear` は成功したときだけ記録を消します。部分成功では残っている値の期限を失わないよう、記録を残します。
- 期限切れのコピー元・移動元は `KeyNotFoundError` になりますが、記録は残します。ストリームは帯域内に期限を持たないため、失敗したコピー・移動の後でも失効を判定できます。成功した `set` は記録を新しい期限で置き換え (TTL なしなら削除し)、成功した `delete`・`clear` は記録を消します。
- `decodeStream` が期限切れを検出しても記録は残します。同じキーへの `decodeStream` は引き続き `KeyNotFoundError` になります。
- `get`・`stream` の `repair` による書き戻しは読み取りの一部として扱われ、`afterUse` は呼ばれません。そのため、書き戻し先のストレージには期限エンベロープを持たないストリームの記録が読み取り側のインスタンスに確定されません。

:::note[部分成功時の制約]
複数のストレージへ書き込む操作が部分的に成功した場合、TTL の記録は安全側に倒して変更しません。たとえば `set` や `move` が一部のストレージだけで成功すると、書き込み先へ保存されたストリームには期限が付かないことがあります。`copy` や `move` でコピー元・移動元が一部のストレージにしか存在しない場合も、触れていないストレージの古い期限を復活させないため、コピー先・移動先の記録は変更しません。部分的な状態を 1 つのキーの記録で正確に表せないためです。
:::

## エラー [#errors]

このパッケージが投げるエラーは次のとおりです。

### TtlInvalidValueError [#error-ttl-invalid-value]

| Prop | Type | Default | Description |
| - | - | - | - |
| `actual` | `[#ttl-invalid-value-actual] unknown` | - | 指定された TTL の値です。 |

#### 意味 [#error-ttl-invalid-value-meaning]

TTL 値が相対秒数・絶対時刻・オブジェクト形式のいずれとしても解釈できないときに、このエラーが投げられます。コンストラクターの `defaultTtl` と書き込みごとの変数の両方が検証対象です。

#### 対処方法 [#error-ttl-invalid-value-howto]

有限の数値 (相対秒数)、`Date`、または `{ ttl }` や `{ expiresAt }` の形のオブジェクトを指定してください。文字列や非有限値は受け付けません。

#### 例 [#error-ttl-invalid-value-example]

以下は、`TtlInvalidValueError` を捕捉する例です。

```ts
import { TtlInvalidValueError } from "@unikvs/ttl";

try {
  await kvs.set("report", "hello", { vars: { ttl: "60" } });
} catch (ex) {
  if (ex instanceof TtlInvalidValueError) {
    console.log(ex.meta); // { actual: "60" }
  } else {
    throw ex;
  }
}
```

期限切れの読み取りは `@unikvs/core` の `KeyNotFoundError` として失敗します。詳細は[エラー](/unikvs/packages/core/errors#error-key-not-found)を参照してください。
