Skip to content

refactor: simplify realtime message dispatch - #27

Closed
Rieranthony wants to merge 3 commits into
mainfrom
codex/adapt-upstash/realtime-for-our-codebase-dp2nye
Closed

Rieranthony wants to merge 3 commits into
mainfrom
codex/adapt-upstash/realtime-for-our-codebase-dp2nye

Conversation

@Rieranthony

@Rieranthony Rieranthony commented Oct 9, 2025 •

Copy link
Copy Markdown
Contributor

Summary

  • register a single realtime instance scoped by website channels and emit message.created events directly from the API emitter
  • update the realtime SSE routes to filter the unified message namespace and rely on visitor ids for audience checks
  • simplify the visitor and dashboard realtime hooks plus their consumers to use nested handler maps derived from the shared schema

Testing

  • bunx turbo run lint --filter=@cossistant/realtime --filter=@cossistant/api --filter=@cossistant/react --filter=@cossistant/web (fails: @cossistant/location lint requires the biome binary in this environment)

https://chatgpt.com/codex/tasks/task_e_68e7952b9b28832b9d6f041c8db8c71a

Summary by CodeRabbit

  • New Features

    • Live real‑time message delivery to dashboard and visitor widget via new streaming endpoints and client hooks.
  • Improvements

    • Missed-message replay, better reconnect/keepalive behavior, and configurable realtime endpoints/params.
    • Stricter event validation and scoped filtering to improve delivery correctness.
  • Tests

    • New unit tests covering realtime event validation and subscription matching.
  • Chores

    • Redis driver switched to improve streaming reliability.

@vercel

vercel Bot commented Oct 9, 2025 •

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Preview Comments Updated (UTC)
web Error Error Oct 9, 2025 3:54pm

@coderabbitai

coderabbitai Bot commented Oct 9, 2025 •

Copy link
Copy Markdown
Contributor

Walkthrough

Introduces a Redis Streams–backed realtime subsystem: adds a Realtime instance wired to ioredis with zod validation, a realtime emitter, EventSource stream routes for visitors and dashboards, client hooks/providers (visitor/dashboard) and updates useRealtime to support endpoints/params; plus related test and event-shape adjustments.

Changes

Cohort / File(s) Summary
API bootstrap
apps/api/src/index.ts
Imports and calls registerRealtimeRoutes(app) to register realtime routes at startup.
API realtime wiring
apps/api/src/realtime/realtime.ts, apps/api/src/realtime/emitter.ts, apps/api/src/realtime/routes.ts
New Realtime instance (ioredis-backed) with MESSAGE_CREATED schema; emitter exposes getRealtimeEmitter/realtimeEmitter and emits MESSAGE_CREATED; visitor and dashboard EventSource stream handlers and registerRealtimeRoutes added with auth/filtering.
API utils & websocket shapes
apps/api/src/utils/message.ts, apps/api/src/utils/websocket-connection.ts, apps/api/src/ws/socket.ts, apps/api/src/ws/router.test.ts, apps/api/src/realtime/routes.test.ts, apps/api/src/ws/socket.test.ts
Message creation now resolves/embeds visitorId for realtime payloads; connection/disconnect event shapes moved to include metadata in payload; tests updated to match reflowed event shapes; route helper tests added.
Web dashboard integration
apps/web/src/app/(dashboard)/[websiteSlug]/providers/realtime.tsx
Moves MESSAGE_CREATED handling into memoized dashboardRealtimeEvents and uses useDashboardRealtime(websiteId, events).
React support provider
packages/react/src/realtime/support-provider.tsx
Adds memoized handleMessageCreated, integrates useVisitorRealtime, and forwards message.created via visitorRealtime events; passes publicKey when available.
Realtime client surface
packages/realtime/src/client/index.ts, packages/realtime/src/client/use-realtime.ts, packages/realtime/src/client/use-dashboard-realtime.ts, packages/realtime/src/client/use-visitor-realtime.ts, packages/realtime/src/client/utils.ts
Exposes new hooks useVisitorRealtime and useDashboardRealtime; useRealtime gains endpoint and params and URL building via URL/URLSearchParams with browser guard; adds EventHandlerRecord type and hasEventHandlers helper; dashboard/visitor hooks wrap useRealtime with typed events and gating.
Realtime server core & handler
packages/realtime/src/server/realtime.ts, packages/realtime/src/server/handler.ts
Switched to ioredis types; runtime zod shape extraction (getObjectShape, isZodObject); publishing uses JSON payload via XADD (MAXLEN "~" 100) and PUBLISH; server handler reworked to use duplicate Redis subscriber, parse stream entries, support keepalive/replay/filtering, responseHeaders, and safer enqueue/cleanup.
Types & event shapes
packages/types/src/realtime-events.ts
Reworked event schemas to merge a common metadata schema into each event; MESSAGE_CREATED & other event payloads reorganized (top-level conversationId/visitorId, enriched message fields).
Tests & stores updates
packages/core/src/store/*-store.test.ts, packages/core/src/store/messages-store.test.ts
Tests updated to match new event normalization (some payload fields moved to top-level); minor reflows in typing-store tests.
Manifests / formatting
packages/realtime/package.json, apps/web/package.json
Replaced @upstash/redis with ioredis in realtime package.json; added trailing newline in web package.json.

Sequence Diagram(s)

sequenceDiagram
  autonumber
  actor Browser as Visitor Browser
  participant API as API /v1/realtime/visitor
  participant Auth as Auth + DB
  participant RT as Realtime (ioredis Streams)
  participant Sub as Redis Subscriber

  Browser->>API: GET /v1/realtime/visitor?publicKey&visitorId
  API->>Auth: performAuthentication + visitor lookup
  Auth-->>API: websiteId, visitor, access
  API->>Sub: subscribe(streamKey=channel)
  API-->>Browser: 200 EventSource stream

  note over RT,Sub: MESSAGE_CREATED publish flow
  participant App as App Logic (emit)
  App-->>RT: realtime.emit(MESSAGE_CREATED, payload)
  RT->>Sub: XADD stream (JSON payload) + PUBLISH
  Sub-->>API: stream message parsed
  API-->>Browser: event: message.created (payload)

  rect rgba(220,240,255,0.25)
    note right of API: visitor filter ensures visitorId/channel match
  end
Loading
sequenceDiagram
  autonumber
  actor Agent as Dashboard User
  participant API as API /v1/realtime/dashboard
  participant Session as TRPC Session
  participant Access as Website Access
  participant RT as Realtime (ioredis Streams)
  participant Sub as Redis Subscriber

  Agent->>API: GET /v1/realtime/dashboard?channel=websiteId
  API->>Session: getTRPCSession
  Session-->>API: session
  API->>Access: getWebsiteByIdWithAccess
  Access-->>API: authorized
  API->>Sub: subscribe(streamKey=websiteId)
  API-->>Agent: 200 EventSource stream

  RT-->>Sub: MESSAGE_CREATED (stream entry)
  Sub-->>API: parsed event
  API-->>Agent: event: message.created
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Possibly related PRs

Poem

I twitch my whiskers at streams that race,
Redis hums softly, events find their place.
Visitors listen, dashboards delight,
Messages hop through day and night.
A rabbit cheers for realtime’s pace 🐇

Pre-merge checks and finishing touches

✅ Passed checks (3 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title Check ✅ Passed The title accurately summarizes the primary change as a refactor aimed at simplifying how realtime messages are dispatched across the API and client hooks, matching the PR’s objectives around consolidating the emitter, SSE routes, and hook implementations. It is concise, clear, and focused on the main refactoring effort without extraneous details.
Docstring Coverage ✅ Passed No functions found in the changes. Docstring coverage check skipped.
✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch codex/adapt-upstash/realtime-for-our-codebase-dp2nye

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
packages/realtime/src/server/realtime.ts (1)

69-98: Streams trim length is too low and hard-coded; risks missed replays on reconnect

Using MAXLEN "~" 100 can drop events before clients reconnect, causing gaps if last_ack lags. Also, XADD/PUBLISH errors aren’t caught.

  • Make max stream length configurable (per instance or channel) and raise default (e.g., 1000–10000) to reduce loss under bursty loads.
  • Wrap XADD and PUBLISH in try/catch and log via _logger to avoid unhandled rejections.

Minimal immediate change:

-              "100",
+              "1000",

Example (configurable) outside this hunk:

// In Opts:
export type Opts = { /* ... */ maxStreamLen?: number };

