/** A DynamoDB `LastEvaluatedKey` / `ExclusiveStartKey`. */ export type DynamoKey = Record; /** * The query a cursor must belong to. Every tenant-scoped entity shares the * partition key `TENANT#{id}` and is separated only by its sort-key prefix, so * the prefix is part of a cursor's identity, not a detail. */ export interface CursorQuery { /** Partition key value the query filters on. */ pk: string; /** GSI being queried, if any. Its keys are named `{indexName}PK`/`SK`. */ indexName?: string; /** `begins_with` prefix applied to the sort key, if any. */ skPrefix?: string; } /** * Encode a `LastEvaluatedKey` into an opaque cursor token suitable for * returning as `next` and accepting back as `from`. */ export declare function encodeDynamoCursor(key: Record): string; /** * Decode an opaque cursor token into an `ExclusiveStartKey`, verifying it was * minted by the same query that is now presenting it. * * Returns `null` for anything unusable so a client-supplied `from` degrades to * "start from the beginning" rather than throwing — matching `decodeCursor()` * in adapter-interfaces. * * The query check is not decoration. DynamoDB rejects a key that disagrees with * the query's key conditions ("The provided starting key does not match the * range key predicate", "...is invalid"), so without this an attacker-supplied * or merely stale `from` turns into an unhandled ValidationException — a 500 * driven by a query parameter. Note that this validation is a robustness * measure, not a tenant boundary: DynamoDB already refuses a cursor whose * partition key differs from the one being queried, so a foreign cursor could * never read another tenant's rows. It fails cleanly here instead of loudly * there. */ export declare function decodeDynamoCursor(token: string, query: CursorQuery): DynamoKey | null;