# @coda/common

Shared utilities, storage abstractions, and graph structures for the ows-coda monorepo. Everything in this package is pure TypeScript with no runtime dependencies — it can be imported from any workspace package without pulling in extra weight.

> **Note:** Data structures (lists, heaps, trees) are in `@coda/collections`. Async patterns (retry, circuit breaker, throttlers) are in `@coda/async`. Search algorithms are in `@coda/search`.

## Installation

This is a private workspace package — consumed via the pnpm workspace link:

```jsonc
// package.json
{ "dependencies": { "@coda/common": "workspace:*" } }
```

## Usage

```ts
import {
  // Cache
  LRUCache,
  NullCache,
  type SyncCache,
  type AsyncCache,

  // Graph
  Graph,
  bfsPath,
  bfsNeighborhood,
  dijkstra,
  kahnSort,
  type GraphNode,
  type GraphEdge,

  // Storage interfaces
  type BlobStore,
  type KeyValueStore,
  type DurableQueue,

  // Storage implementations
  S3BlobStore,
  MemoryBlobStore,
  RedisKeyValueStore,
  MemoryKeyValueStore,
  NullKeyValueStore,
  RedisDurableQueue,

  // Redis
  normalizeRedisUrl,
  SERVER_PREFIXES,

  // Utilities
  clamp,
  chunk,
  toErrorMessage,

  // Unit constants
  Bytes,
  Millis,
  Seconds,
  Nanos,

  // Runtime limits (namespaced)
  runtime,
} from "@coda/common";
```

## API Overview

### Cache

In-process caching with interface-driven design.

| Export           | Description                                                      |
| ---------------- | ---------------------------------------------------------------- |
| `LRUCache`       | Bounded LRU cache backed by a `Map`                              |
| `NullCache`      | No-op `SyncCache` — always misses (null object pattern)          |
| `NullAsyncCache` | No-op `AsyncCache` — always misses                               |
| `SyncCache`      | Interface: `get`, `set`, `has`, `delete`, `clear`, `size`        |
| `AsyncCache`     | Async variant of `SyncCache`                                     |
| `CounterStore`   | Interface for atomic increment/decrement (Redis-backed counters) |

### Graph

Directed graph with BFS/Dijkstra traversal.

| Export                         | Description                                                   |
| ------------------------------ | ------------------------------------------------------------- |
| `Graph<T>`                     | Directed graph with typed nodes and edges                     |
| `bfsPath`                      | BFS shortest path — returns ID array or `null`                |
| `bfsBidirectionalPath`         | Bidirectional BFS for faster shortest path                    |
| `bfsNeighborhood`              | All node IDs reachable within `depth` hops, capped at `limit` |
| `dijkstra`                     | Weighted shortest path                                        |
| `kahnSort` / `topologicalSort` | Topological sort (DAG ordering)                               |
| `augmentWithNeighbors`         | Enrich a result set with graph-neighbor context               |
| `DSU`                          | Disjoint Set Union (union-find)                               |
| `GraphNode<T>`                 | Node with `id`, `kind`, `data`                                |
| `GraphEdge`                    | Edge with `from`, `to`, `relation`, optional `weight`         |
| `ReadonlyGraph<T>`             | Read-only graph interface                                     |

### Storage

Interface-driven storage abstractions with multiple backends.

#### Interfaces

| Interface       | Description                                        |
| --------------- | -------------------------------------------------- |
| `BlobStore`     | Put/get/delete binary blobs (S3, memory, null)     |
| `KeyValueStore` | Get/set/delete string-keyed values (Redis, memory) |
| `DurableQueue`  | Push/pop message queue (Redis, memory, null)       |
| `RedisLike`     | Minimal Redis client interface for DI              |

#### Implementations

| Class                 | Backend   | Notes                   |
| --------------------- | --------- | ----------------------- |
| `S3BlobStore`         | AWS S3    | Production blob storage |
| `MemoryBlobStore`     | In-memory | Tests and local dev     |
| `NullBlobStore`       | No-op     | Null object pattern     |
| `RedisKeyValueStore`  | Redis     | Production KV           |
| `MemoryKeyValueStore` | In-memory | Tests and local dev     |
| `NullKeyValueStore`   | No-op     | Null object pattern     |
| `RedisDurableQueue`   | Redis     | Production queue        |
| `MemoryDurableQueue`  | In-memory | Tests                   |
| `NullDurableQueue`    | No-op     | Null object pattern     |
| `RedisCounterStore`   | Redis     | Atomic counters         |