// In ctor:
this._maxStreamLen = data.maxStreamLen ?? 5000;

// In emit:
await this._redis.xadd(
  `channel:${channel}`,
  "MAXLEN", "~", String(this._maxStreamLen),
  "*", "payload", streamPayload
);

And add try/catch around xadd/publish:

try {
  const id = await this._redis.xadd(...);
  await this._redis.publish(...);
} catch (err) {
  this._logger.error("Emit failed", { channel, outerKey, innerKey, err });
}
🧹 Nitpick comments (5)
packages/realtime/src/client/use-realtime.ts (1)

90-127: Reconnect and handler staleness: include endpoint/params in deps and use a ref for handlers

Current connection won’t refresh when endpoint/params change, and onmessage closes over an old events object. Also, consider bounding processedIds to avoid growth.

Proposed adjustments:

  • Track events in a ref and read from it inside onmessage.
  • Include endpoint and a stable params key in the effect deps to trigger reconnection on changes.
  • Bound duplicate set (e.g., keep last N ids or rely on lastAckRef).

Illustrative changes:

// At hook top:
const handlersRef = useRef(events);
useEffect(() => { handlersRef.current = events; }, [events]);

// Build a stable key for params:
const paramsKey = useMemo(() => JSON.stringify(params ?? {}), [params]);

// In onmessage:
const handler = userEvent.__event_path.reduce(
  (acc: any, key: any) => acc?.[key],
  handlersRef.current
);

// In useEffect deps:
}, [channel, enabled, endpoint, paramsKey]);

Optionally, replace processedIdsRef with a small ring buffer or compare against lastAckRef to avoid unbounded growth.

apps/api/src/realtime/routes.ts (3)

68-74: Use structured logger instead of console.error.

Replace console.error with a proper logger (e.g., realtime._logger.error) for consistency with the realtime subsystem's logging approach.

Apply this diff:

 	} catch (error) {
 		if (error instanceof AuthValidationError) {
 			return new Response(error.message, { status: error.statusCode });
 		}
-		console.error("[Realtime] Visitor auth failed", error);
+		// TODO: Add logger to handler or import from realtime
+		console.error("[Realtime] Visitor auth failed", error);
 		return new Response("Authentication failed", { status: 401 });
 	}

131-137: Add data type validation to dashboard filter for consistency.

The dashboard filter only validates the event path but doesn't check the data type, unlike the visitor filter (lines 88-91). This inconsistency could allow events with the correct path but wrong data structure to pass through.

Apply this diff to add type validation:

 	filter: ({ event }) => {
+		const data = event.data as RealtimeMessageEvent | undefined;
 		return (
 			Array.isArray(event.__event_path) &&
 			event.__event_path[0] === "message" &&
-			event.__event_path[1] === "created"
+			event.__event_path[1] === "created" &&
+			data?.type === "MESSAGE_CREATED"
 		);
 	},

98-105: Document visitorId fallback logic. Add a comment above the extraction to explain that data.payload.message.visitorId is the DB-recorded visitor ID and data.visitorId is the event’s metadata fallback for cases where the stored message lacks a visitorId (per the MESSAGE_CREATED router tests).

packages/realtime/src/server/handler.ts (1)

281-359: Consider using Zod for payload validation.

The manual JSON parsing and validation in parsePublishedPayload and parseStreamEntry works but could be more robust and maintainable with Zod schemas, especially since the project already uses Zod extensively (as seen in the realtime schema patterns).

Example refactor for parsePublishedPayload:

import { z } from "zod";

const publishedPayloadSchema = z.union([
  z.object({ type: z.literal("ping"), timestamp: z.number() }),
  z.object({
    __event_path: z.array(z.string()),
    __stream_id: z.string(),
    data: z.unknown(),
  }),
]);

function parsePublishedPayload(message: unknown): PublishedPayload | null {
  if (typeof message !== "string") {
    return null;
  }

  try {
    const parsed = JSON.parse(message);
    return publishedPayloadSchema.parse(parsed);
  } catch {
    return null;
  }
}
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 8bd8040 and a3a1a28.

⛔ Files ignored due to path filters (1)
  • bun.lock is excluded by !**/*.lock
📒 Files selected for processing (15)
  • apps/api/src/index.ts (2 hunks)
  • apps/api/src/realtime/emitter.ts (3 hunks)
  • apps/api/src/realtime/realtime.ts (1 hunks)
  • apps/api/src/realtime/routes.ts (1 hunks)
  • apps/web/package.json (1 hunks)
  • apps/web/src/app/(dashboard)/[websiteSlug]/providers/realtime.tsx (2 hunks)
  • packages/react/src/realtime/support-provider.tsx (4 hunks)
  • packages/realtime/package.json (1 hunks)
  • packages/realtime/src/client/index.ts (1 hunks)
  • packages/realtime/src/client/use-dashboard-realtime.ts (1 hunks)
  • packages/realtime/src/client/use-realtime.ts (3 hunks)
  • packages/realtime/src/client/use-visitor-realtime.ts (1 hunks)
  • packages/realtime/src/client/utils.ts (1 hunks)
  • packages/realtime/src/server/handler.ts (7 hunks)
  • packages/realtime/src/server/realtime.ts (3 hunks)
🧰 Additional context used
📓 Path-based instructions (15)
**/*.{ts,tsx}

📄 CodeRabbit inference engine (.cursor/rules/global-rules.mdc)

**/*.{ts,tsx}: Manage shared component state via React Context
Implement full TypeScript support for all public APIs
Provide proper type definitions for all props and refs
Use appropriate generic constraints where necessary
Export type definitions for the public API
Avoid TypeScript enums; prefer const objects with 'as const' or Object.freeze
Provide clear, descriptive runtime errors for missing context/props and misuse
Expose dedicated hooks for context access that throw helpful errors when context is missing
Add explicit error messages for: outside-context usage, missing required props, invalid prop combinations, and validation failures
Document each component with JSDoc: purpose, props (required/optional), context requirements, example usage, pitfalls
State management: use React Context for global state, perform correct state updates, and handle side effects appropriately
Validation: implement runtime checks and validate prop combinations with clear errors
Create context with createContext<ContextType|null>(null) and a useContext hook that throws a clear error when missing

**/*.{ts,tsx}: Don't use primitive type aliases or misleading types
Don't use empty type parameters in type aliases and interfaces
Don't use any or unknown as type constraints
Don't have unused function parameters
Don't have unused private class members
Don't return a value from a function that has a 'void' return type
Don't use the TypeScript directive @ts-ignore
Ensure switch-case statements are exhaustive (especially with unions)
Don't use TypeScript enums
Don't add type annotations to variables, parameters, and class properties initialized with literal expressions
Don't use TypeScript namespaces
Don't use non-null assertions with the ! postfix operator
Don't use parameter properties in class constructors
Don't use user-defined types (where discouraged by policy)
Use as const instead of literal types and type annotations
Use either T[] or Array consistently
Use consistent accessibili...

Files:

  • packages/realtime/src/client/use-realtime.ts
  • packages/realtime/src/client/use-visitor-realtime.ts
  • apps/api/src/realtime/realtime.ts
  • packages/realtime/src/client/utils.ts
  • apps/api/src/index.ts
  • packages/react/src/realtime/support-provider.tsx
  • packages/realtime/src/server/handler.ts
  • packages/realtime/src/client/index.ts
  • packages/realtime/src/client/use-dashboard-realtime.ts
  • apps/api/src/realtime/routes.ts
  • apps/web/src/app/(dashboard)/[websiteSlug]/providers/realtime.tsx
  • apps/api/src/realtime/emitter.ts
  • packages/realtime/src/server/realtime.ts
**/*.{js,jsx,ts,tsx}

📄 CodeRabbit inference engine (.cursor/rules/ultracite.mdc)

**/*.{js,jsx,ts,tsx}: Don't use the accessKey attribute on any HTML element
Use correct ISO language/country codes for the lang attribute
Don't hardcode sensitive data like API keys and tokens
Use === and !==

