Initial Commit

This commit is contained in:
2026-05-27 23:47:53 +01:00
parent fbc70f96e6
commit 9b28db724f
18103 changed files with 1600554 additions and 0 deletions

View File

@@ -0,0 +1,89 @@
# Pagination Adapters
<!-- begin:auto-docs -->
_(no submodules)_
<!-- end:auto-docs -->
## Purpose
The adapters module defines the `PaginationAdapter<T>` interface that all three pagination strategies — cursor, offset, and keyset — consume for data access. It also ships `MemoryPaginationAdapter<T>`, a pure in-memory reference implementation that stores items in a plain array and supports equality-based filtering and single-field sorting. The adapter abstraction decouples pagination logic from the underlying data layer, so swapping from an in-memory store to a SQL database or HTTP API requires implementing just two methods.
## Features
- `PaginationAdapter<T>` interface — two methods: `fetchPage` (paginated fetch) and `count` (total matching items).
- `MemoryPaginationAdapter<T>` — in-memory implementation with `addItems`, `setItems`, `getAll`, `fetchPage`, and `count`.
- `createMemoryAdapter` — factory function that creates a pre-populated `MemoryPaginationAdapter`.
- `createSortConfig` — convenience builder for `SortConfig` values.
## Usage: Basic Example
```typescript
import { createMemoryAdapter, createSortConfig } from "@suaveplan/pagination/adapters";
const adapter = createMemoryAdapter([
{ id: 1, name: "Alice", age: 30 },
{ id: 2, name: "Bob", age: 25 },
{ id: 3, name: "Carol", age: 35 },
]);
const sort = createSortConfig("name", "asc");
const page = await adapter.fetchPage(0, 2, sort);
// page.items → [{ id: 1, name: "Alice" }, { id: 2, name: "Bob" }]
// page.total → 3
```
## Usage: Advanced Example
```typescript
import { createMemoryAdapter } from "@suaveplan/pagination/adapters";
import { fetchAfter, encodeCursor } from "@suaveplan/pagination/cursor";
const adapter = createMemoryAdapter(
Array.from({ length: 50 }, (_, i) => ({ id: i + 1, score: i * 2 }))
);
// Count with filter
const activeCount = await adapter.count({ score: 10 });
// Use with cursor pagination
const page = await fetchAfter(adapter, undefined, { limit: 5, sortBy: "id" }, (i) => i.id);
// page.items → first 5 items sorted by id
// Replace dataset at runtime
adapter.setItems([{ id: 99, score: 0 }]);
const count = await adapter.count();
// → 1
```
## API Reference
### `interface PaginationAdapter<T>`
| Method | Signature |
|-------------|------------------------------------------------------------------------------|
| `fetchPage` | `(offset, limit, sort?, filter?) => Promise<PageResult<T>>` |
| `count` | `(filter?) => Promise<number>` |
`fetchPage` must return items in the requested sort order and the total matching count in `PageResult.total`.
### `class MemoryPaginationAdapter<T>`
| Method | Description |
|-------------|-----------------------------------------------------|
| `constructor(items?)` | Initialises with a shallow copy of `items`. |
| `addItems(items)` | Appends items to the store. |
| `setItems(items)` | Replaces the entire store. |
| `getAll()` | Returns a shallow copy of all items. |
| `fetchPage(...)` | Applies filter, sorts, slices, returns `PageResult`. |
| `count(filter?)` | Returns the count of items matching the filter. |
### `createMemoryAdapter<T>(items?): MemoryPaginationAdapter<T>`
Factory that creates a `MemoryPaginationAdapter` pre-populated with `items` (default: empty array).
### `createSortConfig(field, direction?): SortConfig`
Builds a `{ field, direction }` sort config. Direction defaults to `"asc"`.
## Implementation Notes
Filtering uses strict equality (`===`) on top-level fields only — nested field access is not supported in the reference implementation. Sorting uses `localeCompare` for strings and arithmetic comparison for numbers; null values sort last. The `items` array is always stored as a shallow copy to prevent external mutation from affecting the adapter state. Class members are `protected` (not `private`) to enable subclassing in tests without re-implementing the full interface.