authorgravatar for me@t3.ggTheo Browne <me@t3.gg> 2026-09-04 09:01:57-07:00
committergravatar for noreply@github.comGitHub <noreply@github.com> 2026-09-04 09:01:57-07:00
logc75299ee2085a121bceb6df76796e971fe92b5b6
tree4db8c44b3bf06e3d6cd1b698ecc8ace8a3fc987f
parentdffb4cd3b16dc6f41aced99922950ee3083082c6
signature Signed by PGP key B5690EEEBB952194

perf(relay): avoid repeated activity decoding (#9708)


4 files changed, 44 insertions(+), 25 deletions(-)

infra/relay/src/agentActivity/AgentActivityPublisher.test.ts+2-8
......@@ -517,7 +517,7 @@ describe("AgentActivityPublisher", () => {
517517 });
518518
519519 it.effect(
520 "does not build Live Activity aggregates for links with Live Activities disabled",
520 "delivers notifications without querying activity rows when Live Activities are disabled",
521521 () => {
522522 const notificationState: RelayAgentActivityState = {
523523 ...state,
......@@ -549,13 +549,7 @@ describe("AgentActivityPublisher", () => {
549549 AgentActivityRows.AgentActivityRows,
550550 makeAgentActivityRows({
551551 listForUser: () =>
552 Effect.succeed([
553 {
554 ...state,
555 environmentId: "other-env" as RelayAgentActivityState["environmentId"],
556 threadId: "other-thread" as RelayAgentActivityState["threadId"],
557 },
558 ]),
552 Effect.die("Notification-only delivery must not read rows"),
559553 }),
560554 ),
561555 Layer.succeed(
infra/relay/src/agentActivity/AgentActivityPublisher.ts+3-1
......@@ -58,7 +58,9 @@ export const make = Effect.gen(function* () {
5858 readonly state: RelayAgentActivityState | null;
5959 readonly nowMs: number;
6060 }) {
61 const activeStates = yield* rows.listForUser({ userId: input.deliveryUser.userId });
61 const activeStates = input.deliveryUser.liveActivitiesEnabled
62 ? yield* rows.listForUser({ userId: input.deliveryUser.userId })
63 : [];
6264 const liveActivityAggregate = input.deliveryUser.liveActivitiesEnabled
6365 ? makeAggregateState({
6466 activeStates,
infra/relay/src/agentActivity/AgentActivityRows.test.ts+36
......@@ -19,6 +19,42 @@ const state: RelayAgentActivityState = {
1919};
2020
2121describe("AgentActivityRows", () => {
22 it.effect("validates database JSON objects and ignores invalid activity rows", () => {
23 const queryRows = [
24 { stateJson: null },
25 { stateJson: JSON.stringify(state) },
26 { stateJson: { ...state, phase: "invalid" } },
27 { stateJson: { ...state, unrelated: "ignored" } },
28 ];
29 const db = {
30 select: () => ({
31 from: () => ({
32 innerJoin: () => ({
33 where: () => ({
34 orderBy: () => Effect.succeed(queryRows),
35 }),
36 }),
37 }),
38 }),
39 } as unknown as RelayDb.RelayDb["Service"];
40
41 return Effect.gen(function* () {
42 const rows = yield* AgentActivityRows.AgentActivityRows;
43 expect(yield* rows.listForUser({ userId: "user-2" })).toEqual([state]);
44 expect(
45 yield* rows.getForUserThread({
46 userId: "user-2",
47 environmentId: "env-1",
48 threadId: "thread-1",
49 }),
50 ).toEqual(state);
51 }).pipe(
52 Effect.provide(
53 AgentActivityRows.layer.pipe(Layer.provide(Layer.succeed(RelayDb.RelayDb, db))),
54 ),
55 );
56 });
57
2258 it.effect("preserves activity context on persistence failures", () => {
2359 const cause = new Error("database unavailable");
2460 const failingDb = {
infra/relay/src/agentActivity/AgentActivityRows.ts+3-16
......@@ -92,15 +92,12 @@ export class AgentActivityRows extends Context.Service<
9292>()("t3code-relay/agentActivity/AgentActivityRows") {}
9393
9494const decodeJsonString = Schema.decodeEffect(Schema.fromJsonString(Schema.Unknown));
95const encodeJsonValue = Schema.encodeEffect(Schema.fromJsonString(Schema.Unknown));
9695
9796const encodeRelayAgentActivityStateJson = Schema.encodeEffect(
9897 Schema.fromJsonString(RelayAgentActivityStateSchema),
9998);
10099
101const decodeRelayAgentActivityStateJson = Schema.decodeUnknownOption(
102 Schema.fromJsonString(RelayAgentActivityStateSchema),
103);
100const decodeRelayAgentActivityState = Schema.decodeUnknownOption(RelayAgentActivityStateSchema);
104101
105102export const make = Effect.gen(function* () {
106103 const db = yield* RelayDb.RelayDb;
......@@ -229,13 +226,8 @@ export const make = Effect.gen(function* () {
229226 )
230227 .orderBy(desc(relayAgentActivityRows.updatedAt))
231228 .pipe(
232 Effect.flatMap((rows) =>
233 Effect.forEach(rows, (row) => encodeJsonValue(row.stateJson), {
234 concurrency: "unbounded",
235 }),
236 ),
237229 Effect.map((rows) =>
238 rows.flatMap((row) => Option.toArray(decodeRelayAgentActivityStateJson(row))),
230 rows.flatMap((row) => Option.toArray(decodeRelayAgentActivityState(row.stateJson))),
239231 ),
240232 Effect.mapError(
241233 (cause) =>
......@@ -271,14 +263,9 @@ export const make = Effect.gen(function* () {
271263 )
272264 .orderBy(desc(relayAgentActivityRows.updatedAt))
273265 .pipe(
274 Effect.flatMap((rows) =>
275 Effect.forEach(rows, (row) => encodeJsonValue(row.stateJson), {
276 concurrency: "unbounded",
277 }),
278 ),
279266 Effect.map((rows) => {
280267 for (const row of rows) {
281 const decoded = decodeRelayAgentActivityStateJson(row);
268 const decoded = decodeRelayAgentActivityState(row.stateJson);
282269 if (Option.isSome(decoded)) {
283270 return decoded.value;
284271 }