Files:

  • packages/realtime/src/client/use-realtime.ts
  • packages/realtime/src/client/use-visitor-realtime.ts
  • apps/api/src/realtime/realtime.ts
  • packages/realtime/src/client/utils.ts
  • apps/api/src/index.ts
  • packages/react/src/realtime/support-provider.tsx
  • packages/realtime/src/server/handler.ts
  • packages/realtime/src/client/index.ts
  • packages/realtime/src/client/use-dashboard-realtime.ts
  • apps/api/src/realtime/routes.ts
  • apps/web/src/app/(dashboard)/[websiteSlug]/providers/realtime.tsx
  • apps/api/src/realtime/emitter.ts
  • packages/realtime/src/server/realtime.ts
**/*.{js,ts}

📄 CodeRabbit inference engine (.cursor/rules/ultracite.mdc)

**/*.{js,ts}: Don't use consecutive spaces in regular expression literals
Don't use the arguments object
Don't use the comma operator
Don't use unnecessary boolean casts
Don't use unnecessary callbacks with flatMap
Prefer for...of statements instead of Array.forEach
Don't use this and super in static contexts
Don't use unnecessary catch clauses
Don't use unnecessary constructors
Don't use unnecessary continue statements
Don't export empty modules that don't change anything
Don't use unnecessary escape sequences in regular expression literals
Don't use unnecessary labels
Don't use unnecessary nested block statements
Don't rename imports, exports, and destructured assignments to the same name
Don't use unnecessary string or template literal concatenation
Don't use String.raw in template literals when there are no escape sequences
Don't use useless case statements in switch statements
Avoid ternary operators when simpler alternatives exist
Don't use useless this aliasing
Don't initialize variables to undefined
Don't use void operators
Use Date.now() to get milliseconds since the Unix Epoch
Prefer .flatMap() over map().flat() when possible
Use literal property access instead of computed property access
Don't use parseInt() or Number.parseInt() when numeric literals suffice (binary/octal/hex)
Use concise optional chaining instead of chained logical expressions
Prefer regular expression literals over the RegExp constructor when possible
Don't use non–base-10 or underscore-separated number literal object member names
Remove redundant terms from logical expressions
Prefer while loops over for loops when no initializer/update is needed
Don't reassign const variables
Don't use constant expressions in conditions
Don't use Math.min/Math.max to clamp values when result is constant
Don't use empty character classes in regular expression literals
Don't use empty destructuring patterns
Don't call global object properties as functions
Don't declare functions and vars that are acce...

Files:

  • packages/realtime/src/client/use-realtime.ts
  • packages/realtime/src/client/use-visitor-realtime.ts
  • apps/api/src/realtime/realtime.ts
  • packages/realtime/src/client/utils.ts
  • apps/api/src/index.ts
  • packages/realtime/src/server/handler.ts
  • packages/realtime/src/client/index.ts
  • packages/realtime/src/client/use-dashboard-realtime.ts
  • apps/api/src/realtime/routes.ts
  • apps/api/src/realtime/emitter.ts
  • packages/realtime/src/server/realtime.ts
**/*.{js,ts,tsx}

📄 CodeRabbit inference engine (.cursor/rules/ultracite.mdc)

**/*.{js,ts,tsx}: Don't create classes that only have static members
Don't return a value from a constructor
Don't use super() incorrectly inside classes; ensure super() is called in derived constructors
Ensure super() is called exactly once on every code path in a class constructor before this is accessed if the class has a superclass
Place getters and setters for the same property next to each other in class and object definitions
Ensure JSDoc comment lines start with a single asterisk, except for the first one
Don't use duplicate class members

Files:

  • packages/realtime/src/client/use-realtime.ts
  • packages/realtime/src/client/use-visitor-realtime.ts
  • apps/api/src/realtime/realtime.ts
  • packages/realtime/src/client/utils.ts
  • apps/api/src/index.ts
  • packages/react/src/realtime/support-provider.tsx
  • packages/realtime/src/server/handler.ts
  • packages/realtime/src/client/index.ts
  • packages/realtime/src/client/use-dashboard-realtime.ts
  • apps/api/src/realtime/routes.ts
  • apps/web/src/app/(dashboard)/[websiteSlug]/providers/realtime.tsx
  • apps/api/src/realtime/emitter.ts
  • packages/realtime/src/server/realtime.ts
**/*.{js,tsx,ts,jsx}

📄 CodeRabbit inference engine (.cursor/rules/ultracite.mdc)

Use arrow functions instead of function expressions

Files:

  • packages/realtime/src/client/use-realtime.ts
  • packages/realtime/src/client/use-visitor-realtime.ts
  • apps/api/src/realtime/realtime.ts
  • packages/realtime/src/client/utils.ts
  • apps/api/src/index.ts
  • packages/react/src/realtime/support-provider.tsx
  • packages/realtime/src/server/handler.ts
  • packages/realtime/src/client/index.ts
  • packages/realtime/src/client/use-dashboard-realtime.ts
  • apps/api/src/realtime/routes.ts
  • apps/web/src/app/(dashboard)/[websiteSlug]/providers/realtime.tsx
  • apps/api/src/realtime/emitter.ts
  • packages/realtime/src/server/realtime.ts
**/*.{js,ts,jsx,tsx}

📄 CodeRabbit inference engine (.cursor/rules/ultracite.mdc)

**/*.{js,ts,jsx,tsx}: Prevent duplicate polyfills from Polyfill.io
Don't use irregular whitespace characters

Files:

  • packages/realtime/src/client/use-realtime.ts
  • packages/realtime/src/client/use-visitor-realtime.ts
  • apps/api/src/realtime/realtime.ts
  • packages/realtime/src/client/utils.ts
  • apps/api/src/index.ts
  • packages/react/src/realtime/support-provider.tsx
  • packages/realtime/src/server/handler.ts
  • packages/realtime/src/client/index.ts
  • packages/realtime/src/client/use-dashboard-realtime.ts
  • apps/api/src/realtime/routes.ts
  • apps/web/src/app/(dashboard)/[websiteSlug]/providers/realtime.tsx
  • apps/api/src/realtime/emitter.ts
  • packages/realtime/src/server/realtime.ts
**/*.{js,ts,ts,tsx}

📄 CodeRabbit inference engine (.cursor/rules/ultracite.mdc)

Don't redeclare variables, functions, classes, and types in the same scope

Files:

  • packages/realtime/src/client/use-realtime.ts
  • packages/realtime/src/client/use-visitor-realtime.ts
  • apps/api/src/realtime/realtime.ts
  • packages/realtime/src/client/utils.ts
  • apps/api/src/index.ts
  • packages/react/src/realtime/support-provider.tsx
  • packages/realtime/src/server/handler.ts
  • packages/realtime/src/client/index.ts
  • packages/realtime/src/client/use-dashboard-realtime.ts
  • apps/api/src/realtime/routes.ts
  • apps/web/src/app/(dashboard)/[websiteSlug]/providers/realtime.tsx
  • apps/api/src/realtime/emitter.ts
  • packages/realtime/src/server/realtime.ts
packages/**

📄 CodeRabbit inference engine (AGENTS.md)

Put shared contracts and utilities in packages/* (e.g., core, types, transactional)

Files:

  • packages/realtime/src/client/use-realtime.ts
  • packages/realtime/src/client/use-visitor-realtime.ts
  • packages/realtime/src/client/utils.ts
  • packages/react/src/realtime/support-provider.tsx
  • packages/realtime/src/server/handler.ts
  • packages/realtime/src/client/index.ts
  • packages/realtime/src/client/use-dashboard-realtime.ts
  • packages/realtime/package.json
  • packages/realtime/src/server/realtime.ts
packages/react/src/**/*.{ts,tsx}

📄 CodeRabbit inference engine (CLAUDE.md)

Develop headless React components (including and primitives) under packages/react/src/

Files:

  • packages/react/src/realtime/support-provider.tsx
