Home

boring / v2

publicBoring-Software-Inc/v2
Overview Code History Branches Pull requestsIssuesInsights Campfire Docs
main
HomeOverview Code PRsIssues

refactor(db): activity feed wire shapes to contracts + typed row mapping

4 months ago

e47c736
Authored
grim7/12/2026, 11:59:56 AM
A live crash (received_at.toISOString is not a function) exposed a typing lie:
listActivityFeed uses db.execute() (raw SQL — the LATERAL + grp CTE aren't
expressible in the query builder) and typed its rows `as unknown as
{ received_at: Date }`. Raw pg rows are NOT Drizzle-mapped — timestamptz comes
back an ISO string, so the .toISOString() blew up. Fix it properly, not with a
band-aid.

- move the feed wire shapes into @tripwire/contracts/activity.ts as zod schemas
  (run summary, timeline entry, group, feed item, feed). they crossed the
  server->client boundary and were duplicated in db/services AND web — the drift
  class. delete both copies; db + web import from contracts. `pending` is an
  explicit optional client-only flag (server never emits it).
- every raw query maps through explicit coercion (mapEntry/mapRun/asMs/asIso/
  asString) off Record<string, unknown> — no `as unknown as` on query results.
  the one surviving `as` is row.normalized as NormalizedEvent (jsonb validated
  on write, §5.6), commented.
- the getActivityFeed server fn now activityFeedSchema.parse()s its output — a
  drifted normalized event or mistyped timestamp fails loudly at the boundary.
- integration test on real postgres: listActivityFeed returns a group whose
  latestActivityAt is a valid ISO string and whose output parses clean against
  the contract, including a standalone (subject_number IS NULL) row.

audit of other web server-fn casts: only rules.functions.ts has two field-level
`as JsonValue` (JSON coercion, not a return-shape cast) — left as is.

Checks: biome clean, typecheck all pkgs 0, boundaries ok, 197 tests.

Parent1e50b33

7 files changed
  • DECISIONS.md+27−0
  • all-summaries.md+14−0
  • apps/web/src/lib/activity.functions.ts+17−40
  • packages/contracts/src/activity.ts+62−0
  • packages/contracts/src/index.ts+1−0
  • packages/db/src/services/activity.integration.test.ts+181−17
  • packages/db/src/services/events.ts+85−104