### Redis

Connection helpers and namespace prefixes.

| Export              | Description                                          |
| ------------------- | ---------------------------------------------------- |
| `normalizeRedisUrl` | Normalize a Redis URL for ioredis                    |
| `asRedisLike`       | Adapt an ioredis client to the `RedisLike` interface |
| `SERVER_PREFIXES`   | Key namespace prefixes for the server app            |
| `PLATFORM_PREFIXES` | Key namespace prefixes for the platform service      |
| `RUNNER_PREFIXES`   | Key namespace prefixes for the runner service        |

> `createRedisClient` is **not** re-exported from the barrel — ioredis is an optional peer dependency. Import directly:
>
> ```ts
> import { createRedisClient } from "@coda/common/redis/client";
> ```

### Crypto

Crypto utilities depend on `node:crypto` and are **not** re-exported from the main barrel (browser-incompatible). Import directly:

```ts
import {
  hmacSha256,
  encryptAes256Gcm,
  extractDomain,
} from "@coda/common/crypto";
```

### Utilities

#### Math

| Function     | Description                                    |
| ------------ | ---------------------------------------------- |
| `clamp`      | Limit a number to a `[min, max]` range         |
| `wrapLeft`   | Wrap only when below `min`                     |
| `addIfBelow` | Conditionally add to a value if below a target |
| `isInRange`  | Check if value is within `[min, max)`          |
| `log`        | Logarithm with arbitrary base                  |
| `toInteger`  | Convert value to integer with fallback default |

#### Array & Iterable

| Function       | Description                                       |
| -------------- | ------------------------------------------------- |
| `splice`       | Like `Array.splice` but safe for large insertions |
| `isArray`      | Check if value is `Array` or `TypedArray`         |
| `isTypedArray` | Check if value is a `TypedArray`                  |
| `chunk`        | Split an iterable into fixed-size chunks          |
| `isIterable`   | Has `Symbol.iterator`                             |

#### Errors

| Function         | Description                                    |
| ---------------- | ---------------------------------------------- |
| `toErrorMessage` | Extract a message string from an unknown error |

#### Bit manipulation (`u32`)

Low-level 32-bit unsigned integer utilities: `u32`, `bitsSet`, `isPow2`, `lsb`/`lsp`, `msb`/`msp`, `lzb`/`lzp`, `lsps`, `msps`, `invert`, `reverse`.

### Unit Constants

Typed numeric constants for readable, typo-proof arithmetic:

```ts
const timeout = 30 * Millis.Sec; // 30_000
const limit = 64 * Bytes.MB; // 67_108_864
const delta = 500 * Nanos.MS; // 500_000_000
```

| Object    | Keys                                      |
| --------- | ----------------------------------------- |
| `Bytes`   | `B`, `KB`, `MB`, `GB`, `TB`, `PB`         |
| `Millis`  | `MS`, `Sec`, `Min`, `Hour`, `Day`, `Week` |
| `Seconds` | `Sec`, `Min`, `Hour`, `Day`, `Week`       |
| `Nanos`   | `NS`, `MS`, `Sec`                         |

### Runtime (`runtime`)

Runtime-detected platform limits, imported as a namespace:

| Export                      | Description                                                      |
| --------------------------- | ---------------------------------------------------------------- |
| `runtime.maxArrayLength()`  | Maximum array length per ECMA-262 (lazily computed, then cached) |
| `runtime.maxLinkedLength()` | Maximum safe linked structure length (`Number.MAX_SAFE_INTEGER`) |

### Resource Pooling

This package intentionally does **not** include a generic resource pool. For bounded acquire/release pooling of reusable resources (DB connections, workers, etc.), use [`generic-pool`](https://www.npmjs.com/package/generic-pool) — the de facto standard in the Node ecosystem. Add it as a direct dependency in the package that needs it rather than routing through `common/`.

## Building

```sh
pnpm build        # one-shot build (ESM + declarations)
pnpm dev          # watch mode
```

Output lands in `dist/` as ESM:

| File                        | Format           |
| --------------------------- | ---------------- |
| `index.mjs` / `index.d.mts` | ESM + type decls |