**/*.tsx

📄 CodeRabbit inference engine (.cursor/rules/global-rules.mdc)

**/*.tsx: Use the Compound Components pattern with namespaced exports (e.g., FormWizard.Root, FormWizard.Step) so pieces compose under a single parent
Ensure accessibility: keyboard navigation support
Ensure accessibility: include appropriate ARIA attributes
Ensure accessibility: use semantic markup for screen readers
Maintain proper focus management in interactive components
Structure components with forwardRef and typed props/refs as shown (ComponentRef, HTMLAttributes, ReactNode)
Root component acts as main context provider, manages global state, and handles initialization
Child components must be used within Root, read context via hooks, support asChild, and follow consistent props
Props ordering: list required props first; optional props have clear defaults; support common HTML attributes; expose className
Support composition with an asChild prop using Radix UI Slot to allow element replacement
Styling: support className, use data-* attributes for state, and allow style customization
Event handling: support standard DOM events, add custom events when needed, and manage propagation correctly
Base component pattern: forwardRef, choose Slot or intrinsic element via asChild, pass ref/className and spread props

Files:

  • packages/react/src/realtime/support-provider.tsx
  • apps/web/src/app/(dashboard)/[websiteSlug]/providers/realtime.tsx
**/*.{jsx,tsx}

📄 CodeRabbit inference engine (.cursor/rules/ultracite.mdc)

**/*.{jsx,tsx}: Don't set aria-hidden="true" on focusable elements
Don't add ARIA roles, states, and properties to elements that don't support them
Don't use distracting elements like or
Only use the scope prop on elements
Don't assign non-interactive ARIA roles to interactive HTML elements
Ensure label elements have text content and are associated with an input
Don't assign interactive ARIA roles to non-interactive HTML elements
Don't assign tabIndex to non-interactive HTML elements
Don't use positive integers for tabIndex property
Don't include the words "image", "picture", or "photo" in img alt prop
Don't use an explicit role that's the same as the element's implicit role
Static elements with click handlers must have a valid role attribute
Always include a <title> element within SVG elements
Provide meaningful alt text for elements that require alt text
Ensure anchors have content accessible to screen readers
Assign tabIndex to non-interactive elements only when using aria-activedescendant
Include all required ARIA attributes for elements with ARIA roles
Ensure ARIA properties are valid for the element's supported roles
Always include a type attribute for button elements
Elements with interactive roles and handlers must be focusable
Heading elements must have content accessible to screen readers (not hidden with aria-hidden)
Always include a lang attribute on the html element
Always include a title attribute for iframe elements
Accompany onClick with at least one of: onKeyUp, onKeyDown, or onKeyPress
Accompany onMouseOver/onMouseOut with onFocus/onBlur
Include caption tracks for audio and video elements
Prefer semantic elements instead of role attributes in JSX
Ensure all anchors are valid and navigable
Ensure all aria-* properties are valid
Use valid, non-abstract ARIA roles for elements with ARIA roles
Use valid ARIA state and property values
Use valid values for the autocomplete attribute on input elements
Don't use unnecessary fragmen...

Files:

  • packages/react/src/realtime/support-provider.tsx
  • apps/web/src/app/(dashboard)/[websiteSlug]/providers/realtime.tsx
**/*.{jsx,tsx,html}

📄 CodeRabbit inference engine (.cursor/rules/ultracite.mdc)

**/*.{jsx,tsx,html}: Ensure the preconnect attribute is used when using Google Fonts
Don't use target="_blank" without rel="noopener"
Use a recommended display strategy with Google Fonts

Files:

  • packages/react/src/realtime/support-provider.tsx
  • apps/web/src/app/(dashboard)/[websiteSlug]/providers/realtime.tsx
packages/react/**/*.{ts,tsx}

📄 CodeRabbit inference engine (.cursor/rules/react-package-rules.mdc)

packages/react/**/*.{ts,tsx}: Components must render with no built-in styles (headless, unstyled)
Components must accept className, style, and asChild props to enable external styling/control
Do not use emotion, styled-components, or style-prop APIs; styling is via Tailwind/CSS through className/style
Use useRenderElement to create elements and encapsulate render logic (supporting intrinsic tags and render prop overrides)
Components with internal state must be controllable via props (e.g., value/defaultValue/onChange or isOpen/onOpenChange)
Expose internal state via a hook (e.g., useInput, useWindow) or a render prop function child
May reference Base UI logic internally, but never expose Base UI or MUI APIs; do not leak getRootProps/getInputProps; prefer useRenderElement
Prefer small, composable primitives (e.g., Provider, Bubble, Window, MessageList, Message, Input, SendButton, Typing, Event) over monolithic components
Provide a render prop API: render?: (props, state) => ReactElement, and allow className to be string or (state) => string
Support developer-owned rendering: components should not impose visual structure and should allow parent-driven markup via render prop/asChild

Files:

  • packages/react/src/realtime/support-provider.tsx
apps/web/src/app/**

📄 CodeRabbit inference engine (AGENTS.md)

Place Next.js App Router pages/layouts/routes under apps/web/src/app

Files:

  • apps/web/src/app/(dashboard)/[websiteSlug]/providers/realtime.tsx
apps/web/src/{app,components}/**/*.tsx

📄 CodeRabbit inference engine (AGENTS.md)

Use snake_case filenames for React components and layouts in the web app

Files:

  • apps/web/src/app/(dashboard)/[websiteSlug]/providers/realtime.tsx
🧠 Learnings (1)
📚 Learning: 2025-09-17T09:00:28.751Z
Learnt from: CR
PR: cossistantcom/cossistant#0
File: CLAUDE.md:0-0
Timestamp: 2025-09-17T09:00:28.751Z
Learning: Use Bun as the package manager and executor across the monorepo (install, dev, build, lint, format, type-check)

Applied to files:

  • apps/web/package.json
🧬 Code graph analysis (10)
packages/realtime/src/client/use-realtime.ts (1)
packages/realtime/src/server/realtime.ts (1)
  • channel (105-110)
packages/realtime/src/client/use-visitor-realtime.ts (2)
packages/realtime/src/client/utils.ts (2)
  • EventHandlerRecord (1-8)
  • hasEventHandlers (10-27)
packages/realtime/src/client/use-realtime.ts (1)
  • useRealtime (26-240)
apps/api/src/realtime/realtime.ts (3)
packages/types/src/realtime-events.ts (1)
  • RealtimeEvents (9-100)
apps/api/src/redis.ts (1)
  • getRedis (7-39)
packages/realtime/src/server/realtime.ts (2)
  • Realtime (121-129)
  • Realtime (138-140)
apps/api/src/index.ts (1)
apps/api/src/realtime/routes.ts (1)
  • registerRealtimeRoutes (152-162)
packages/react/src/realtime/support-provider.tsx (4)
packages/react/src/provider.tsx (1)
  • useSupport (215-241)
packages/types/src/realtime-events.ts (1)
  • RealtimeEvent (108-115)
packages/react/src/realtime/use-realtime.ts (1)
  • useRealtime (68-144)
packages/realtime/src/client/use-visitor-realtime.ts (1)
  • useVisitorRealtime (22-45)
packages/realtime/src/server/handler.ts (2)
packages/realtime/src/types.ts (2)
  • UserEvent (8-12)
  • SystemEvent (1-6)
packages/realtime/src/server/realtime.ts (4)
  • Opts (11-16)
  • Realtime (121-129)
  • Realtime (138-140)
  • channel (105-110)
packages/realtime/src/client/use-dashboard-realtime.ts (2)
packages/realtime/src/client/utils.ts (2)
  • EventHandlerRecord (1-8)
  • hasEventHandlers (10-27)
packages/realtime/src/client/use-realtime.ts (1)
  • useRealtime (26-240)
apps/api/src/realtime/routes.ts (6)
packages/realtime/src/server/handler.ts (1)
  • handle (34-279)
apps/api/src/lib/auth-validation.ts (2)
  • performAuthentication (366-415)
  • AuthValidationError (25-33)
apps/api/src/db/queries/visitor.ts (1)
  • findVisitorForWebsite (141-160)
apps/api/src/realtime/realtime.ts (1)
  • RealtimeMessageEvent (27-27)
apps/api/src/db/queries/session.ts (1)
  • getTRPCSession (104-111)
apps/api/src/db/queries/website.ts (1)
  • getWebsiteByIdWithAccess (247-302)
apps/web/src/app/(dashboard)/[websiteSlug]/providers/realtime.tsx (3)
packages/types/src/realtime-events.ts (1)
  • RealtimeEvent (108-115)
