-
Notifications
You must be signed in to change notification settings - Fork 47
joel/inngest realtime #581
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Conversation
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
|
Note Other AI code review bot(s) detectedCodeRabbit has detected other AI code review bot(s) in this pull request and will avoid duplicating their findings in the review comments. This may lead to a less comprehensive review. WalkthroughAdds Inngest Realtime support and feature-flagged realtime video events across server and client: middleware wiring, a realtime channel schema, conditional publish paths in core video handlers, client subscription hooks and token action, removal of PartyKit onConnect logic, and dependency bumps (inngest and @inngest/realtime). Adds planning doc and changesets. Changes
Sequence Diagram(s)sequenceDiagram
autonumber
actor U as User (Browser)
participant C as React Component
participant H as useVideoRealtimeSubscription
participant SA as fetchRealtimeVideoToken (server)
participant RT as Inngest Realtime
participant IF as Inngest Function(s)
Note over C,U: Flag: NEXT_PUBLIC_ENABLE_REALTIME_VIDEO_UPLOAD=true
U->>C: Load page (video resource)
C->>H: Init subscription (roomId, enabled)
H->>SA: Request token for roomId
SA->>RT: getSubscriptionToken(videoChannel(roomId), topics:['status'])
RT-->>SA: Token
SA-->>H: Token
H->>RT: Subscribe to channel topic: status
par Processing
IF->>RT: publish(videoChannel(roomId).status(payload))
and Fallback
IF->>C: partyProvider.broadcastMessage(payload) (when realtime disabled/unavailable)
end
RT-->>H: status event (payload)
H-->>C: latestData update
C->>C: React effect handles payload (refetch/refresh/update)
sequenceDiagram
autonumber
participant IF as Inngest Function
participant RM as realtimeMiddleware
participant PUB as publish
participant RT as Inngest Realtime
IF->>RM: Handler invoked with context
RM-->>IF: Injects publish()
IF->>IF: Build payload {name, body, requestId}
alt realtimeEnabled && publish
IF->>PUB: videoChannel(roomId).status(payload)
PUB->>RT: send
else
IF->>IF: partyProvider.broadcastMessage(payload)
end
Estimated code review effort🎯 3 (Moderate) | ⏱️ ~25 minutes Possibly related PRs
Suggested reviewers
Poem
Pre-merge checks and finishing touches✅ Passed checks (3 passed)
✨ Finishing touches
🧪 Generate unit tests (beta)
📜 Recent review detailsConfiguration used: CodeRabbit UI Review profile: CHILL Plan: Pro 📒 Files selected for processing (1)
🧰 Additional context used📓 Path-based instructions (5)**/*.{js,jsx,ts,tsx}📄 CodeRabbit inference engine (.cursorrules)
Files:
**/*.{ts,tsx,js,jsx}📄 CodeRabbit inference engine (CLAUDE.md)
Files:
apps/**/*📄 CodeRabbit inference engine (.cursor/rules/monorepo-setup.mdc)
Files:
apps/ai-hero/**/*.{ts,tsx}📄 CodeRabbit inference engine (apps/ai-hero/.cursor/rules/analytics.mdc)
Files:
apps/ai-hero/**/*.tsx📄 CodeRabbit inference engine (apps/ai-hero/.cursor/rules/component-system.mdc)
Files:
⏰ 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). (6)
🔇 Additional comments (2)
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. Comment |
|
Review the following changes in direct dependencies. Learn more about Socket for GitHub.
|
|
All alerts resolved. Learn more about Socket for GitHub. This PR previously contained dependency changes with security issues that have been resolved, removed, or ignored. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 13
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
packages/core/src/inngest/video-processing/functions/generate-transcript-with-screnshots.ts (1)
1-107: Extract duplicated announcement pattern into a shared helper.The pattern of constructing a payload, checking the realtime flag, and conditionally publishing or broadcasting is duplicated across video-uploaded.ts, video-processing-error.ts, and this file. This violates the DRY principle.
Consider creating a shared helper function in a utilities file:
// packages/core/src/inngest/video-processing/utils/announce-status.ts import { videoChannel } from '../realtime' import type { PartyProvider } from '../../types' const realtimeEnabled = process.env.NEXT_PUBLIC_ENABLE_REALTIME_VIDEO_UPLOAD === 'true' export async function announceVideoStatus( params: { roomId: string payload: { name: string body: any requestId: string } publish?: any partyProvider: PartyProvider } ) { const { roomId, payload, publish, partyProvider } = params if (realtimeEnabled && publish) { await publish(videoChannel(roomId).status(payload)) } else { await partyProvider.broadcastMessage({ body: payload, roomId, }) } }Then use it in each handler:
await step.run('announce video resource created', async () => { await announceVideoStatus({ roomId: videoResource.id, payload: { name: 'videoResource.created', body: videoResource, requestId: videoResource.id, }, publish, partyProvider, }) })
🧹 Nitpick comments (5)
apps/code-with-antonio/src/app/actions/realtime.ts (2)
3-7: Reorder imports: 3rd‑party before internalImport order should be React → Next → 3rd party → internal. Move @inngest/realtime above internal paths. As per coding guidelines.
-'use server' - -import { inngest } from '@/inngest/inngest.server' -import { getSubscriptionToken, type Realtime } from '@inngest/realtime' - -import { videoChannel } from '@coursebuilder/core/inngest/video-processing/realtime' +'use server' + +import { getSubscriptionToken, type Realtime } from '@inngest/realtime' +import { inngest } from '@/inngest/inngest.server' +import { videoChannel } from '@coursebuilder/core/inngest/video-processing/realtime'
10-13: Add JSDoc to exported server actionDocument params/return and auth expectation. As per coding guidelines.
+/** + * Fetches a scoped realtime subscription token for the given video room. + * Requires the caller to be authorized to access the room. + * @param roomId string Room identifier (e.g. video ID) + * @returns Realtime token scoped to ['status'] topic + */ export async function fetchRealtimeVideoToken( roomId: string, ): Promise<VideoRealtimeToken> {apps/code-with-antonio/src/components/party.tsx (1)
9-11: Add JSDoc to exported componentPer guidelines, document the component and props.
+/** + * Subscribes to video realtime updates and refreshes the page on relevant events. + * @param room optional room ID used to scope the subscription + */ export function Party({ room }: { room?: string }) {packages/core/src/inngest/video-processing/functions/video-processing-error.ts (1)
56-63: Eliminate payload restructuring in fallback path.The payload object is already constructed with the correct structure (lines 46-50), but the fallback path unnecessarily destructures and reconstructs it (lines 58-60).
Apply this diff to pass the payload directly:
await partyProvider.broadcastMessage({ - body: { - body: payload.body, - requestId: payload.requestId, - name: payload.name, - }, + body: payload, roomId, })packages/core/src/inngest/video-processing/functions/video-uploaded.ts (1)
19-20: Use a server-only env var for realtime video upload in Inngest functions
Replace all instances ofprocess.env.NEXT_PUBLIC_ENABLE_REALTIME_VIDEO_UPLOADinpackages/core/src/inngest/video-processing/functions/*with a non-public variable (e.g.ENABLE_REALTIME_VIDEO_UPLOAD) to decouple backend feature flags from client-exposed prefixes.
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
⛔ Files ignored due to path filters (1)
pnpm-lock.yamlis excluded by!**/pnpm-lock.yaml
📒 Files selected for processing (32)
.changeset/config.json(1 hunks).changeset/purple-doors-attend.md(1 hunks)apps/ai-hero/package.json(1 hunks)apps/astro-party/package.json(1 hunks)apps/code-with-antonio/.env.development(1 hunks)apps/code-with-antonio/package.json(2 hunks)apps/code-with-antonio/party/index.ts(0 hunks)apps/code-with-antonio/src/app/(content)/posts/_components/standalone-video-resource-uploader-and-viewer.tsx(2 hunks)apps/code-with-antonio/src/app/actions/realtime.ts(1 hunks)apps/code-with-antonio/src/components/content/content-video-resource-field.tsx(2 hunks)apps/code-with-antonio/src/components/party.tsx(1 hunks)apps/code-with-antonio/src/hooks/use-video-realtime.ts(1 hunks)apps/code-with-antonio/src/inngest/inngest.server.ts(2 hunks)apps/course-builder-web/package.json(1 hunks)apps/craft-of-ui/package.json(1 hunks)apps/cursor-pro/package.json(1 hunks)apps/egghead/package.json(1 hunks)apps/epic-react/package.json(1 hunks)apps/epic-web/package.json(1 hunks)apps/epicdev-ai/package.json(1 hunks)apps/go-local-first/package.json(1 hunks)cli/template-app-with-posts/package.json(1 hunks)packages/core/package.json(2 hunks)packages/core/src/inngest/create-inngest-middleware.ts(2 hunks)packages/core/src/inngest/video-processing/functions/generate-transcript-with-screnshots.ts(3 hunks)packages/core/src/inngest/video-processing/functions/transcript-ready.ts(3 hunks)packages/core/src/inngest/video-processing/functions/video-processing-error.ts(3 hunks)packages/core/src/inngest/video-processing/functions/video-ready.ts(3 hunks)packages/core/src/inngest/video-processing/functions/video-uploaded.ts(2 hunks)packages/core/src/inngest/video-processing/realtime.ts(1 hunks)packages/next/package.json(1 hunks)plans/partykit-to-inngest-realtime.md(1 hunks)
💤 Files with no reviewable changes (1)
- apps/code-with-antonio/party/index.ts
🧰 Additional context used
📓 Path-based instructions (7)
**/*.{js,jsx,ts,tsx}
📄 CodeRabbit inference engine (.cursorrules)
Add JS Doc comments to functions and React components
Files:
packages/core/src/inngest/video-processing/realtime.tspackages/core/src/inngest/create-inngest-middleware.tsapps/code-with-antonio/src/components/content/content-video-resource-field.tsxpackages/core/src/inngest/video-processing/functions/video-uploaded.tsapps/code-with-antonio/src/app/actions/realtime.tsapps/code-with-antonio/src/app/(content)/posts/_components/standalone-video-resource-uploader-and-viewer.tsxapps/code-with-antonio/src/hooks/use-video-realtime.tspackages/core/src/inngest/video-processing/functions/video-ready.tspackages/core/src/inngest/video-processing/functions/video-processing-error.tsapps/code-with-antonio/src/components/party.tsxpackages/core/src/inngest/video-processing/functions/transcript-ready.tspackages/core/src/inngest/video-processing/functions/generate-transcript-with-screnshots.tsapps/code-with-antonio/src/inngest/inngest.server.ts
**/*.{ts,tsx,js,jsx}
📄 CodeRabbit inference engine (CLAUDE.md)
**/*.{ts,tsx,js,jsx}: Use single quotes, no semicolons, tabs (width: 2), and an 80 character line limit for code formatting.
Organize imports in the following order: React → Next → 3rd party → internal.
Do NOT use Object.defineProperty(exports, ...) for re-exports. Use standard export patterns instead to avoid conflicts with framework internals.
Files:
packages/core/src/inngest/video-processing/realtime.tspackages/core/src/inngest/create-inngest-middleware.tsapps/code-with-antonio/src/components/content/content-video-resource-field.tsxpackages/core/src/inngest/video-processing/functions/video-uploaded.tsapps/code-with-antonio/src/app/actions/realtime.tsapps/code-with-antonio/src/app/(content)/posts/_components/standalone-video-resource-uploader-and-viewer.tsxapps/code-with-antonio/src/hooks/use-video-realtime.tspackages/core/src/inngest/video-processing/functions/video-ready.tspackages/core/src/inngest/video-processing/functions/video-processing-error.tsapps/code-with-antonio/src/components/party.tsxpackages/core/src/inngest/video-processing/functions/transcript-ready.tspackages/core/src/inngest/video-processing/functions/generate-transcript-with-screnshots.tsapps/code-with-antonio/src/inngest/inngest.server.ts
packages/**/*
📄 CodeRabbit inference engine (.cursor/rules/monorepo-setup.mdc)
Packages are located in the /packages directory
Files:
packages/core/src/inngest/video-processing/realtime.tspackages/core/src/inngest/create-inngest-middleware.tspackages/core/src/inngest/video-processing/functions/video-uploaded.tspackages/core/src/inngest/video-processing/functions/video-ready.tspackages/core/src/inngest/video-processing/functions/video-processing-error.tspackages/core/package.jsonpackages/core/src/inngest/video-processing/functions/transcript-ready.tspackages/core/src/inngest/video-processing/functions/generate-transcript-with-screnshots.tspackages/next/package.json
**/package.json
📄 CodeRabbit inference engine (CLAUDE.md)
**/package.json: When adding dependencies to package.json files, ensure all packages use consistent dependency versions and dependencies are sorted alphabetically.
When updating package.json files to add dependencies, use string replacement to add dependencies and maintain alphabetical order. Do not replace entire sections, just add the new line.
Files:
apps/course-builder-web/package.jsonapps/craft-of-ui/package.jsonapps/ai-hero/package.jsonapps/go-local-first/package.jsonapps/astro-party/package.jsonapps/epicdev-ai/package.jsonapps/code-with-antonio/package.jsonpackages/core/package.jsonapps/epic-react/package.jsonapps/epic-web/package.jsonapps/egghead/package.jsoncli/template-app-with-posts/package.jsonapps/cursor-pro/package.jsonpackages/next/package.json
apps/**/*
📄 CodeRabbit inference engine (.cursor/rules/monorepo-setup.mdc)
Apps are located in the /apps directory
Files:
apps/course-builder-web/package.jsonapps/craft-of-ui/package.jsonapps/ai-hero/package.jsonapps/go-local-first/package.jsonapps/astro-party/package.jsonapps/epicdev-ai/package.jsonapps/code-with-antonio/src/components/content/content-video-resource-field.tsxapps/code-with-antonio/src/app/actions/realtime.tsapps/code-with-antonio/src/app/(content)/posts/_components/standalone-video-resource-uploader-and-viewer.tsxapps/code-with-antonio/src/hooks/use-video-realtime.tsapps/code-with-antonio/package.jsonapps/epic-react/package.jsonapps/code-with-antonio/src/components/party.tsxapps/epic-web/package.jsonapps/egghead/package.jsonapps/cursor-pro/package.jsonapps/code-with-antonio/src/inngest/inngest.server.ts
apps/code-with-antonio/**/*.{ts,tsx}
📄 CodeRabbit inference engine (apps/code-with-antonio/.cursor/rules/analytics.mdc)
apps/code-with-antonio/**/*.{ts,tsx}: Only usetrackfrom@/utils/analytics.tsfor learner/customer activity (course progress, video watching, exercise completion, purchase activity, user preferences)
Do not usetrackfor internal admin actions, content management, system operations, or backend processesAlways use kebab-case when naming TypeScript and TSX files
apps/code-with-antonio/**/*.{ts,tsx}: Add JSDoc comments to exported methods/functions in TypeScript
Add JSDoc comments to exported React components
Files:
apps/code-with-antonio/src/components/content/content-video-resource-field.tsxapps/code-with-antonio/src/app/actions/realtime.tsapps/code-with-antonio/src/app/(content)/posts/_components/standalone-video-resource-uploader-and-viewer.tsxapps/code-with-antonio/src/hooks/use-video-realtime.tsapps/code-with-antonio/src/components/party.tsxapps/code-with-antonio/src/inngest/inngest.server.ts
apps/code-with-antonio/**/*.tsx
📄 CodeRabbit inference engine (apps/code-with-antonio/.cursor/rules/component-system.mdc)
apps/code-with-antonio/**/*.tsx: Use base React components from the shared UI library instead of creating custom implementations
Prefer Radix/shadcn-based primitives exposed by /packages/ui (e.g., Dialog, Tooltip, Tabs) rather than importing Radix directly or using raw primitives
Before adding a new component, check /packages/ui for an existing base component to avoid duplication
Files:
apps/code-with-antonio/src/components/content/content-video-resource-field.tsxapps/code-with-antonio/src/app/(content)/posts/_components/standalone-video-resource-uploader-and-viewer.tsxapps/code-with-antonio/src/components/party.tsx
🧬 Code graph analysis (10)
apps/code-with-antonio/src/components/content/content-video-resource-field.tsx (2)
apps/code-with-antonio/src/hooks/use-video-realtime.ts (1)
useVideoRealtimeSubscription(10-32)apps/code-with-antonio/src/app/actions/realtime.ts (1)
fetchRealtimeVideoToken(10-21)
packages/core/src/inngest/video-processing/functions/video-uploaded.ts (2)
packages/core/src/inngest/create-inngest-middleware.ts (2)
CoreInngestTrigger(51-53)CoreInngestHandler(54-54)packages/core/src/inngest/video-processing/realtime.ts (1)
videoChannel(18-20)
apps/code-with-antonio/src/app/actions/realtime.ts (1)
packages/core/src/inngest/video-processing/realtime.ts (1)
videoChannel(18-20)
apps/code-with-antonio/src/app/(content)/posts/_components/standalone-video-resource-uploader-and-viewer.tsx (2)
apps/code-with-antonio/src/hooks/use-video-realtime.ts (1)
useVideoRealtimeSubscription(10-32)apps/code-with-antonio/src/app/actions/realtime.ts (1)
fetchRealtimeVideoToken(10-21)
apps/code-with-antonio/src/hooks/use-video-realtime.ts (2)
apps/code-with-antonio/src/app/actions/realtime.ts (1)
VideoRealtimeToken(8-8)packages/core/src/inngest/video-processing/realtime.ts (1)
videoChannel(18-20)
packages/core/src/inngest/video-processing/functions/video-ready.ts (2)
packages/core/src/inngest/create-inngest-middleware.ts (1)
CoreInngestHandler(54-54)packages/core/src/inngest/video-processing/realtime.ts (1)
videoChannel(18-20)
packages/core/src/inngest/video-processing/functions/video-processing-error.ts (2)
packages/core/src/inngest/create-inngest-middleware.ts (1)
CoreInngestHandler(54-54)packages/core/src/inngest/video-processing/realtime.ts (1)
videoChannel(18-20)
apps/code-with-antonio/src/components/party.tsx (2)
apps/code-with-antonio/src/hooks/use-video-realtime.ts (1)
useVideoRealtimeSubscription(10-32)apps/code-with-antonio/src/app/actions/realtime.ts (1)
fetchRealtimeVideoToken(10-21)
packages/core/src/inngest/video-processing/functions/transcript-ready.ts (2)
packages/core/src/inngest/create-inngest-middleware.ts (1)
CoreInngestHandler(54-54)packages/core/src/inngest/video-processing/realtime.ts (1)
videoChannel(18-20)
packages/core/src/inngest/video-processing/functions/generate-transcript-with-screnshots.ts (2)
packages/core/src/inngest/create-inngest-middleware.ts (1)
CoreInngestFunctionInput(50-50)packages/core/src/inngest/video-processing/realtime.ts (1)
videoChannel(18-20)
🪛 dotenv-linter (3.3.0)
apps/code-with-antonio/.env.development
[warning] 12-12: [QuoteCharacter] The value has quote characters (', ")
(QuoteCharacter)
[warning] 12-12: [UnorderedKey] The NEXT_PUBLIC_ENABLE_REALTIME_VIDEO_UPLOAD key should go before the NEXT_PUBLIC_PARTNER_FIRST_NAME key
(UnorderedKey)
⏰ 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). (6)
- GitHub Check: format
- GitHub Check: typecheck
- GitHub Check: lint
- GitHub Check: build
- GitHub Check: e2e-node (course-builder-web)
- GitHub Check: Cursor Bugbot
🔇 Additional comments (11)
.changeset/config.json (1)
28-29: LGTM!The addition of "code-with-antonio" to the ignore list is properly formatted and aligns with the PR's realtime integration work.
.changeset/purple-doors-attend.md (1)
1-7: LGTM!The changeset properly documents the inngest realtime addition with appropriate patch-level version bumps for the affected packages.
apps/go-local-first/package.json (1)
155-155: LGTM!The inngest version bump to 3.44.2 is consistent with the broader upgrade across the monorepo and maintains alphabetical ordering.
apps/epic-react/package.json (1)
71-71: LGTM!The inngest version upgrade is consistent with other packages in the monorepo and maintains proper dependency ordering.
packages/core/src/inngest/create-inngest-middleware.ts (2)
88-88: LGTM! Middleware ordering is correct.The realtime middleware is properly positioned before the custom middleware, which follows the correct initialization order for Inngest middleware composition.
3-3: Approve import of realtimeMiddleware
@inngest/realtimeis listed at^0.4.4in packages/core/package.json; no further changes needed.apps/epic-web/package.json (1)
143-143: LGTM!The inngest dependency update is consistent with the monorepo-wide upgrade to 3.44.2 and maintains alphabetical ordering.
apps/epicdev-ai/package.json (1)
146-146: LGTM!The inngest version update aligns with the monorepo-wide upgrade and maintains proper dependency ordering.
apps/code-with-antonio/src/inngest/inngest.server.ts (2)
157-157: LGTM! Middleware configuration is correct.The realtime middleware is properly integrated alongside the existing custom middleware, enabling real-time capabilities for the Inngest client.
46-46: @inngest/realtime dependency verified: version ^0.4.4 is present in apps/code-with-antonio/package.json.apps/code-with-antonio/src/components/party.tsx (1)
17-21: Verify calling a Server Action from a Client component is supported in your Next versionImporting and invoking a 'use server' action as refreshToken from a Client component should work on modern Next (server actions RPC). If you see bundler/runtime errors, switch to a GET /api/realtime/token route and call that instead.
Run locally and ensure no build/runtime errors are thrown from this import/call. If issues arise, I can provide an /api route version.
| NEXT_PUBLIC_ENABLE_REALTIME_VIDEO_UPLOAD="true" | ||
|
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Drop quotes and add server flag for consistency
Remove quotes per dotenv best practices, and consider also setting the server flag to keep parity with core handlers.
-NEXT_PUBLIC_ENABLE_REALTIME_VIDEO_UPLOAD="true"
+NEXT_PUBLIC_ENABLE_REALTIME_VIDEO_UPLOAD=true
+# Server-side gate used by Inngest/functions
+# ENABLE_REALTIME_VIDEO_UPLOAD=trueOptional: add INNGEST_REALTIME_SIGNING_KEY placeholder here (do not commit secrets).
📝 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.
| NEXT_PUBLIC_ENABLE_REALTIME_VIDEO_UPLOAD="true" | |
| NEXT_PUBLIC_ENABLE_REALTIME_VIDEO_UPLOAD=true | |
| # Server-side gate used by Inngest/functions | |
| # ENABLE_REALTIME_VIDEO_UPLOAD=true |
🧰 Tools
🪛 dotenv-linter (3.3.0)
[warning] 12-12: [QuoteCharacter] The value has quote characters (', ")
(QuoteCharacter)
[warning] 12-12: [UnorderedKey] The NEXT_PUBLIC_ENABLE_REALTIME_VIDEO_UPLOAD key should go before the NEXT_PUBLIC_PARTNER_FIRST_NAME key
(UnorderedKey)
🤖 Prompt for AI Agents
In apps/code-with-antonio/.env.development around lines 12-13, remove the
surrounding quotes from NEXT_PUBLIC_ENABLE_REALTIME_VIDEO_UPLOAD so it becomes
an unquoted boolean token per dotenv best practices, add a matching server-side
flag (e.g., NEXT_PUBLIC_ENABLE_REALTIME_VIDEO_UPLOAD_SERVER=true) on the next
line to keep parity with core handlers, and optionally add an
INNGEST_REALTIME_SIGNING_KEY=placeholder line (do not commit secrets).
| export async function fetchRealtimeVideoToken( | ||
| roomId: string, | ||
| ): Promise<VideoRealtimeToken> { | ||
| if (!roomId) { | ||
| throw new Error('roomId is required to fetch realtime token') | ||
| } | ||
|
|
||
| return await getSubscriptionToken(inngest, { | ||
| channel: videoChannel(roomId), | ||
| topics: ['status'], | ||
| }) | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Add an authorization check for room access before issuing tokens
Any client can request a token for any roomId. Validate the caller is authorized to subscribe to that room (ownership/membership). Otherwise this is a data exposure risk.
Example minimal guard (replace with your auth primitives):
export async function fetchRealtimeVideoToken(
roomId: string,
): Promise<VideoRealtimeToken> {
if (!roomId) {
throw new Error('roomId is required to fetch realtime token')
}
+ // TODO: replace with your actual auth & authorization checks
+ const user = await getCurrentUser() // e.g. from your auth layer
+ const allowed = user && (await canAccessVideoRoom(user.id, roomId))
+ if (!allowed) {
+ throw new Error('unauthorized')
+ }
return await getSubscriptionToken(inngest, {
channel: videoChannel(roomId),
topics: ['status'],
})
}Committable suggestion skipped: line range outside the PR's diff.
🤖 Prompt for AI Agents
In apps/code-with-antonio/src/app/actions/realtime.ts around lines 10 to 21, the
function issues realtime tokens for any roomId without verifying the caller; add
an authorization guard that fetches the current user/session from your auth
context, validate that the user is owner or a member of the room (e.g., DB
lookup on room membership or ownership), and if not authorized throw an
Unauthorized error or return a 403 response; only call getSubscriptionToken
after the membership check passes, and optionally log unauthorized token
requests for auditing.
| useMemo(() => { | ||
| if (!realtimeEnabled || !subscription?.latestData) return | ||
|
|
||
| const message = subscription.latestData | ||
| const name = message.data?.name | ||
|
|
||
| const invalidateOn = new Set([ | ||
| 'videoResource.created', | ||
| 'video.asset.ready', | ||
| 'transcript.ready', | ||
| 'ai.tip.draft.completed', | ||
| 'video.asset.detached', | ||
| 'video.asset.attached', | ||
| ]) | ||
|
|
||
| if (name && invalidateOn.has(name)) { | ||
| router.refresh() | ||
| } | ||
| }, [subscription?.latestData, realtimeEnabled, router]) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Use useEffect for side effects; avoid useMemo here
router.refresh is a side effect. useMemo is not for effects and may be skipped. Use useEffect with the same deps.
- useMemo(() => {
+ useEffect(() => {
if (!realtimeEnabled || !subscription?.latestData) return
const message = subscription.latestData
const name = message.data?.name
const invalidateOn = new Set([
'videoResource.created',
'video.asset.ready',
'transcript.ready',
'ai.tip.draft.completed',
'video.asset.detached',
'video.asset.attached',
])
if (name && invalidateOn.has(name)) {
router.refresh()
}
- }, [subscription?.latestData, realtimeEnabled, router])
+ }, [subscription?.latestData, realtimeEnabled, router])Optional: hoist invalidateOn Set outside the component to avoid re‑allocations.
📝 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.
| useMemo(() => { | |
| if (!realtimeEnabled || !subscription?.latestData) return | |
| const message = subscription.latestData | |
| const name = message.data?.name | |
| const invalidateOn = new Set([ | |
| 'videoResource.created', | |
| 'video.asset.ready', | |
| 'transcript.ready', | |
| 'ai.tip.draft.completed', | |
| 'video.asset.detached', | |
| 'video.asset.attached', | |
| ]) | |
| if (name && invalidateOn.has(name)) { | |
| router.refresh() | |
| } | |
| }, [subscription?.latestData, realtimeEnabled, router]) | |
| useEffect(() => { | |
| if (!realtimeEnabled || !subscription?.latestData) return | |
| const message = subscription.latestData | |
| const name = message.data?.name | |
| const invalidateOn = new Set([ | |
| 'videoResource.created', | |
| 'video.asset.ready', | |
| 'transcript.ready', | |
| 'ai.tip.draft.completed', | |
| 'video.asset.detached', | |
| 'video.asset.attached', | |
| ]) | |
| if (name && invalidateOn.has(name)) { | |
| router.refresh() | |
| } | |
| }, [subscription?.latestData, realtimeEnabled, router]) |
🤖 Prompt for AI Agents
In apps/code-with-antonio/src/components/party.tsx around lines 23 to 41,
replace the useMemo block with a useEffect so router.refresh is executed as a
side effect rather than memoized; keep the same dependency array
([subscription?.latestData, realtimeEnabled, router]), read
subscription.latestData inside the effect, perform the same early return when
realtimeEnabled is false or latestData is missing, check the message.data?.name
against the invalidateOn set and call router.refresh() when matched, and
optionally hoist the invalidateOn Set definition outside the component to avoid
re-allocations on each render.
| export function useVideoRealtimeSubscription({ | ||
| room, | ||
| refreshToken, | ||
| enabled, | ||
| }: { | ||
| room?: string | null | ||
| refreshToken: RefreshTokenFn | ||
| enabled?: boolean | ||
| }) { | ||
| const finalRoom = room ?? '' | ||
| const realtimeEnabled = enabled && Boolean(finalRoom) | ||
|
|
||
| const subscription = useInngestSubscription({ | ||
| enabled: realtimeEnabled, | ||
| refreshToken: async () => { | ||
| return refreshToken(finalRoom) | ||
| }, | ||
| channel: () => videoChannel(finalRoom), | ||
| topics: ['status'], | ||
| }) | ||
|
|
||
| return subscription | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🛠️ Refactor suggestion | 🟠 Major
Add required JSDoc for exported hook
Per the project coding guidelines, every exported function in TS/TSX files must include a JSDoc block. Please document the hook’s purpose and parameters before the export so we stay compliant.
+/**
+ * Subscribes to realtime video status updates for the given room.
+ *
+ * @param room - Video room identifier used to scope the subscription.
+ * @param refreshToken - Callback that fetches a fresh realtime token for the room.
+ * @param enabled - Optional flag to toggle the subscription.
+ */
export function useVideoRealtimeSubscription({As per coding guidelines
📝 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.
| export function useVideoRealtimeSubscription({ | |
| room, | |
| refreshToken, | |
| enabled, | |
| }: { | |
| room?: string | null | |
| refreshToken: RefreshTokenFn | |
| enabled?: boolean | |
| }) { | |
| const finalRoom = room ?? '' | |
| const realtimeEnabled = enabled && Boolean(finalRoom) | |
| const subscription = useInngestSubscription({ | |
| enabled: realtimeEnabled, | |
| refreshToken: async () => { | |
| return refreshToken(finalRoom) | |
| }, | |
| channel: () => videoChannel(finalRoom), | |
| topics: ['status'], | |
| }) | |
| return subscription | |
| } | |
| /** | |
| * Subscribes to realtime video status updates for the given room. | |
| * | |
| * @param room - Video room identifier used to scope the subscription. | |
| * @param refreshToken - Callback that fetches a fresh realtime token for the room. | |
| * @param enabled - Optional flag to toggle the subscription. | |
| */ | |
| export function useVideoRealtimeSubscription({ | |
| room, | |
| refreshToken, | |
| enabled, | |
| }: { | |
| room?: string | null | |
| refreshToken: RefreshTokenFn | |
| enabled?: boolean | |
| }) { | |
| const finalRoom = room ?? '' | |
| const realtimeEnabled = enabled && Boolean(finalRoom) | |
| const subscription = useInngestSubscription({ | |
| enabled: realtimeEnabled, | |
| refreshToken: async () => { | |
| return refreshToken(finalRoom) | |
| }, | |
| channel: () => videoChannel(finalRoom), | |
| topics: ['status'], | |
| }) | |
| return subscription | |
| } |
🤖 Prompt for AI Agents
In apps/code-with-antonio/src/hooks/use-video-realtime.ts around lines 10 to 32,
the exported hook is missing the required JSDoc block; add a JSDoc comment
immediately above the export that briefly describes the hook’s purpose,
documents each parameter (room, refreshToken, enabled) including types/behavior
(room can be string|null, refreshToken is a RefreshTokenFn used to fetch a token
for the finalRoom, enabled toggles realtime subscription), and documents the
return value (the subscription object from useInngestSubscription). Keep the
JSDoc concise and follow project style (description line, @param for each param,
and @returns).
| export const generateTranscriptWithScreenshotsHandler: CoreInngestHandler = | ||
| async ({ event, step, db, partyProvider }: CoreInngestFunctionInput) => { | ||
| async ({ | ||
| event, | ||
| step, | ||
| db, | ||
| partyProvider, | ||
| publish, | ||
| }: CoreInngestFunctionInput) => { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🛠️ Refactor suggestion | 🟠 Major
Add JSDoc comment to the handler function.
As per coding guidelines, functions should have JSDoc comments.
Apply this diff:
+/**
+ * Generates a transcript with screenshots by merging SRT data with Mux
+ * playback screenshots and announces completion via realtime or broadcast.
+ *
+ * @param event - SRT ready event data
+ * @param step - Inngest step utilities
+ * @param db - Database adapter
+ * @param partyProvider - Party broadcast provider
+ * @param publish - Optional realtime publish function
+ * @returns Transcript with screenshots data
+ */
export const generateTranscriptWithScreenshotsHandler: CoreInngestHandler =
async ({🤖 Prompt for AI Agents
In
packages/core/src/inngest/video-processing/functions/generate-transcript-with-screnshots.ts
around lines 23 to 30, the exported handler lacks a JSDoc comment; add a JSDoc
block immediately above the export that briefly describes the purpose of
generateTranscriptWithScreenshotsHandler, its parameters (event, step, db,
partyProvider, publish) with types/expected shapes, and the return value/promise
behavior, following the project's JSDoc style conventions.
| const realtimeEnabled = | ||
| process.env.NEXT_PUBLIC_ENABLE_REALTIME_VIDEO_UPLOAD === 'true' | ||
|
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Use a server-side env flag (not NEXT_PUBLIC_) in server/Inngest code
Relying on NEXT_PUBLIC_* in server functions is brittle; those are intended for client build-time exposure. Switch to ENABLE_REALTIME_VIDEO_UPLOAD (or similar server-only flag) to gate publishing. Matches the migration plan.
-const realtimeEnabled =
- process.env.NEXT_PUBLIC_ENABLE_REALTIME_VIDEO_UPLOAD === 'true'
+const realtimeEnabled =
+ process.env.ENABLE_REALTIME_VIDEO_UPLOAD === 'true'Also ensure the flag is set in the Inngest environment. Based on learnings.
📝 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.
| const realtimeEnabled = | |
| process.env.NEXT_PUBLIC_ENABLE_REALTIME_VIDEO_UPLOAD === 'true' | |
| const realtimeEnabled = | |
| process.env.ENABLE_REALTIME_VIDEO_UPLOAD === 'true' |
🤖 Prompt for AI Agents
In packages/core/src/inngest/video-processing/functions/video-ready.ts around
lines 17 to 19, the code currently reads
NEXT_PUBLIC_ENABLE_REALTIME_VIDEO_UPLOAD which is a client-facing build-time
env; change this to a server-only flag (e.g. ENABLE_REALTIME_VIDEO_UPLOAD) and
update the check to process.env.ENABLE_REALTIME_VIDEO_UPLOAD === 'true' so the
server/Inngest function uses a server-only env var, and ensure this new
ENABLE_REALTIME_VIDEO_UPLOAD flag is added to the Inngest deployment/environment
variables.
| const videoUploadedHandler: CoreInngestHandler = async ({ | ||
| event, | ||
| step, | ||
| db, | ||
| partyProvider, | ||
| publish, | ||
| }: CoreInngestFunctionInput) => { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🛠️ Refactor suggestion | 🟠 Major
Add JSDoc comment to the handler function.
As per coding guidelines, functions should have JSDoc comments describing their purpose, parameters, and return value.
Apply this diff to add a JSDoc comment:
+/**
+ * Handles video upload events by creating Mux assets, video resources,
+ * and announcing the creation via realtime or broadcast channels.
+ *
+ * @param event - Video upload event data
+ * @param step - Inngest step utilities
+ * @param db - Database adapter
+ * @param partyProvider - Party broadcast provider
+ * @param publish - Optional realtime publish function
+ * @returns Upload result with video resource and Mux asset details
+ */
const videoUploadedHandler: CoreInngestHandler = async ({📝 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.
| const videoUploadedHandler: CoreInngestHandler = async ({ | |
| event, | |
| step, | |
| db, | |
| partyProvider, | |
| publish, | |
| }: CoreInngestFunctionInput) => { | |
| /** | |
| * Handles video upload events by creating Mux assets, video resources, | |
| * and announcing the creation via realtime or broadcast channels. | |
| * | |
| * @param event - Video upload event data | |
| * @param step - Inngest step utilities | |
| * @param db - Database adapter | |
| * @param partyProvider - Party broadcast provider | |
| * @param publish - Optional realtime publish function | |
| * @returns Upload result with video resource and Mux asset details | |
| */ | |
| const videoUploadedHandler: CoreInngestHandler = async ({ | |
| event, | |
| step, | |
| db, | |
| partyProvider, | |
| publish, | |
| }: CoreInngestFunctionInput) => { |
🤖 Prompt for AI Agents
In packages/core/src/inngest/video-processing/functions/video-uploaded.ts around
lines 22 to 28, the handler function lacks a JSDoc comment; add a JSDoc block
immediately above the const videoUploadedHandler declaration describing the
function’s purpose, documenting its parameters (event, step, db, partyProvider,
publish) with types/expected shapes and any important behavior, and specifying
the return type (Promise and what it resolves to or void). Keep the comment
concise, use standard JSDoc tags (@param, @returns, and optionally @throws), and
ensure wording follows project style guidelines.
| await step.run('announce video resource created', async () => { | ||
| return await partyProvider.broadcastMessage({ | ||
| body: { | ||
| body: videoResource, | ||
| requestId: videoResource.id, | ||
| name: 'videoResource.created', | ||
| }, | ||
| roomId: videoResource.id, | ||
| }) | ||
| if (realtimeEnabled && publish) { | ||
| await publish( | ||
| videoChannel(videoResource.id).status({ | ||
| name: 'videoResource.created', | ||
| body: videoResource, | ||
| requestId: videoResource.id, | ||
| }), | ||
| ) | ||
| } | ||
|
|
||
| if (!realtimeEnabled || !publish) { | ||
| await partyProvider.broadcastMessage({ | ||
| body: { | ||
| body: videoResource, | ||
| requestId: videoResource.id, | ||
| name: 'videoResource.created', | ||
| }, | ||
| roomId: videoResource.id, | ||
| }) | ||
| } | ||
| }) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🛠️ Refactor suggestion | 🟠 Major
Eliminate payload duplication and clarify conditional logic.
The payload structure is duplicated between the realtime path (lines 107-111) and fallback path (lines 117-121). Additionally, the complementary conditionals would be clearer as a simple if-else.
Apply this diff to construct the payload once and use clearer conditional logic:
await step.run('announce video resource created', async () => {
- if (realtimeEnabled && publish) {
- await publish(
- videoChannel(videoResource.id).status({
- name: 'videoResource.created',
- body: videoResource,
- requestId: videoResource.id,
- }),
- )
- }
-
- if (!realtimeEnabled || !publish) {
- await partyProvider.broadcastMessage({
- body: {
- body: videoResource,
- requestId: videoResource.id,
- name: 'videoResource.created',
- },
- roomId: videoResource.id,
- })
- }
+ const payload = {
+ name: 'videoResource.created' as const,
+ body: videoResource,
+ requestId: videoResource.id,
+ }
+
+ if (realtimeEnabled && publish) {
+ await publish(videoChannel(videoResource.id).status(payload))
+ } else {
+ await partyProvider.broadcastMessage({
+ body: payload,
+ roomId: videoResource.id,
+ })
+ }
})📝 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.
| await step.run('announce video resource created', async () => { | |
| return await partyProvider.broadcastMessage({ | |
| body: { | |
| body: videoResource, | |
| requestId: videoResource.id, | |
| name: 'videoResource.created', | |
| }, | |
| roomId: videoResource.id, | |
| }) | |
| if (realtimeEnabled && publish) { | |
| await publish( | |
| videoChannel(videoResource.id).status({ | |
| name: 'videoResource.created', | |
| body: videoResource, | |
| requestId: videoResource.id, | |
| }), | |
| ) | |
| } | |
| if (!realtimeEnabled || !publish) { | |
| await partyProvider.broadcastMessage({ | |
| body: { | |
| body: videoResource, | |
| requestId: videoResource.id, | |
| name: 'videoResource.created', | |
| }, | |
| roomId: videoResource.id, | |
| }) | |
| } | |
| }) | |
| await step.run('announce video resource created', async () => { | |
| const payload = { | |
| name: 'videoResource.created' as const, | |
| body: videoResource, | |
| requestId: videoResource.id, | |
| } | |
| if (realtimeEnabled && publish) { | |
| await publish(videoChannel(videoResource.id).status(payload)) | |
| } else { | |
| await partyProvider.broadcastMessage({ | |
| body: payload, | |
| roomId: videoResource.id, | |
| }) | |
| } | |
| }) |
| const statusPayloadSchema = z.object({ | ||
| name: z.enum([ | ||
| 'videoResource.created', | ||
| 'video.asset.ready', | ||
| 'video.asset.errored', | ||
| 'transcript.ready', | ||
| 'transcriptWithScreenshots.ready', | ||
| ]), | ||
| body: z.any(), | ||
| requestId: z.string(), | ||
| }) | ||
|
|
||
| export type VideoStatusPayload = z.infer<typeof statusPayloadSchema> | ||
|
|
||
| export const videoChannel = channel( | ||
| (videoId: string) => `video:${videoId}`, | ||
| ).addTopic(topic('status').schema(statusPayloadSchema)) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Include all emitted event names in the status schema
Clients (e.g., content-video-resource-field.tsx, Lines 184-213) handle video.asset.attached and video.asset.detached, but the schema forbids them, so any publish with those names will throw a Zod validation error. Please extend the enum to cover every emitted status event before shipping.
const statusPayloadSchema = z.object({
name: z.enum([
'videoResource.created',
'video.asset.ready',
'video.asset.errored',
+ 'video.asset.attached',
+ 'video.asset.detached',
'transcript.ready',
'transcriptWithScreenshots.ready',
]),📝 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.
| const statusPayloadSchema = z.object({ | |
| name: z.enum([ | |
| 'videoResource.created', | |
| 'video.asset.ready', | |
| 'video.asset.errored', | |
| 'transcript.ready', | |
| 'transcriptWithScreenshots.ready', | |
| ]), | |
| body: z.any(), | |
| requestId: z.string(), | |
| }) | |
| export type VideoStatusPayload = z.infer<typeof statusPayloadSchema> | |
| export const videoChannel = channel( | |
| (videoId: string) => `video:${videoId}`, | |
| ).addTopic(topic('status').schema(statusPayloadSchema)) | |
| const statusPayloadSchema = z.object({ | |
| name: z.enum([ | |
| 'videoResource.created', | |
| 'video.asset.ready', | |
| 'video.asset.errored', | |
| 'video.asset.attached', | |
| 'video.asset.detached', | |
| 'transcript.ready', | |
| 'transcriptWithScreenshots.ready', | |
| ]), | |
| body: z.any(), | |
| requestId: z.string(), | |
| }) |
🤖 Prompt for AI Agents
In packages/core/src/inngest/video-processing/realtime.ts around lines 4 to 20,
the statusPayloadSchema enum is missing event names that are emitted elsewhere
(specifically video.asset.attached and video.asset.detached), causing Zod
validation failures; update the enum to include those two event names so the
schema covers every emitted status event, then run tests or a quick publish flow
to confirm no validation errors occur when those events are published.
| ### greenfield spike: `apps/code-with-antonio` | ||
| 1. **bootstrap realtime provider** | ||
| - files: `packages/core/providers/realtime.ts`, `packages/core/providers/broadcast.ts` | ||
| - create `RealtimeProvider` matching `PartyProviderConfig` interface (`publish({channel,topic,data})`) | ||
| - add `multiCastBroadcast({ message, providers })` helper that forwards to both realtime + legacy | ||
| 2. **wire middleware in greenfield app** | ||
| - update `apps/code-with-antonio/src/inngest/inngest.server.ts` | ||
| - import `RealtimeProvider` + `LegacyPartyProvider` | ||
| - instantiate inngest client with `@inngest/realtime/middleware` | ||
| - pass `{ realtimeProvider, partyProvider }` into `createInngestMiddleware` | ||
| - gate realtime config behind `process.env.ENABLE_REALTIME_VIDEO_UPLOAD === 'true'` | ||
| 3. **adapt video pipeline handlers** |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🛠️ Refactor suggestion | 🟠 Major
🧩 Analysis chain
Standardize env flag names: server vs client
The plan uses ENABLE_REALTIME_VIDEO_UPLOAD (server), while code uses NEXT_PUBLIC_ENABLE_REALTIME_VIDEO_UPLOAD in handlers and .env. Align to:
- Server (Inngest/functions): ENABLE_REALTIME_VIDEO_UPLOAD
- Client/UI gating: NEXT_PUBLIC_ENABLE_REALTIME_VIDEO_UPLOAD
This avoids server code depending on a client‑scoped variable and matches the plan’s “gate realtime config behind process.env.ENABLE_REALTIME_VIDEO_UPLOAD === 'true'”.
To find mismatches, run:
🏁 Script executed:
#!/bin/bash
rg -n "ENABLE_REALTIME_VIDEO_UPLOAD|NEXT_PUBLIC_ENABLE_REALTIME_VIDEO_UPLOAD" -g '!*dist*' -g '!*build*'Length of output: 1548
Standardize environment flags for server vs client
Server-side (Inngest) functions should use process.env.ENABLE_REALTIME_VIDEO_UPLOAD (non-public); update all occurrences in packages/core/src/inngest/... accordingly. Retain NEXT_PUBLIC_ENABLE_REALTIME_VIDEO_UPLOAD only in client/UI code.
Summary by CodeRabbit