BaseContext: Shared Hydration Context#
Goal: Extract a shared BaseContext type from hatk so hydrators work with both feed and XRPC contexts without manual field spreading or as any casts.
Problem: HydrateContext and XrpcContext share 90% of their fields (db, lookup, count, getRecords, labels, blobUrl, viewer). Hydrators that need to work with both must manually spread every field and cast. Adding getRecords to XrpcContext required touching four construction sites.
Design#
BaseContext#
Shared interface in hydrate.ts. Provides data access tools any hydrator needs.
export interface BaseContext {
viewer: { did: string; handle?: string } | null
db: { query: (sql: string, params?: unknown[]) => Promise<unknown[]> }
getRecords: <R = unknown>(collection: string, uris: string[]) => Promise<Map<string, Row<R>>>
lookup: <R = unknown>(collection: string, field: string, values: string[]) => Promise<Map<string, Row<R>>>
count: (collection: string, field: string, values: string[]) => Promise<Map<string, number>>
labels: (uris: string[]) => Promise<Map<string, unknown[]>>
blobUrl: (did: string, ref: unknown, preset?: 'avatar' | 'banner' | 'feed_thumbnail' | 'feed_fullsize') => string | undefined
}
buildBaseContext(viewer) constructs one. No items parameter — items are always a separate argument.
XrpcContext extends BaseContext#
Adds XRPC-specific fields: params, cursor, limit, search, resolve, exists, packCursor, unpackCursor, isTakendown, filterTakendownDids, db.run. Context construction in xrpc.ts and opengraph.ts reuses buildBaseContext and extends.
Feed hydrate signature#
Changes from (ctx: HydrateContext<T>) => Promise<View[]> to (ctx: BaseContext, items: Row<T>[]) => Promise<View[]>. HydrateContext is removed entirely.
Hydrator reuse#
XRPC handlers pass ctx directly to hydrators since XrpcContext extends BaseContext:
// Before
const galleries = await hydrateGalleries({
items: result.records, viewer: ctx.viewer, db: ctx.db,
getRecords: ctx.getRecords, lookup: ctx.lookup, count: ctx.count,
labels: ctx.labels, blobUrl: ctx.blobUrl,
} as any);
// After
const galleries = await hydrateGalleries(ctx, result.records);
Files to change#
hatk framework:
hydrate.ts— Add BaseContext, remove HydrateContext, buildBaseContextxrpc.ts— XrpcContext extends BaseContext, reuse buildBaseContextopengraph.ts— Reuse buildBaseContextfeeds.ts— Hydrate signature, executeFeed, exportscli.ts— Codegen: BaseContext imports/exports, defineFeed overloads
Template projects:
server/feeds/_hydrate.ts— Accept (ctx: BaseContext, items: Row[])server/feeds/*.ts— Pass (ctx, items) to hydrateserver/xrpc/*.ts— Pass ctx directly to hydrators