apps/web/src/app/(dashboard)/[websiteSlug]/providers/events/handlers/message-created.ts (1)
  • handleMessageCreated (73-132)
packages/realtime/src/client/use-dashboard-realtime.ts (1)
  • useDashboardRealtime (20-38)
apps/api/src/realtime/emitter.ts (5)
packages/types/src/realtime-events.ts (1)
  • RealtimeEvent (108-115)
apps/api/src/ws/socket.ts (2)
  • sendEventToVisitor (85-96)
  • sendEventToWebsite (98-109)
packages/realtime/src/server/realtime.ts (1)
  • channel (105-110)
apps/api/src/realtime/realtime.ts (1)
  • realtime (17-25)
apps/api/src/ws/router.ts (1)
  • routeEvent (254-280)
🔇 Additional comments (14)
apps/api/src/realtime/realtime.ts (1)

17-25: Typed schema wiring looks great

Nice job instantiating the shared Realtime schema with a strongly typed MESSAGE_CREATED channel. This keeps payload validation centralized and future-proof.

apps/api/src/realtime/emitter.ts (1)

150-158: Realtime channel emission path is solid

Conditionally emitting MESSAGE_CREATED into the Redis-backed channel while keeping websocket routing as a fallback is a clean integration and maintains resilience on failures.

packages/react/src/realtime/support-provider.tsx (2)

33-43: MESSAGE_CREATED handler wiring looks good

Filters by website, clears typing, forwards to client; aligns with visitor realtime flow.


117-122: Visitor realtime hook usage LGTM

Correctly passes websiteId, visitorId, publicKey, and nested handler map.

packages/realtime/src/client/use-dashboard-realtime.ts (1)

20-38: Typed dashboard realtime hook looks solid

Enablement gating, channel binding, endpoint default, and params plumbing are correct.

apps/api/src/realtime/routes.ts (2)

13-29: LGTM!

The origin header parsing is correct with appropriate fallback handling when URL parsing fails.


152-162: LGTM!

Route registration properly delegates to the stream handlers and returns 204 when no response is provided.

packages/realtime/src/client/use-visitor-realtime.ts (2)

5-20: LGTM!

Type definitions are clear, properly structured, and follow TypeScript best practices with appropriate nullability.


22-45: LGTM!

The hook correctly:

  • Guards against enabling without all required credentials and handlers
  • Converts nullable values to undefined for URL search params
  • Prevents passing empty event handlers to avoid unnecessary subscriptions
packages/realtime/src/server/handler.ts (5)

8-32: LGTM!

The type definitions for middleware, filter, and response headers are clear and provide appropriate context parameters.


34-39: LGTM!

The updated function signature maintains backward compatibility while adding optional middleware, filter, and header customization.


66-143: LGTM!

The Redis integration correctly:

  • Uses duplicate() for independent subscriber connection
  • Implements proper cleanup with unsubscribe/disconnect
  • Applies filter with error handling
  • Guards enqueue operations with isClosed flag

154-196: LGTM!

The reconnection and message replay logic correctly:

  • Fetches the latest cursor for new connections
  • Replays missed messages on reconnect using Redis Streams range queries
  • Applies filters to replayed messages

200-263: LGTM!

Event handlers correctly:

  • Convert Redis errors to system events
  • Handle ping messages separately from user events
  • Log received events with channel context
  • Publish keepalive pings to maintain connection

Comment on lines +84 to +96
const dashboardRealtimeEvents = useMemo(
() => ({
message: {
created: (event: RealtimeEvent<"MESSAGE_CREATED">) => {
handleMessageCreated(event.payload, {
event,
context: realtimeContext,
});
},
},
}),
[handleMessageCreated, realtimeContext]
);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

Fix handler invocation: pass { event, context } (not payload + meta)

handleMessageCreated expects a single object with event and context. Passing event.payload as the first argument will break filtering and message mapping. Use the full event with context.

Apply this diff:

-        const dashboardRealtimeEvents = useMemo(
-                () => ({
-                        message: {
-                                created: (event: RealtimeEvent<"MESSAGE_CREATED">) => {
-                                        handleMessageCreated(event.payload, {
-                                                event,
-                                                context: realtimeContext,
-                                        });
-                                },
-                        },
-                }),
-                [handleMessageCreated, realtimeContext]
-        );
+        const dashboardRealtimeEvents = useMemo(
+                () => ({
+                        message: {
+                                created: (event: RealtimeEvent<"MESSAGE_CREATED">) => {
+                                        handleMessageCreated({
+                                                event,
+                                                context: realtimeContext,
+                                        });
+                                },
+                        },
+                }),
+                [realtimeContext]
+        );

Committable suggestion skipped: line range outside the PR's diff.

🤖 Prompt for AI Agents
In apps/web/src/app/(dashboard)/[websiteSlug]/providers/realtime.tsx around
lines 84 to 96, the handler invocation incorrectly passes event.payload and a
separate meta object to handleMessageCreated; change the call to pass a single
object containing the full event and the realtimeContext (i.e., call
handleMessageCreated({ event, context: realtimeContext })) so the handler
receives the expected shape and filtering/mapping works correctly.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🧹 Nitpick comments (1)
packages/realtime/src/server/realtime.ts (1)

74-102: Consider caching JSON.stringify result.

The code calls JSON.stringify twice (lines 74 and 97) with nearly identical data structures. Consider caching the first stringified result and reusing it, or restructuring to stringify once.

Apply this diff to cache the stringify result:

