import type { Kysely } from 'kysely'; import type { Database, FieldRow } from '../db/schema.js'; import { type ContentItem } from './items.js'; /** * Running the `query` fields on a page. * * A query field stores a rule and never its answer, so the answer has to be produced on every read. * That is the whole reason the field exists: "the six soonest Arts events" has to change when * somebody publishes a seventh, without anyone editing the page the listing sits on. * * **Cost is O(query fields), not O(results).** One list query per field, plus one content-type load * per distinct target type, memoised for the request. Every result's own media, relations and terms * are unioned into the payload's existing `collected` sets and ride the loaders that were going to * run anyway, so a listing of twenty events costs no more round trips than a listing of two. */ /** One resolved query, addressed by where the field sits rather than by the field alone. */ export interface DeliveryQueryResult { /** Matching item ids, in the query's own order. Look each up in `references`. */ ids: string[]; /** How many matched in total, which is usually more than `ids.length`. */ total: number; } /** * The fields a listed item carries, out of everything its type defines. * * Exported because a query result is no longer the only listed item: `deliverItems` answers * `/delivery/items?include=data` with the same shape, so a consumer's card component works against * either without a branch. That promise is only true while both ask *this* function — two copies of * the filter is how one of them keeps carrying `block` after the other stops. */ export declare function resultFields(fields: FieldRow[]): FieldRow[]; /** * One item's values, narrowed to the fields a result carries. * * `undefined` values are dropped rather than sent as null: absent and "explicitly nothing" are the * same thing for a field nobody filled in, and JSON has no `undefined` to distinguish them with. */ export declare function resultData(fields: FieldRow[], data: Record): Record; export { queryKey } from './queryKeys.js'; export interface ResolveQueriesOptions { /** * Block type schemas keyed by `api_id`, so the walk can descend into blocks. * * `ReadonlyMap` because the caller is entitled to pass a shared empty one: an item with no placed * blocks skips loading the registry altogether, and nothing here has any business writing to it. */ blockTypes: ReadonlyMap; /** Whether unpublished items may appear — true only under a preview token. */ includeUnpublished?: boolean; } export interface ResolvedQueries { /** Keyed by `queryKey(containerId, fieldApiId)`. */ queries: Record; /** Every matched item, stripped to what a result carries, for the caller to fold into its maps. */ items: { item: ContentItem; fields: FieldRow[]; data: Record; }[]; /** * The `api_id` of every content type this page listed, for cache tagging. * * A page carrying a listing depends on the *type*, not only on the members it matched. The cached * copy of "the six soonest events" names the six it had, which is exactly the set that does not * contain the seventh — so invalidating by matched item can never bring a newly published one * into the list. Reported from here because this is the only place that knows which types were * consulted; the field stores a type id and the tag wants the readable `api_id`. */ targetTypeApiIds: string[]; } /** * Resolve every query field on an item. * * Returns the answers **and** the matched rows, rather than writing into the payload itself, so the * caller keeps one place where the lookup maps are assembled. That matters because a matched item * may also be a relation target on the same page, and merging is the caller's job. */ export declare function resolveItemQueries(db: Kysely, fields: FieldRow[], data: Record, itemId: string, options: ResolveQueriesOptions): Promise;