npm package: @ralalabs/rid
Description: Fast deterministic reversible ID encoding for numbers and strings using AES-128-CTR with base62 output
Author: Rala Labs
Latest version: 1.0.1

# @ralalabs/rid

Fast, deterministic, reversible ID encoding for Node.js and edge runtimes.

`@ralalabs/rid` turns internal IDs into short opaque tokens and decodes them back with the same secret key. It supports both numeric IDs and string IDs works in Node.js and Cloudflare Workers, and keeps the API intentionally small.

## Features

- fast encode and decode for hot-path usage
- supports both `number` and `string` IDs
- works across runtimes: Node.js, browsers with WebCrypto, Cloudflare Workers, and other edge runtimes
- deterministic output for the same input and key
- short URL-safe base62 tokens
- optional prefixes such as `media-`, `season-`, or `episode-`
- same high-level API for encode and decode
- small surface area with stricter typed helpers available when needed

## Install

```bash
npm install @ralalabs/rid
# or
pnpm add @ralalabs/rid
```

## Quick start

```ts
import { createCodec, generateKey } from '@ralalabs/rid';

const key = process.env.RID_KEY;

const codec = createCodec({
  key,
  prefix: 'media',
  // default separator is '-'
});

const rid = await codec.encode('kv:ro');
const value = await codec.decode(rid);

console.log(rid);
console.log(value);
```

`encode()` and `decode()` are the default high-level methods. They accept both `number` and `string` IDs and return the original value type on decode.

## Prefixes

Prefixes let you namespace tokens by domain or entity type.

```ts
const mediaCodec = createCodec({ key: RID_KEY, prefix: 'media' });
const userCodec = createCodec({ key: RID_KEY, prefix: 'user' });

await mediaCodec.encodeNumber(1); // media-...
await userCodec.encodeNumber(1); // user-...
```

Prefix validation is enforced on decode.

Custom separators are also supported. Use non-alphanumeric separators only, such as `-`, `_`, `:`, `::`, or `~`. This keeps prefix parsing unambiguous because the token body is base62.

## API

### `createCodec(options)`

Creates a codec instance.

#### Options

| Option      | Type                   | Required | Description                                                                    |
| ----------- | ---------------------- | -------- | ------------------------------------------------------------------------------ |
| `key`       | `string \| Uint8Array` | yes      | 128-bit key as a 32-char hex string or 16-byte array                           |
| `prefix`    | `string`               | no       | optional token namespace, for example `med`                                    |
| `separator` | `string`               | no       | separator used after prefix, default `-`. Must not contain letters or numbers. |

### Codec methods

| Method                | Returns                            | Notes                                                       |
| --------------------- | ---------------------------------- | ----------------------------------------------------------- |
| `encode(value)`       | `Promise<string>`                  | accepts `number` or `string`                                |
| `decode(token)`       | `Promise<number \| string>`        | returns the original ID type                                |
| `encodeNumber(value)` | `Promise<string>`                  | strict numeric helper                                       |
| `decodeNumber(token)` | `Promise<number>`                  | strict numeric helper; throws if token contains a string ID |
| `encodeString(value)` | `Promise<string>`                  | strict string helper                                        |
| `decodeString(token)` | `Promise<string>`                  | strict string helper; throws if token contains a numeric ID |
| `encodeBatch(values)` | `Promise<string[]>`                | batch encode                                                |
| `decodeBatch(tokens)` | `Promise<Array<number \| string>>` | batch decode                                                |

### `generateKey()`

Returns a random 128-bit key as a lowercase 32-character hex string.

```ts
import { generateKey } from '@ralalabs/rid';

const key = await generateKey();
```

### `RidError`

All package validation and decode failures throw `RidError`.

## ID support

### Numbers

- non-negative integers only
- maximum value: `Number.MAX_SAFE_INTEGER`

### Strings

- UTF-8 strings are supported
- maximum encoded string size: `65535` bytes before encryption
- suitable for IDs such as `media:x:y`, `user:abc`, `show:season:1`

## Security notes

This package is meant for opaque reversible IDs. It helps hide internal identifiers from public URLs and APIs, but it is not an authorization mechanism.

Important properties:

- deterministic by design
- same key + same ID always produces the same token
- no MAC or authenticity guarantee
- malformed tokens are rejected through structural validation after decode
- tampered tokens usually fail validation, but this package should not be treated as a cryptographic integrity layer

Use it when you want:

- opaque public route IDs
- reversible IDs across services
- one secret key shared between Node.js and edge runtimes

Do not use it as your only control for:

- access control
- anti-tampering guarantees
- high-assurance cryptographic protocols

## Runtime support

| Runtime            | Backend       |
| ------------------ | ------------- |
| Node.js 18+        | `node:crypto` |
| Cloudflare Workers | `WebCrypto`   |
| Deno               | `WebCrypto`   |
| Modern browsers    | `WebCrypto`   |

## Performance notes

The package is built for hot-path use:

- small API surface
- cached crypto adapter selection
- cached imported WebCrypto keys
- no JSON serialization in the encoded payload
- deterministic output means token length reflects payload size
- compact binary payload before base62 conversion

For the best type safety and clarity in application code, prefer `encodeNumber` / `decodeNumber` and `encodeString` / `decodeString` when the ID shape is known.

## License

MIT