-            const streamPayload = JSON.stringify({
+            const eventPayload = {
               data,
               __event_path: [outerKey, innerKey],
-            });
+            };
+            const streamPayload = JSON.stringify(eventPayload);

             this._logger.log("⬆️  Emitting event:", {
               channel,
-              __event_path: [outerKey, innerKey],
-              data,
+              ...eventPayload,
             });

             const id = await this._redis.xadd(
               `channel:${channel}`,
               "MAXLEN",
               "~",
               "100",
               "*",
               "payload",
               streamPayload
             );

             await this._redis.publish(
               `channel:${channel}`,
-              JSON.stringify({
-                data,
-                __event_path: [outerKey, innerKey],
+              JSON.stringify({
+                ...eventPayload,
                 __stream_id: id,
               })
             );

Verify error handling approach for Redis operations.

The xadd and publish calls at lines 85 and 95 have no explicit error handling, so failures will propagate to callers. Confirm this is the intended behavior, or consider wrapping in try-catch with appropriate logging/recovery if these operations should be more resilient.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between a3a1a28 and b0bcd17.

📒 Files selected for processing (1)
  • packages/realtime/src/server/realtime.ts (4 hunks)
🧰 Additional context used
📓 Path-based instructions (8)
**/*.{ts,tsx}

📄 CodeRabbit inference engine (.cursor/rules/global-rules.mdc)

**/*.{ts,tsx}: Manage shared component state via React Context
Implement full TypeScript support for all public APIs
Provide proper type definitions for all props and refs
Use appropriate generic constraints where necessary
Export type definitions for the public API
Avoid TypeScript enums; prefer const objects with 'as const' or Object.freeze
Provide clear, descriptive runtime errors for missing context/props and misuse
Expose dedicated hooks for context access that throw helpful errors when context is missing
Add explicit error messages for: outside-context usage, missing required props, invalid prop combinations, and validation failures
Document each component with JSDoc: purpose, props (required/optional), context requirements, example usage, pitfalls
State management: use React Context for global state, perform correct state updates, and handle side effects appropriately
Validation: implement runtime checks and validate prop combinations with clear errors
Create context with createContext<ContextType|null>(null) and a useContext hook that throws a clear error when missing

**/*.{ts,tsx}: Don't use primitive type aliases or misleading types
Don't use empty type parameters in type aliases and interfaces
Don't use any or unknown as type constraints
Don't have unused function parameters
Don't have unused private class members
Don't return a value from a function that has a 'void' return type
Don't use the TypeScript directive @ts-ignore
Ensure switch-case statements are exhaustive (especially with unions)
Don't use TypeScript enums
Don't add type annotations to variables, parameters, and class properties initialized with literal expressions
Don't use TypeScript namespaces
Don't use non-null assertions with the ! postfix operator
Don't use parameter properties in class constructors
Don't use user-defined types (where discouraged by policy)
Use as const instead of literal types and type annotations
Use either T[] or Array consistently
Use consistent accessibili...

Files:

  • packages/realtime/src/server/realtime.ts
**/*.{js,jsx,ts,tsx}

📄 CodeRabbit inference engine (.cursor/rules/ultracite.mdc)

**/*.{js,jsx,ts,tsx}: Don't use the accessKey attribute on any HTML element
Use correct ISO language/country codes for the lang attribute
Don't hardcode sensitive data like API keys and tokens
Use === and !==

Files:

  • packages/realtime/src/server/realtime.ts
**/*.{js,ts}

📄 CodeRabbit inference engine (.cursor/rules/ultracite.mdc)

**/*.{js,ts}: Don't use consecutive spaces in regular expression literals
Don't use the arguments object
Don't use the comma operator
Don't use unnecessary boolean casts
Don't use unnecessary callbacks with flatMap
Prefer for...of statements instead of Array.forEach
Don't use this and super in static contexts
Don't use unnecessary catch clauses
Don't use unnecessary constructors
Don't use unnecessary continue statements
Don't export empty modules that don't change anything
Don't use unnecessary escape sequences in regular expression literals
Don't use unnecessary labels
Don't use unnecessary nested block statements
Don't rename imports, exports, and destructured assignments to the same name
Don't use unnecessary string or template literal concatenation
Don't use String.raw in template literals when there are no escape sequences
Don't use useless case statements in switch statements
Avoid ternary operators when simpler alternatives exist
Don't use useless this aliasing
Don't initialize variables to undefined
Don't use void operators
Use Date.now() to get milliseconds since the Unix Epoch
Prefer .flatMap() over map().flat() when possible
Use literal property access instead of computed property access
Don't use parseInt() or Number.parseInt() when numeric literals suffice (binary/octal/hex)
Use concise optional chaining instead of chained logical expressions
Prefer regular expression literals over the RegExp constructor when possible
Don't use non–base-10 or underscore-separated number literal object member names
Remove redundant terms from logical expressions
Prefer while loops over for loops when no initializer/update is needed
Don't reassign const variables
Don't use constant expressions in conditions
Don't use Math.min/Math.max to clamp values when result is constant
Don't use empty character classes in regular expression literals
Don't use empty destructuring patterns
Don't call global object properties as functions
Don't declare functions and vars that are acce...

Files:

  • packages/realtime/src/server/realtime.ts
**/*.{js,ts,tsx}

📄 CodeRabbit inference engine (.cursor/rules/ultracite.mdc)

**/*.{js,ts,tsx}: Don't create classes that only have static members
Don't return a value from a constructor
Don't use super() incorrectly inside classes; ensure super() is called in derived constructors
Ensure super() is called exactly once on every code path in a class constructor before this is accessed if the class has a superclass
Place getters and setters for the same property next to each other in class and object definitions
Ensure JSDoc comment lines start with a single asterisk, except for the first one
Don't use duplicate class members

Files:

  • packages/realtime/src/server/realtime.ts
**/*.{js,tsx,ts,jsx}

📄 CodeRabbit inference engine (.cursor/rules/ultracite.mdc)

Use arrow functions instead of function expressions

Files:

  • packages/realtime/src/server/realtime.ts
**/*.{js,ts,jsx,tsx}

📄 CodeRabbit inference engine (.cursor/rules/ultracite.mdc)

**/*.{js,ts,jsx,tsx}: Prevent duplicate polyfills from Polyfill.io
Don't use irregular whitespace characters

Files:

  • packages/realtime/src/server/realtime.ts
**/*.{js,ts,ts,tsx}

📄 CodeRabbit inference engine (.cursor/rules/ultracite.mdc)

Don't redeclare variables, functions, classes, and types in the same scope

Files:

  • packages/realtime/src/server/realtime.ts
packages/**

📄 CodeRabbit inference engine (AGENTS.md)

Put shared contracts and utilities in packages/* (e.g., core, types, transactional)

Files:

  • packages/realtime/src/server/realtime.ts
🧠 Learnings (1)
📚 Learning: 2025-09-17T09:00:28.751Z
Learnt from: CR
PR: cossistantcom/cossistant#0
File: CLAUDE.md:0-0
Timestamp: 2025-09-17T09:00:28.751Z
Learning: Applies to packages/types/src/**/*.ts : Ensure Zod schemas mirror any API/database shape changes for end-to-end type safety

Applied to files:

  • packages/realtime/src/server/realtime.ts
🔇 Additional comments (3)
packages/realtime/src/server/realtime.ts (3)

4-6: LGTM! Clean migration to public APIs.

The imports correctly migrate from @upstash/redis to ioredis and update Zod type usage to rely on public API types (ZodObject<ZodRawShape>) instead of internal types like z.$ZodObject. This aligns with Zod v4 best practices and maintains compatibility with v3.25+.


60-67: Verify intentional silent validation skip.

The optional chaining at line 67 (schema?.parse?.(data)) will silently skip validation if schema is undefined (which can occur when shape[innerKey] doesn't exist). This means potentially invalid data could be emitted without validation.

If this is intentional to support flexible schema definitions, consider adding a log statement when validation is skipped. Otherwise, consider using a non-optional parse to fail fast on schema mismatches.


148-164: LGTM! Solid abstraction over Zod internals.

The helper functions provide a robust abstraction for extracting schema shapes, handling both the public .shape API and the internal ._def.shape() pattern for lazy schemas. The instanceof check in isZodObject is the correct approach for type guarding. This design maintains compatibility with both Zod v3.25+ and v4.

Comment on lines +148 to +160
function getObjectShape(schema: z.ZodTypeAny): Record<string, ZodAny | undefined> {
if (isZodObject(schema)) {
if (typeof schema.shape === "object") {
return schema.shape as Record<string, ZodAny>;
}

if (typeof schema._def?.shape === "function") {
return schema._def.shape() as Record<string, ZodAny>;
}
}

return {};
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Fix return type to use ZodTypeAny instead of ZodAny.

The return type Record<string, ZodAny | undefined> is incorrect. ZodAny is the specific type for z.any() schemas, while ZodTypeAny (or z.ZodTypeAny) is the base type for all Zod schemas. Since the shape can contain any Zod schema type (ZodString, ZodNumber, ZodBoolean, etc.), the return type should be Record<string, ZodTypeAny>.

Apply this diff to fix the type:

+import type { ZodAny, ZodObject, ZodRawShape, ZodTypeAny } from "zod";
-function getObjectShape(schema: z.ZodTypeAny): Record<string, ZodAny | undefined> {
+function getObjectShape(schema: z.ZodTypeAny): Record<string, ZodTypeAny> {
   if (isZodObject(schema)) {
     if (typeof schema.shape === "object") {
-      return schema.shape as Record<string, ZodAny>;
+      return schema.shape as Record<string, ZodTypeAny>;
     }

     if (typeof schema._def?.shape === "function") {
-      return schema._def.shape() as Record<string, ZodAny>;
+      return schema._def.shape() as Record<string, ZodTypeAny>;
     }
   }

   return {};
 }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
function getObjectShape(schema: z.ZodTypeAny): Record<string, ZodAny | undefined> {
if (isZodObject(schema)) {
if (typeof schema.shape === "object") {
return schema.shape as Record<string, ZodAny>;
}
if (typeof schema._def?.shape === "function") {
return schema._def.shape() as Record<string, ZodAny>;
}
}
return {};
}
// At the top of the file, alongside your existing imports:
import type { ZodAny, ZodObject, ZodRawShape, ZodTypeAny } from "zod";
function getObjectShape(schema: z.ZodTypeAny): Record<string, ZodTypeAny> {
if (isZodObject(schema)) {
if (typeof schema.shape === "object") {
return schema.shape as Record<string, ZodTypeAny>;
}
if (typeof schema._def?.shape === "function") {
return schema._def.shape() as Record<string, ZodTypeAny>;
}
}
return {};
}
🤖 Prompt for AI Agents
In packages/realtime/src/server/realtime.ts around lines 148 to 160, the
function getObjectShape currently uses Record<string, ZodAny | undefined> which
is incorrect; change the return type to Record<string, z.ZodTypeAny> and update
the two casts to cast to Record<string, z.ZodTypeAny> (remove the | undefined),
so the function returns the correct base Zod schema type for all shape entries.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between b0bcd17 and e9552d9.

📒 Files selected for processing (11)
  • apps/api/src/realtime/routes.test.ts (1 hunks)
  • apps/api/src/realtime/routes.ts (1 hunks)
  • apps/api/src/utils/message.ts (3 hunks)
  • apps/api/src/utils/websocket-connection.ts (2 hunks)
  • apps/api/src/ws/router.test.ts (11 hunks)
  • apps/api/src/ws/socket.test.ts (1 hunks)
  • apps/api/src/ws/socket.ts (1 hunks)
  • packages/core/src/store/messages-store.test.ts (4 hunks)
  • packages/core/src/store/seen-store.test.ts (1 hunks)
  • packages/core/src/store/typing-store.test.ts (1 hunks)
  • packages/types/src/realtime-events.ts (1 hunks)
✅ Files skipped from review due to trivial changes (1)
  • packages/core/src/store/typing-store.test.ts
🚧 Files skipped from review as they are similar to previous changes (1)
  • apps/api/src/realtime/routes.ts
🧰 Additional context used
📓 Path-based instructions (15)
**/*.{ts,tsx}

📄 CodeRabbit inference engine (.cursor/rules/global-rules.mdc)

**/*.{ts,tsx}: Manage shared component state via React Context
Implement full TypeScript support for all public APIs
Provide proper type definitions for all props and refs
Use appropriate generic constraints where necessary
Export type definitions for the public API
Avoid TypeScript enums; prefer const objects with 'as const' or Object.freeze
Provide clear, descriptive runtime errors for missing context/props and misuse
Expose dedicated hooks for context access that throw helpful errors when context is missing
Add explicit error messages for: outside-context usage, missing required props, invalid prop combinations, and validation failures
Document each component with JSDoc: purpose, props (required/optional), context requirements, example usage, pitfalls
State management: use React Context for global state, perform correct state updates, and handle side effects appropriately
Validation: implement runtime checks and validate prop combinations with clear errors
Create context with createContext<ContextType|null>(null) and a useContext hook that throws a clear error when missing

**/*.{ts,tsx}: Don't use primitive type aliases or misleading types
Don't use empty type parameters in type aliases and interfaces
Don't use any or unknown as type constraints
Don't have unused function parameters
Don't have unused private class members
Don't return a value from a function that has a 'void' return type
Don't use the TypeScript directive @ts-ignore
Ensure switch-case statements are exhaustive (especially with unions)
Don't use TypeScript enums
Don't add type annotations to variables, parameters, and class properties initialized with literal expressions
Don't use TypeScript namespaces
Don't use non-null assertions with the ! postfix operator
Don't use parameter properties in class constructors
Don't use user-defined types (where discouraged by policy)
Use as const instead of literal types and type annotations
Use either T[] or Array consistently
Use consistent accessibili...

Files:

  • apps/api/src/realtime/routes.test.ts
  • apps/api/src/ws/router.test.ts
  • packages/core/src/store/seen-store.test.ts
  • apps/api/src/utils/websocket-connection.ts
  • apps/api/src/ws/socket.ts
  • apps/api/src/ws/socket.test.ts
  • packages/core/src/store/messages-store.test.ts
  • apps/api/src/utils/message.ts
  • packages/types/src/realtime-events.ts
**/*.{test.ts,test.tsx,spec.ts,spec.tsx,test.js,test.jsx,spec.js,spec.jsx}

📄 CodeRabbit inference engine (.cursor/rules/global-rules.mdc)

Include tests: unit tests for components, integration tests for composed components, accessibility tests, and error boundary tests

Files:

  • apps/api/src/realtime/routes.test.ts
  • apps/api/src/ws/router.test.ts
  • packages/core/src/store/seen-store.test.ts
  • apps/api/src/ws/socket.test.ts
  • packages/core/src/store/messages-store.test.ts
**/*.{js,jsx,ts,tsx}

📄 CodeRabbit inference engine (.cursor/rules/ultracite.mdc)

**/*.{js,jsx,ts,tsx}: Don't use the accessKey attribute on any HTML element
Use correct ISO language/country codes for the lang attribute
Don't hardcode sensitive data like API keys and tokens
Use === and !==

Files:

  • apps/api/src/realtime/routes.test.ts
  • apps/api/src/ws/router.test.ts
  • packages/core/src/store/seen-store.test.ts
  • apps/api/src/utils/websocket-connection.ts
  • apps/api/src/ws/socket.ts
  • apps/api/src/ws/socket.test.ts
  • packages/core/src/store/messages-store.test.ts
  • apps/api/src/utils/message.ts
  • packages/types/src/realtime-events.ts
**/*.{js,ts}

📄 CodeRabbit inference engine (.cursor/rules/ultracite.mdc)

**/*.{js,ts}: Don't use consecutive spaces in regular expression literals
Don't use the arguments object
Don't use the comma operator
Don't use unnecessary boolean casts
Don't use unnecessary callbacks with flatMap
Prefer for...of statements instead of Array.forEach
Don't use this and super in static contexts
Don't use unnecessary catch clauses
Don't use unnecessary constructors
Don't use unnecessary continue statements
Don't export empty modules that don't change anything
Don't use unnecessary escape sequences in regular expression literals
Don't use unnecessary labels
Don't use unnecessary nested block statements
Don't rename imports, exports, and destructured assignments to the same name
Don't use unnecessary string or template literal concatenation
Don't use String.raw in template literals when there are no escape sequences
Don't use useless case statements in switch statements
Avoid ternary operators when simpler alternatives exist
Don't use useless this aliasing
Don't initialize variables to undefined
Don't use void operators
Use Date.now() to get milliseconds since the Unix Epoch
Prefer .flatMap() over map().flat() when possible
Use literal property access instead of computed property access
Don't use parseInt() or Number.parseInt() when numeric literals suffice (binary/octal/hex)
Use concise optional chaining instead of chained logical expressions
Prefer regular expression literals over the RegExp constructor when possible
Don't use non–base-10 or underscore-separated number literal object member names
Remove redundant terms from logical expressions
Prefer while loops over for loops when no initializer/update is needed
Don't reassign const variables
Don't use constant expressions in conditions
Don't use Math.min/Math.max to clamp values when result is constant
Don't use empty character classes in regular expression literals
Don't use empty destructuring patterns
Don't call global object properties as functions
Don't declare functions and vars that are acce...

Files:

  • apps/api/src/realtime/routes.test.ts
  • apps/api/src/ws/router.test.ts
  • packages/core/src/store/seen-store.test.ts
  • apps/api/src/utils/websocket-connection.ts
  • apps/api/src/ws/socket.ts
  • apps/api/src/ws/socket.test.ts
  • packages/core/src/store/messages-store.test.ts
  • apps/api/src/utils/message.ts
  • packages/types/src/realtime-events.ts
**/*.{test,spec}.{js,jsx,ts,tsx}

📄 CodeRabbit inference engine (.cursor/rules/ultracite.mdc)

**/*.{test,spec}.{js,jsx,ts,tsx}: Don't nest describe() blocks too deeply in test files
Don't use callbacks in asynchronous tests and hooks
Don't have duplicate hooks in describe blocks
Don't use export or module.exports in test files
Don't use focused tests
Ensure the assertion function (e.g., expect) is placed inside an it() function call
Don't use disabled tests

Files:

  • apps/api/src/realtime/routes.test.ts
  • apps/api/src/ws/router.test.ts
  • packages/core/src/store/seen-store.test.ts
  • apps/api/src/ws/socket.test.ts
  • packages/core/src/store/messages-store.test.ts
**/*.{js,ts,tsx}

📄 CodeRabbit inference engine (.cursor/rules/ultracite.mdc)

**/*.{js,ts,tsx}: Don't create classes that only have static members
Don't return a value from a constructor
Don't use super() incorrectly inside classes; ensure super() is called in derived constructors
Ensure super() is called exactly once on every code path in a class constructor before this is accessed if the class has a superclass
Place getters and setters for the same property next to each other in class and object definitions
Ensure JSDoc comment lines start with a single asterisk, except for the first one
Don't use duplicate class members

Files:

  • apps/api/src/realtime/routes.test.ts
  • apps/api/src/ws/router.test.ts
  • packages/core/src/store/seen-store.test.ts
  • apps/api/src/utils/websocket-connection.ts
  • apps/api/src/ws/socket.ts
  • apps/api/src/ws/socket.test.ts
  • packages/core/src/store/messages-store.test.ts
  • apps/api/src/utils/message.ts
  • packages/types/src/realtime-events.ts
**/*.{js,tsx,ts,jsx}

📄 CodeRabbit inference engine (.cursor/rules/ultracite.mdc)

Use arrow functions instead of function expressions

Files:

  • apps/api/src/realtime/routes.test.ts
  • apps/api/src/ws/router.test.ts
  • packages/core/src/store/seen-store.test.ts
  • apps/api/src/utils/websocket-connection.ts
  • apps/api/src/ws/socket.ts
  • apps/api/src/ws/socket.test.ts
  • packages/core/src/store/messages-store.test.ts
  • apps/api/src/utils/message.ts
  • packages/types/src/realtime-events.ts
**/*.{js,ts,jsx,tsx}

📄 CodeRabbit inference engine (.cursor/rules/ultracite.mdc)

**/*.{js,ts,jsx,tsx}: Prevent duplicate polyfills from Polyfill.io
Don't use irregular whitespace characters

Files:

  • apps/api/src/realtime/routes.test.ts
  • apps/api/src/ws/router.test.ts
  • packages/core/src/store/seen-store.test.ts
  • apps/api/src/utils/websocket-connection.ts
  • apps/api/src/ws/socket.ts
  • apps/api/src/ws/socket.test.ts
  • packages/core/src/store/messages-store.test.ts
  • apps/api/src/utils/message.ts
  • packages/types/src/realtime-events.ts
**/*.{js,ts,ts,tsx}

📄 CodeRabbit inference engine (.cursor/rules/ultracite.mdc)

Don't redeclare variables, functions, classes, and types in the same scope

Files:

  • apps/api/src/realtime/routes.test.ts
  • apps/api/src/ws/router.test.ts
  • packages/core/src/store/seen-store.test.ts
  • apps/api/src/utils/websocket-connection.ts
  • apps/api/src/ws/socket.ts
  • apps/api/src/ws/socket.test.ts
  • packages/core/src/store/messages-store.test.ts
  • apps/api/src/utils/message.ts
  • packages/types/src/realtime-events.ts
apps/api/src/**/*.test.ts

📄 CodeRabbit inference engine (AGENTS.md)

In API tests, reuse mocks from apps/api/src/utils where possible

Files:

  • apps/api/src/realtime/routes.test.ts
  • apps/api/src/ws/router.test.ts
  • apps/api/src/ws/socket.test.ts
apps/api/src/ws/**/*.{ts,tsx}

📄 CodeRabbit inference engine (AGENTS.md)

Place WebSocket handlers under apps/api/src/ws

Files:

  • apps/api/src/ws/router.test.ts
  • apps/api/src/ws/socket.ts
  • apps/api/src/ws/socket.test.ts
apps/api/src/{db,trpc,ws}/**/*.test.ts

📄 CodeRabbit inference engine (AGENTS.md)

Co-locate tests as *.test.ts next to DB, TRPC, and WebSocket source files

Files:

  • apps/api/src/ws/router.test.ts
  • apps/api/src/ws/socket.test.ts
packages/**

📄 CodeRabbit inference engine (AGENTS.md)

Put shared contracts and utilities in packages/* (e.g., core, types, transactional)

Files:

  • packages/core/src/store/seen-store.test.ts
  • packages/core/src/store/messages-store.test.ts
  • packages/types/src/realtime-events.ts
packages/types/src/**/*.ts

📄 CodeRabbit inference engine (CLAUDE.md)

packages/types/src/**/*.ts: Keep shared TypeScript types and Zod schemas in packages/types/src/ and update them alongside schema changes
Ensure Zod schemas mirror any API/database shape changes for end-to-end type safety

Files:

  • packages/types/src/realtime-events.ts
packages/types/**/*.{ts,tsx}

📄 CodeRabbit inference engine (AGENTS.md)

Surface shared DTOs in packages/types

Files:

  • packages/types/src/realtime-events.ts
🧬 Code graph analysis (6)
apps/api/src/realtime/routes.test.ts (2)
apps/api/src/realtime/routes.ts (2)
  • isMessageCreatedUserEvent (19-39)
  • matchesVisitorMessageSubscription (41-60)
packages/types/src/realtime-events.ts (1)
  • RealtimeEvent (125-132)
apps/api/src/ws/router.test.ts (1)
packages/types/src/realtime-events.ts (1)
  • RealtimeEvent (125-132)
packages/core/src/store/seen-store.test.ts (1)
packages/types/src/realtime-events.ts (1)
  • RealtimeEvent (125-132)
apps/api/src/ws/socket.ts (2)
packages/types/src/realtime-events.ts (1)
  • RealtimeEvent (125-132)
apps/api/src/ws/router.ts (1)
  • routeEvent (254-280)
apps/api/src/utils/message.ts (3)
apps/api/src/db/schema/conversation.ts (1)
  • message (167-244)
packages/types/src/schemas.ts (1)
  • Message (25-25)
packages/types/src/realtime-events.ts (1)
  • RealtimeEventData (134-135)
packages/types/src/realtime-events.ts (2)
packages/types/src/schemas.ts (1)
  • conversationSchema (41-56)
packages/types/src/trpc/conversation.ts (1)
  • conversationHeaderSchema (63-87)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: API Tests
🔇 Additional comments (1)
packages/core/src/store/seen-store.test.ts (1)

97-112: Verify the CONVERSATION_SEEN payload schema
Run the following to confirm which fields the CONVERSATION_SEEN z.object actually defines in RealtimeEvents:

rg -n 'CONVERSATION_SEEN:' -A12 packages/types/src/realtime-events.ts

Comment on lines +60 to +69
CONVERSATION_TYPING: realtimeEventMetadataSchema.merge(
z.object({
conversationId: z.string(),
organizationId: z.string(),
type: z.string(),
actorUserId: z.string().nullable(),
actorAiAgentId: z.string().nullable(),
}),
}),
MESSAGE_CREATED: z.object({
message: z.object({
id: z.string(),
bodyMd: z.string(),
type: z.string(),
userId: z.string().nullable(),
visitorId: z.string().nullable(),
organizationId: z.string(),
websiteId: z.string(),
aiAgentId: z.string().nullable(),
isTyping: z.boolean(),
visitorPreview: z.string().max(2000).nullable().optional(),
})
),

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Restore actorType/actorId on typing events

CONVERSATION_TYPING lost the actorType/actorId fields. We still emit them (see apps/api/src/ws/router.test.ts and routing logic), and consumers rely on the discriminator to know whether a user, visitor, or agent is typing. With this schema the validator strips those properties and the inferred type no longer exposes them, so downstream code loses that context. Please add the fields back so validation and typings stay aligned with the emitted data.

 	CONVERSATION_TYPING: realtimeEventMetadataSchema.merge(
 		z.object({
 			conversationId: z.string(),
-			userId: z.string().nullable(),
-			visitorId: z.string().nullable(),
-			aiAgentId: z.string().nullable(),
+			actorType: z.enum(["user", "visitor", "ai_agent"]),
+			actorId: z.string(),
+			userId: z.string().nullable(),
+			visitorId: z.string().nullable(),
+			aiAgentId: z.string().nullable(),
 			isTyping: z.boolean(),
 			visitorPreview: z.string().max(2000).nullable().optional(),
 		})
 	),
🤖 Prompt for AI Agents
In packages/types/src/realtime-events.ts around lines 60 to 69, the
CONVERSATION_TYPING schema removed the actorType/actorId discriminator so
emitted typing events get stripped of who is typing; restore these fields by
adding actorType as a discriminant (e.g. z.enum or z.union of the literal
strings used elsewhere such as 'user', 'visitor', 'aiAgent') and actorId as
z.string().nullable() (matching how user/visitor/agent ids are represented),
making them required on the schema so validation and generated types include the
actor context.

This branch had an error being deployed

1 failed deployment
Preview — e9552d94 Deployed Oct 9, 2025 by vercel[bot]
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant