@btravstack/contract / index
index
Type Aliases
Authenticated
type Authenticated<T, R> = T & object;Defined in: auth.ts:31
A contract node whose procedures require an authenticated caller.
Type Parameters
| Type Parameter |
|---|
T |
R extends Requirements |
Type Declaration
| Name | Type | Defined in |
|---|---|---|
[PRINCIPAL] | R | auth.ts:31 |
CursorRefused
type CursorRefused = object;Defined in: pagination.ts:208
A cursor that cannot be honoured under the sort it arrived with, and why: "malformed" never had a sort-shaped head to compare, where "sort-mismatch" did, and named a different one. The two are separately triageable — the first is not actionable, the second tells a caller to re-issue from the first page — so an adapter needs the reason, not just the refusal.
Properties
| Property | Modifier | Type | Defined in |
|---|---|---|---|
cursor | readonly | string | pagination.ts:210 |
reason | readonly | "malformed" | "sort-mismatch" | pagination.ts:211 |
resumable | readonly | false | pagination.ts:209 |
IsMarked
type IsMarked<T> = T extends object ? true : false;Defined in: auth.ts:37
Whether this exact node carries the marker.
Type Parameters
| Type Parameter |
|---|
T |
Keyset
type Keyset = object;Defined in: pagination.ts:150
One keyset window: what to ask the store for, and how to fold what comes back.
The two halves ride one object because they have to AGREE. take is one more row than the page so the extra row answers "is there another page" without a second count query — and page subtracts that same row back out. A store queried for limit and folded as though it had been queried for limit + 1 reports the last page as having a next one, forever.
Properties
| Property | Modifier | Type | Description | Defined in |
|---|---|---|---|---|
backward | readonly | boolean | Walk descending from the cursor. before pages backward; everything else forward. | pagination.ts:154 |
cursor | readonly | string | undefined | The cursor to resume from, whichever side it came from, or undefined at the end. | pagination.ts:156 |
page | readonly | <T, U>(rows, cursorOf, item?) => Page<U> | The rows the store answered — in the order it answered them — folded into a page, with cursorOf minting the opaque cursor for a row. item is there because the row a store seeks by is rarely the thing a port hands back: an adapter pages on a surrogate key and answers domain entities, so the cursor and the item come off the same row by two different routes. It defaults to the row itself. | pagination.ts:166 |
take | readonly | number | How many rows to ask for: the page, plus one to detect the next. | pagination.ts:152 |
OneScheme
type OneScheme<Q> = SeveralKeys<keyof Q> extends false ? Q : never;Defined in: auth.ts:25
A requirement naming two schemes is OpenAPI's AND, and nothing here models it: @btravstack/http-server takes the first entry that satisfies, which is OR — so a two-key requirement copied out of an OpenAPI document would execute as a WEAKER rule than the one it states. Refused at the mark instead.
Type Parameters
| Type Parameter |
|---|
Q |
Page
type Page<T> = object & (
| {
hasPreviousPage: true;
previousCursor: string;
}
| {
hasPreviousPage: false;
previousCursor?: never;
}) & (
| {
hasNextPage: true;
nextCursor: string;
}
| {
hasNextPage: false;
nextCursor?: never;
});Defined in: pagination.ts:15
One page of a listing, in the vocabulary a client and a server share.
A flag and its cursor are one fact, spelled once. hasNextPage: true carries the nextCursor that continues the listing; hasNextPage: false has no nextCursor field at all. So "there is more, and nothing to follow it with" — and its twin, a cursor nobody may use — are unrepresentable rather than merely unexpected, and a reader that checks the flag has the cursor in hand, with no null to widen it.
A cursor is an opaque string: the server's to mint and to read, the client's to hand back verbatim. Nothing above the adapter that issued one may interpret it.
Type Parameters
| Type Parameter |
|---|
T |
Type Declaration
| Name | Type | Defined in |
|---|---|---|
items | readonly T[] | pagination.ts:15 |
PageQuery
type PageQuery = object & (
| {
sort: Sort;
}
| {
sort?: never;
});Defined in: pagination.ts:93
The flat shape a validated page input arrives in, before its two cursors have been narrowed to the one direction PageRequest allows.
A schema states "at most one of these" as a rule over two optional fields; a type states it as a union. This is the former, and pageRequest is the crossing between them.
The sort keeps PageRequest's two states and gains no third: a query either carries one or has no such field. An OPTIONAL sort would cross into the unsorted PageRequest at compile time and mint a sorted cursor at runtime, so it is refused here rather than at the seek.
Type Declaration
| Name | Type | Defined in |
|---|---|---|
after? | string | pagination.ts:95 |
before? | string | pagination.ts:96 |
limit | number | pagination.ts:94 |
PageRequest
type PageRequest<F> = (
| {
after?: string;
before?: never;
limit: number;
}
| {
after?: never;
before?: string;
limit: number;
}) & ([F] extends [never] ? object : object);Defined in: pagination.ts:74
What a caller asks for: a size, at most one cursor, and — for a listing that declares one — the sort it runs in.
after and before are the opaque cursors a previous page handed back, and they are mutually exclusive in the type: a page runs in one direction, and "after X and before Y" is a range query wearing a page's clothes. A union is what makes that unrepresentable rather than merely documented.
F is the listing's own sortable vocabulary. There are two states and no third: a listing that names one always has a sort, a listing that names none can never carry one — which is what lets a cursor's arity be known from the request alone.
Type Parameters
| Type Parameter | Default type |
|---|---|
F extends string | never |
PrincipalKey
type PrincipalKey = typeof PRINCIPAL;Defined in: auth.ts:34
The marker's key, so a consumer's mapped type can Exclude it from keyof.
Requirement
type Requirement = Readonly<Record<string, readonly string[]>>;Defined in: auth.ts:13
One OpenAPI security requirement: a scheme, and the scopes it must grant.
The CARRIER — what a marked node holds and isAuthenticated reads back — so it says nothing about arity. OneScheme below is what refuses a second key.
Requirements
type Requirements = readonly Requirement[];Defined in: auth.ts:28
Requirements are ORed, in order: the first one a caller satisfies wins.
RequirementsOf
type RequirementsOf<T> = T extends object ? R : never;Defined in: auth.ts:40
What this exact node requires, or never when it is unmarked.
Type Parameters
| Type Parameter |
|---|
T |
Sort
type Sort<F> = object;Defined in: pagination.ts:55
One sort key and the direction it runs in.
Both halves are required, so a field without a direction is unrepresentable. One key, not a list: a cursor has to encode every ordering column, and each extra column is another value the seek and the token must agree about.
Type Parameters
| Type Parameter | Default type |
|---|---|
F extends string | string |
Properties
| Property | Modifier | Type | Defined in |
|---|---|---|---|
direction | readonly | "asc" | "desc" | pagination.ts:57 |
field | readonly | F | pagination.ts:56 |
SortedKeyset
type SortedKeyset<F> = object;Defined in: pagination.ts:187
A keyset for a sorted listing, or the refusal of a cursor that was issued under a different one.
cursor carries BOTH values a sorted seek needs — the sort key's and the tiebreak's — because a store queried with only the first seeks on one column and silently skips every row that ties on it.
It is the DECODED pair, with the sort head already stripped and the escaping undone: it is what the seek takes, not what the client sent. So an adapter that refuses it downstream — a value its own storage cannot read — reports the adapter's vocabulary rather than the wire token, and a support engineer handed that string cannot replay the request with it.
Type Parameters
| Type Parameter |
|---|
F extends string |
Properties
| Property | Modifier | Type | Defined in |
|---|---|---|---|
backward | readonly | boolean | pagination.ts:190 |
cursor | readonly | readonly [sortValue: string, key: string] | undefined | pagination.ts:192 |
page | readonly | <T, U>(rows, cursorOf, item?) => Page<U> | pagination.ts:193 |
resumable | readonly | true | pagination.ts:188 |
sort | readonly | Sort<F> | pagination.ts:191 |
take | readonly | number | pagination.ts:189 |
SortIsDecided
type SortIsDecided<Q> = "sort" extends keyof Q ? Q extends object ? unknown : object : unknown;Defined in: pagination.ts:110
The gate pageRequest intersects into its parameter, refusing a sort that is neither required nor absent.
PageQuery's union already states the two states a sort may be in, but a union's arms are compared one at a time, and only that per-arm comparison consults the exactOptionalPropertyTypes relation — assignability to the union as a whole does not. So sort?: Sort, a key present but optional, still types as assignable to a PageQuery the union alone was meant to refuse. This gate closes that spelling directly.
Type Parameters
| Type Parameter |
|---|
Q |
Functions
authenticated()
function authenticated<R>(...requirements): <T>(node) => Authenticated<T, R>;Defined in: auth.ts:71
Marks a contract node as requiring an authenticated caller, with OpenAPI's own requirement shape — a scheme and the scopes it must grant, ORed in the order given.
export const contract = {
orders: authenticated({ user: [] })({ place, find }),
exports: authenticated({ user: ["orders:export"] }, { service: [] })(csvProcedure),
};Applied to a record it is the default for every procedure beneath it; applied to a procedure it replaces that default for itself. Nearest mark wins. Returns the node unchanged and applies after a builder chain, never inside one. See packages/contract/AGENTS.md.
Type Parameters
| Type Parameter |
|---|
R extends Requirements & { readonly [I in string | number | symbol]: OneScheme<R[I]> } |
Parameters
| Parameter | Type |
|---|---|
...requirements | R |
Returns
<T>(node) => Authenticated<T, R>
isAuthenticated()
function isAuthenticated(node): Requirements | undefined;Defined in: auth.ts:84
What this exact node requires, or undefined when nobody marked it. Ancestry is the caller's to carry — @btravstack/http-server's routerOf walks the tree and passes the nearest mark down.
Parameters
| Parameter | Type |
|---|---|
node | object |
Returns
Requirements | undefined
keyset()
Call Signature
function keyset(request): Keyset;Defined in: pagination.ts:318
The keyset pagination an adapter would otherwise hand-roll: the over-fetch, the direction, the trim, and both cursors.
It does not run the query — the store's own seek call is the one thing this tier cannot express, and every store spells it differently. What it owns is the arithmetic around that call, which is identical everywhere and is where the off-by-ones live.
Backward is not forward reversed. before walks the index descending, so the rows arrive newest-first and page hands them back ascending — a previous page reads the way the next one does. It also flips which side the extra row proves: paging forward, one more row means there is a page AFTER; paging backward it means there is one BEFORE.
A cursor is minted only from a row that is actually on the page, so an empty page carries neither — which is what makes both flags on Page honest.
A sorted request answers a union the caller must branch on. The cursor a sorted listing mints carries the sort it was issued under, so one replayed under a different field — or the same field in the other direction — is REFUSED rather than served from the wrong side.
Parameters
| Parameter | Type |
|---|---|
request | PageRequest |
Returns
Example
const keys = keyset(request);
const rows = await store.seek(keys.cursor, keys.backward).take(keys.take);
return keys.page(rows, (row) => String(row.id));Call Signature
function keyset<F>(request):
| SortedKeyset<F>
| CursorRefused;Defined in: pagination.ts:319
The keyset pagination an adapter would otherwise hand-roll: the over-fetch, the direction, the trim, and both cursors.
It does not run the query — the store's own seek call is the one thing this tier cannot express, and every store spells it differently. What it owns is the arithmetic around that call, which is identical everywhere and is where the off-by-ones live.
Backward is not forward reversed. before walks the index descending, so the rows arrive newest-first and page hands them back ascending — a previous page reads the way the next one does. It also flips which side the extra row proves: paging forward, one more row means there is a page AFTER; paging backward it means there is one BEFORE.
A cursor is minted only from a row that is actually on the page, so an empty page carries neither — which is what makes both flags on Page honest.
A sorted request answers a union the caller must branch on. The cursor a sorted listing mints carries the sort it was issued under, so one replayed under a different field — or the same field in the other direction — is REFUSED rather than served from the wrong side.
Type Parameters
| Type Parameter |
|---|
F extends string |
Parameters
| Parameter | Type |
|---|---|
request | PageRequest<F> |
Returns
| SortedKeyset<F> | CursorRefused
Example
const keys = keyset(request);
const rows = await store.seek(keys.cursor, keys.backward).take(keys.take);
return keys.page(rows, (row) => String(row.id));page()
function page<T>(items, cursors): Page<T>;Defined in: pagination.ts:34
A page from its items and the cursor on each side, null where there is nothing to follow.
The flags are DERIVED rather than given: a cursor is what a caller needs to ask for the page on that side, so a side with no cursor is a side it cannot reach — which is what the flags say. An adapter whose pagination library reports a page the other way round, a hasPreviousPage: true with no cursor to go back with, therefore reports the reachable answer instead.
Type Parameters
| Type Parameter |
|---|
T |
Parameters
| Parameter | Type |
|---|---|
items | readonly T[] |
cursors | { next: string | null; previous: string | null; } |
cursors.next | string | null |
cursors.previous | string | null |
Returns
Page<T>
pageRequest()
function pageRequest<Q>(query): (Q extends object ? PageRequest<F> : PageRequest<never>) & Omit<Q, "after" | "before">;Defined in: pagination.ts:129
A validated page input, narrowed into the one-direction PageRequest a port takes, carrying any filters alongside it untouched.
before wins when both are somehow present. That precedence is unreachable through pageRequestOf, whose schema refuses the pair — it exists so this function is total rather than partial, not as a policy a caller should rely on.
A sort stays a sort: it is a field of the query like any filter, so it rides through untouched and the result is the PageRequest of that listing's own vocabulary rather than of none.
Type Parameters
| Type Parameter |
|---|
Q extends PageQuery |
Parameters
| Parameter | Type |
|---|---|
query | Q & SortIsDecided<Q> |
Returns
(Q extends object ? PageRequest<F> : PageRequest<never>) & Omit<Q, "after" | "before">