~ knotless Search/
↑↓ move↵ open⇧↵ all results

grain.social/grain

photography-focused photo sharing platform

grain.social
1 collaborator
0
2
78 lines · 2.8 KB Raw History
# 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.

```ts
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`:

```ts
// 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, buildBaseContext
- `xrpc.ts` — XrpcContext extends BaseContext, reuse buildBaseContext
- `opengraph.ts` — Reuse buildBaseContext
- `feeds.ts` — Hydrate signature, executeFeed, exports
- `cli.ts` — Codegen: BaseContext imports/exports, defineFeed overloads

**Template projects:**

- `server/feeds/_hydrate.ts` — Accept (ctx: BaseContext, items: Row<T>[])
- `server/feeds/*.ts` — Pass (ctx, items) to hydrate
- `server/xrpc/*.ts` — Pass ctx directly to hydrators