From 59a9182c7904d4f37533fefdf3c453afa7972dca Mon Sep 17 00:00:00 2001 From: Brendan Allan Date: Mon, 25 Aug 2025 20:52:36 +0800 Subject: [PATCH 1/2] include web in typescript project --- .../actions/videos/generate-ai-metadata.ts | 4 - apps/web/actions/videos/get-status.ts | 24 +- .../folder/[id]/components/ClientCapCard.tsx | 6 +- .../[id]/components/NewSubfolderButton.tsx | 3 +- .../[id]/components/useUploadPlaceholders.ts | 48 --- .../[spaceId]/folder/[folderId]/page.tsx | 1 - apps/web/app/(org)/verify-otp/form.tsx | 4 +- apps/web/app/(site)/tools/trim/metadata.ts | 2 +- .../app/api/upload/[...route]/multipart.ts | 3 +- .../[videoId]/_components/EmbedVideo.tsx | 2 +- apps/web/app/embed/[videoId]/page.tsx | 7 +- apps/web/app/lib/compose-refs.ts | 4 +- apps/web/app/s/[videoId]/Share.tsx | 12 +- .../s/[videoId]/_components/ShareHeader.tsx | 4 +- .../app/s/[videoId]/_components/Toolbar.tsx | 12 +- .../_components/tabs/Activity/Analytics.tsx | 2 + .../_components/tabs/Activity/index.tsx | 1 + .../_components/video/media-player.tsx | 12 +- apps/web/app/s/[videoId]/page.tsx | 11 +- apps/web/app/s/page.tsx | 1 + apps/web/components/VideoThumbnail.tsx | 2 +- apps/web/components/tools/types.ts | 10 +- apps/web/lib/effect-react-query.ts | 306 +++++++++--------- apps/web/lib/folder.ts | 25 +- apps/web/scripts/reset-ai-processing-flags.ts | 4 +- apps/web/utils/changelog.ts | 3 +- packages/database/schema.ts | 6 +- packages/ui/src/components/Dropdown.tsx | 10 +- tsconfig.json | 3 +- 29 files changed, 251 insertions(+), 281 deletions(-) delete mode 100644 apps/web/app/(org)/dashboard/folder/[id]/components/useUploadPlaceholders.ts diff --git a/apps/web/actions/videos/generate-ai-metadata.ts b/apps/web/actions/videos/generate-ai-metadata.ts index eecd46de68..744d4d7cc9 100644 --- a/apps/web/actions/videos/generate-ai-metadata.ts +++ b/apps/web/actions/videos/generate-ai-metadata.ts @@ -45,13 +45,11 @@ export async function generateAiMetadata(videoId: string, userId: string) { metadata: { ...metadata, aiProcessing: false, - generationError: null, }, }) .where(eq(videos.id, videoId)); metadata.aiProcessing = false; - metadata.generationError = null; } else { return; } @@ -296,8 +294,6 @@ ${transcriptText}`; metadata: { ...currentMetadata, aiProcessing: false, - generationError: - error instanceof Error ? error.message : String(error), }, }) .where(eq(videos.id, videoId)); diff --git a/apps/web/actions/videos/get-status.ts b/apps/web/actions/videos/get-status.ts index 26bc9c06e6..ac73e509ac 100644 --- a/apps/web/actions/videos/get-status.ts +++ b/apps/web/actions/videos/get-status.ts @@ -20,7 +20,7 @@ export interface VideoStatusResult { aiTitle: string | null; summary: string | null; chapters: { title: string; start: number }[] | null; - generationError: string | null; + // generationError: string | null; error?: string; } @@ -62,7 +62,7 @@ export async function getVideoStatus( aiTitle: metadata.aiTitle || null, summary: metadata.summary || null, chapters: metadata.chapters || null, - generationError: metadata.generationError || null, + // generationError: metadata.generationError || null, }; } catch (error) { console.error( @@ -75,7 +75,7 @@ export async function getVideoStatus( aiTitle: metadata.aiTitle || null, summary: metadata.summary || null, chapters: metadata.chapters || null, - generationError: metadata.generationError || null, + // generationError: metadata.generationError || null, error: "Failed to start transcription", }; } @@ -88,7 +88,7 @@ export async function getVideoStatus( aiTitle: metadata.aiTitle || null, summary: metadata.summary || null, chapters: metadata.chapters || null, - generationError: metadata.generationError || null, + // generationError: metadata.generationError || null, error: "Transcription failed", }; } @@ -110,7 +110,7 @@ export async function getVideoStatus( metadata: { ...metadata, aiProcessing: false, - generationError: "AI processing timed out and was reset", + // generationError: "AI processing timed out and was reset", }, }) .where(eq(videos.id, videoId)); @@ -133,7 +133,7 @@ export async function getVideoStatus( aiTitle: updatedMetadata.aiTitle || null, summary: updatedMetadata.summary || null, chapters: updatedMetadata.chapters || null, - generationError: updatedMetadata.generationError || null, + // generationError: updatedMetadata.generationError || null, error: "AI processing timed out and was reset", }; } @@ -144,8 +144,8 @@ export async function getVideoStatus( video.transcriptionStatus === "COMPLETE" && !metadata.aiProcessing && !metadata.summary && - !metadata.chapters && - !metadata.generationError + !metadata.chapters + // !metadata.generationError ) { console.log( `[Get Status] Transcription complete but no AI data, checking feature flag for video owner ${video.ownerId}`, @@ -198,8 +198,8 @@ export async function getVideoStatus( metadata: { ...currentMetadata, aiProcessing: false, - generationError: - error instanceof Error ? error.message : String(error), + // generationError: + // error instanceof Error ? error.message : String(error), }, }) .where(eq(videos.id, videoId)); @@ -221,7 +221,7 @@ export async function getVideoStatus( aiTitle: metadata.aiTitle || null, summary: metadata.summary || null, chapters: metadata.chapters || null, - generationError: metadata.generationError || null, + // generationError: metadata.generationError || null, }; } else { const videoOwner = videoOwnerQuery[0]; @@ -239,6 +239,6 @@ export async function getVideoStatus( aiTitle: metadata.aiTitle || null, summary: metadata.summary || null, chapters: metadata.chapters || null, - generationError: metadata.generationError || null, + // generationError: metadata.generationError || null, }; } diff --git a/apps/web/app/(org)/dashboard/folder/[id]/components/ClientCapCard.tsx b/apps/web/app/(org)/dashboard/folder/[id]/components/ClientCapCard.tsx index f45cdbc9a2..496898652a 100644 --- a/apps/web/app/(org)/dashboard/folder/[id]/components/ClientCapCard.tsx +++ b/apps/web/app/(org)/dashboard/folder/[id]/components/ClientCapCard.tsx @@ -103,7 +103,11 @@ export function ClientCapCard(props: ClientCapCardProps) { onDragEnd={handleDragEnd} className={isDragging ? "opacity-50" : ""} > - + ); } diff --git a/apps/web/app/(org)/dashboard/folder/[id]/components/NewSubfolderButton.tsx b/apps/web/app/(org)/dashboard/folder/[id]/components/NewSubfolderButton.tsx index 0cda644140..efbad54854 100644 --- a/apps/web/app/(org)/dashboard/folder/[id]/components/NewSubfolderButton.tsx +++ b/apps/web/app/(org)/dashboard/folder/[id]/components/NewSubfolderButton.tsx @@ -4,10 +4,11 @@ import { Button } from "@cap/ui"; import { faFolderPlus } from "@fortawesome/free-solid-svg-icons"; import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; import { useState } from "react"; +import { Folder } from "@cap/web-domain"; import { SubfolderDialog } from "./SubfolderDialog"; interface NewSubfolderButtonProps { - parentFolderId: string; + parentFolderId: Folder.FolderId; } export const NewSubfolderButton = ({ diff --git a/apps/web/app/(org)/dashboard/folder/[id]/components/useUploadPlaceholders.ts b/apps/web/app/(org)/dashboard/folder/[id]/components/useUploadPlaceholders.ts deleted file mode 100644 index 69c558aeea..0000000000 --- a/apps/web/app/(org)/dashboard/folder/[id]/components/useUploadPlaceholders.ts +++ /dev/null @@ -1,48 +0,0 @@ -import { useCallback } from "react"; -import { useUploadContext } from "./UploadContext"; - -export interface UploadPlaceholder { - id: string; - progress: number; - thumbnail?: string; - uploadProgress?: number; -} - -export function useUploadPlaceholders() { - const { uploadPlaceholders, setUploadPlaceholders, isUploading } = - useUploadContext(); - - const handleUploadStart = useCallback( - (id: string, thumbnail?: string) => { - setUploadPlaceholders((prev) => [ - { id, progress: 0, thumbnail }, - ...prev, - ]); - }, - [setUploadPlaceholders], - ); - - const handleUploadProgress = useCallback( - (id: string, progress: number, uploadProgress?: number) => { - setUploadPlaceholders((prev) => - prev.map((u) => (u.id === id ? { ...u, progress, uploadProgress } : u)), - ); - }, - [setUploadPlaceholders], - ); - - const handleUploadComplete = useCallback( - (id: string) => { - setUploadPlaceholders((prev) => prev.filter((u) => u.id !== id)); - }, - [setUploadPlaceholders], - ); - - return { - uploadPlaceholders, - isUploading, - handleUploadStart, - handleUploadProgress, - handleUploadComplete, - }; -} diff --git a/apps/web/app/(org)/dashboard/spaces/[spaceId]/folder/[folderId]/page.tsx b/apps/web/app/(org)/dashboard/spaces/[spaceId]/folder/[folderId]/page.tsx index 3959becb1b..466e1f8323 100644 --- a/apps/web/app/(org)/dashboard/spaces/[spaceId]/folder/[folderId]/page.tsx +++ b/apps/web/app/(org)/dashboard/spaces/[spaceId]/folder/[folderId]/page.tsx @@ -74,7 +74,6 @@ const FolderPage = async ({ cardType="shared" initialVideos={videosData} dubApiKeyEnabled={!!serverEnv().DUB_API_KEY} - userId={userId} /> ); diff --git a/apps/web/app/(org)/verify-otp/form.tsx b/apps/web/app/(org)/verify-otp/form.tsx index 6bce9e63b5..f2effe76cf 100644 --- a/apps/web/app/(org)/verify-otp/form.tsx +++ b/apps/web/app/(org)/verify-otp/form.tsx @@ -172,7 +172,9 @@ export function VerifyOTPForm({ {code.map((digit, index) => ( (inputRefs.current[index] = el)} + ref={(el) => { + inputRefs.current[index] = el; + }} type="text" inputMode="numeric" pattern="[0-9]*" diff --git a/apps/web/app/(site)/tools/trim/metadata.ts b/apps/web/app/(site)/tools/trim/metadata.ts index 364a780a38..c0fced6962 100644 --- a/apps/web/app/(site)/tools/trim/metadata.ts +++ b/apps/web/app/(site)/tools/trim/metadata.ts @@ -4,7 +4,7 @@ import { trimVideoContent } from "@/components/tools/content"; export const metadata: Metadata = { title: trimVideoContent.title, description: trimVideoContent.description, - keywords: trimVideoContent.tags.join(", "), + keywords: trimVideoContent.tags?.join(", "), openGraph: { title: trimVideoContent.title, description: trimVideoContent.description, diff --git a/apps/web/app/api/upload/[...route]/multipart.ts b/apps/web/app/api/upload/[...route]/multipart.ts index 667c461e4f..d9e59ae8d3 100644 --- a/apps/web/app/api/upload/[...route]/multipart.ts +++ b/apps/web/app/api/upload/[...route]/multipart.ts @@ -1,9 +1,8 @@ import { db, updateIfDefined } from "@cap/database"; import { s3Buckets, videos } from "@cap/database/schema"; -import type { VideoMetadata } from "@cap/database/types"; import { serverEnv } from "@cap/env"; import { zValidator } from "@hono/zod-validator"; -import { eq, sql } from "drizzle-orm"; +import { eq, and } from "drizzle-orm"; import { Hono } from "hono"; import { z } from "zod"; import { withAuth } from "@/app/api/utils"; diff --git a/apps/web/app/embed/[videoId]/_components/EmbedVideo.tsx b/apps/web/app/embed/[videoId]/_components/EmbedVideo.tsx index 264258eca2..a94617ae88 100644 --- a/apps/web/app/embed/[videoId]/_components/EmbedVideo.tsx +++ b/apps/web/app/embed/[videoId]/_components/EmbedVideo.tsx @@ -44,7 +44,7 @@ type CommentWithAuthor = typeof commentsSchema.$inferSelect & { export const EmbedVideo = forwardRef< HTMLVideoElement, { - data: typeof videos.$inferSelect; + data: Omit; user: typeof userSelectProps | null; comments: CommentWithAuthor[]; chapters?: { title: string; start: number }[]; diff --git a/apps/web/app/embed/[videoId]/page.tsx b/apps/web/app/embed/[videoId]/page.tsx index 16b2b1ec93..cc88f96d7b 100644 --- a/apps/web/app/embed/[videoId]/page.tsx +++ b/apps/web/app/embed/[videoId]/page.tsx @@ -11,7 +11,7 @@ import type { VideoMetadata } from "@cap/database/types"; import { buildEnv } from "@cap/env"; import { provideOptionalAuth, Videos, VideosPolicy } from "@cap/web-backend"; import { Policy, type Video } from "@cap/web-domain"; -import { eq } from "drizzle-orm"; +import { eq, sql } from "drizzle-orm"; import { Effect, Option } from "effect"; import type { Metadata } from "next"; import Link from "next/link"; @@ -142,6 +142,11 @@ export default async function EmbedVideoPage(props: Props) { transcriptionStatus: videos.transcriptionStatus, source: videos.source, folderId: videos.folderId, + width: videos.width, + height: videos.height, + duration: videos.duration, + fps: videos.fps, + hasPassword: sql`IF(${videos.password} IS NULL, 0, 1)`, sharedOrganization: { organizationId: sharedVideos.organizationId, }, diff --git a/apps/web/app/lib/compose-refs.ts b/apps/web/app/lib/compose-refs.ts index 2aaa3e9d5d..ad5c278c0c 100644 --- a/apps/web/app/lib/compose-refs.ts +++ b/apps/web/app/lib/compose-refs.ts @@ -1,6 +1,6 @@ import * as React from "react"; -type PossibleRef = React.Ref | undefined; +type PossibleRef = React.LegacyRef | React.Ref | undefined; /** * Set a given ref to a given value @@ -12,7 +12,7 @@ function setRef(ref: PossibleRef, value: T) { } if (ref !== null && ref !== undefined) { - ref.current = value; + (ref as any).current = value; } } diff --git a/apps/web/app/s/[videoId]/Share.tsx b/apps/web/app/s/[videoId]/Share.tsx index 27942428a2..7788c19987 100644 --- a/apps/web/app/s/[videoId]/Share.tsx +++ b/apps/web/app/s/[videoId]/Share.tsx @@ -19,6 +19,7 @@ import { import { ShareVideo } from "./_components/ShareVideo"; import { Sidebar } from "./_components/Sidebar"; import { Toolbar } from "./_components/Toolbar"; +import { Video } from "@cap/web-domain"; const formatTime = (time: number) => { const minutes = Math.floor(time / 60); @@ -62,7 +63,7 @@ interface ShareProps { } const useVideoStatus = ( - videoId: string, + videoId: Video.VideoId, aiGenerationEnabled: boolean, initialData?: { transcriptionStatus?: string | null; @@ -93,7 +94,6 @@ const useVideoStatus = ( aiTitle: initialData.aiData?.title || null, summary: initialData.aiData?.summary || null, chapters: initialData.aiData?.chapters || null, - generationError: null, } : undefined, refetchInterval: (query) => { @@ -177,7 +177,7 @@ export const Share = ({ summary: videoStatus?.summary || null, chapters: videoStatus?.chapters || null, processing: videoStatus?.aiProcessing || false, - generationError: videoStatus?.generationError || null, + // generationError: videoStatus?.generationError || null, }), [videoStatus], ); @@ -196,9 +196,9 @@ export const Share = ({ } if (transcriptionStatus === "COMPLETE") { - if (aiData.generationError) { - return false; - } + // if (aiData.generationError) { + // return false; + // } if (aiData.processing === true) { return true; } diff --git a/apps/web/app/s/[videoId]/_components/ShareHeader.tsx b/apps/web/app/s/[videoId]/_components/ShareHeader.tsx index 7aa36a2402..48c2823758 100644 --- a/apps/web/app/s/[videoId]/_components/ShareHeader.tsx +++ b/apps/web/app/s/[videoId]/_components/ShareHeader.tsx @@ -2,7 +2,7 @@ import type { userSelectProps } from "@cap/database/auth/session"; import type { videos } from "@cap/database/schema"; -import { buildEnv } from "@cap/env"; +import { buildEnv, NODE_ENV } from "@cap/env"; import { Button } from "@cap/ui"; import { userIsPro } from "@cap/utils"; import { faChevronDown, faLock } from "@fortawesome/free-solid-svg-icons"; @@ -28,7 +28,6 @@ export const ShareHeader = ({ sharedOrganizations = [], sharedSpaces = [], spacesData = null, - NODE_ENV, }: { data: typeof videos.$inferSelect; user: typeof userSelectProps | null; @@ -49,7 +48,6 @@ export const ShareHeader = ({ organizationId: string; }[]; spacesData?: Spaces[] | null; - NODE_ENV: "production" | "development" | "test"; }) => { const { push, refresh } = useRouter(); const [isEditing, setIsEditing] = useState(false); diff --git a/apps/web/app/s/[videoId]/_components/Toolbar.tsx b/apps/web/app/s/[videoId]/_components/Toolbar.tsx index 3516483faf..415331c7ec 100644 --- a/apps/web/app/s/[videoId]/_components/Toolbar.tsx +++ b/apps/web/app/s/[videoId]/_components/Toolbar.tsx @@ -13,8 +13,8 @@ const MotionButton = motion.create(Button); interface ToolbarProps { data: typeof videos.$inferSelect; user: typeof userSelectProps | null; - onOptimisticComment: (comment: CommentType) => void; - onCommentSuccess: (comment: CommentType) => void; + onOptimisticComment?: (comment: CommentType) => void; + onCommentSuccess?: (comment: CommentType) => void; } export const Toolbar = ({ @@ -68,7 +68,7 @@ export const Toolbar = ({ sending: true, }; - onOptimisticComment(optimisticComment); + onOptimisticComment?.(optimisticComment); try { const newCommentData = await newComment({ @@ -78,7 +78,7 @@ export const Toolbar = ({ type: "emoji", }); startTransition(() => { - onCommentSuccess(newCommentData); + onCommentSuccess?.(newCommentData); }); } catch (error) { console.error("Error posting comment:", error); @@ -107,7 +107,7 @@ export const Toolbar = ({ sending: true, }; - onOptimisticComment(optimisticComment); + onOptimisticComment?.(optimisticComment); try { const newCommentData = await newComment({ @@ -117,7 +117,7 @@ export const Toolbar = ({ type: "text", }); startTransition(() => { - onCommentSuccess(newCommentData); + onCommentSuccess?.(newCommentData); }); } catch (error) { console.error("Error posting comment:", error); diff --git a/apps/web/app/s/[videoId]/_components/tabs/Activity/Analytics.tsx b/apps/web/app/s/[videoId]/_components/tabs/Activity/Analytics.tsx index 814eb3a08f..436714a48b 100644 --- a/apps/web/app/s/[videoId]/_components/tabs/Activity/Analytics.tsx +++ b/apps/web/app/s/[videoId]/_components/tabs/Activity/Analytics.tsx @@ -9,6 +9,7 @@ const Analytics = (props: { videoId: string; views: MaybePromise; comments: CommentType[]; + isLoadingAnalytics: boolean; }) => { const [views, setViews] = useState( props.views instanceof Promise ? use(props.views) : props.views, @@ -40,6 +41,7 @@ const Analytics = (props: { return ( } diff --git a/apps/web/app/s/[videoId]/_components/video/media-player.tsx b/apps/web/app/s/[videoId]/_components/video/media-player.tsx index f5de1b2864..6114c8fc0b 100644 --- a/apps/web/app/s/[videoId]/_components/video/media-player.tsx +++ b/apps/web/app/s/[videoId]/_components/video/media-player.tsx @@ -120,18 +120,18 @@ function createStore( ): Store { const store: Store = { subscribe: (cb) => { - listenersRef.current.add(cb); - return () => listenersRef.current.delete(cb); + listenersRef.current?.add(cb); + return () => listenersRef.current?.delete(cb); }, - getState: () => stateRef.current, + getState: () => stateRef.current!, setState: (key, value) => { - if (Object.is(stateRef.current[key], value)) return; - stateRef.current[key] = value; + if (Object.is(stateRef.current?.[key], value)) return; + stateRef.current![key] = value; onValueChange?.[key]?.(value, store); store.notify(); }, notify: () => { - for (const cb of listenersRef.current) { + for (const cb of listenersRef.current ?? []) { cb(); } }, diff --git a/apps/web/app/s/[videoId]/page.tsx b/apps/web/app/s/[videoId]/page.tsx index d1725e78ce..80e63a7e7b 100644 --- a/apps/web/app/s/[videoId]/page.tsx +++ b/apps/web/app/s/[videoId]/page.tsx @@ -280,6 +280,11 @@ export default async function ShareVideoPage(props: Props) { skipProcessing: videos.skipProcessing, transcriptionStatus: videos.transcriptionStatus, source: videos.source, + width: videos.width, + height: videos.height, + duration: videos.duration, + fps: videos.fps, + hasPassword: sql`IF(${videos.password} IS NULL, 0, 1)`, sharedOrganization: { organizationId: sharedVideos.organizationId, }, @@ -334,6 +339,7 @@ async function AuthorizedContent({ }: { video: Omit, "folderId" | "password"> & { sharedOrganization: { organizationId: string } | null; + hasPassword: number; }; searchParams: { [key: string]: string | string[] | undefined }; }) { @@ -490,7 +496,7 @@ async function AuthorizedContent({ !currentMetadata.aiProcessing && !currentMetadata.summary && !currentMetadata.chapters && - !currentMetadata.generationError && + // !currentMetadata.generationError && aiGenerationEnabled ) { try { @@ -640,11 +646,11 @@ async function AuthorizedContent({ const videoWithOrganizationInfo: VideoWithOrganization = { ...video, + hasPassword: video.hasPassword === 1, organizationMembers: membersList.map((member) => member.userId), organizationId: video.sharedOrganization?.organizationId ?? undefined, sharedOrganizations: sharedOrganizations, password: null, - hasPassword: video.password !== null, folderId: null, }; @@ -667,7 +673,6 @@ async function AuthorizedContent({ sharedSpaces={sharedSpaces} userOrganizations={userOrganizations} spacesData={spacesData} - NODE_ENV={process.env.NODE_ENV} /> = memo( {videoDuration && (

- {formatDuration(videoDuration.toString())} + {formatDuration(videoDuration)}

)} {imageUrl.data && ( diff --git a/apps/web/components/tools/types.ts b/apps/web/components/tools/types.ts index 6ddf48db2b..c8ca7dbbbf 100644 --- a/apps/web/components/tools/types.ts +++ b/apps/web/components/tools/types.ts @@ -1,11 +1,11 @@ export interface ToolPageContent { - slug: string; + slug?: string; title: string; description: string; - publishedAt: string; - category: string; - author: string; - tags: string[]; + publishedAt?: string; + category?: string; + author?: string; + tags?: string[]; cta: { title: string; diff --git a/apps/web/lib/effect-react-query.ts b/apps/web/lib/effect-react-query.ts index 9c47937840..c98cb3a946 100644 --- a/apps/web/lib/effect-react-query.ts +++ b/apps/web/lib/effect-react-query.ts @@ -20,8 +20,8 @@ import * as Exit from "effect/Exit"; type Override = { [AKey in keyof TTargetA]: AKey extends keyof TTargetB - ? TTargetB[AKey] - : TTargetA[AKey]; + ? TTargetB[AKey] + : TTargetA[AKey]; }; export function makeUseEffectQuery( @@ -40,10 +40,10 @@ export function makeUseEffectQuery( UseQueryOptions, { queryFn?: - | (( - context: QueryFunctionContext, - ) => Effect.Effect) - | SkipToken; + | (( + context: QueryFunctionContext, + ) => Effect.Effect) + | SkipToken; } >, ): UseQueryResult { @@ -57,66 +57,66 @@ export function makeUseEffectQuery( ...(options as any), ...(typeof queryFn === "function" ? { - queryFn: async (args) => { - let queryEffect: Effect.Effect; - try { - queryEffect = queryFn(args); - } catch (e) { - throw new Cause.UnknownException(e, "queryFn threw"); - } - const effectToRun = queryEffect; - // .pipe( - // Effect.withSpan("useEffectQuery", { - // attributes: { - // queryKey: args.queryKey, - // queryFn: queryFn.toString(), - // }, - // }) - // ); - const result = await runtime.runPromiseExit(effectToRun, { - signal: args.signal, - }); - if (Exit.isFailure(result)) { - // we always throw the cause - throw result.cause; - } else { - return result.value; - } - }, - } + queryFn: async (args) => { + let queryEffect: Effect.Effect; + try { + queryEffect = queryFn(args); + } catch (e) { + throw new Cause.UnknownException(e, "queryFn threw"); + } + const effectToRun = queryEffect; + // .pipe( + // Effect.withSpan("useEffectQuery", { + // attributes: { + // queryKey: args.queryKey, + // queryFn: queryFn.toString(), + // }, + // }) + // ); + const result = await runtime.runPromiseExit(effectToRun, { + signal: args.signal, + }); + if (Exit.isFailure(result)) { + // we always throw the cause + throw result.cause; + } else { + return result.value; + } + }, + } : { queryFn }), ...(typeof throwOnError === "function" ? { - throwOnError: (error, query) => { - // this is safe because internally when we call useQuery we always throw the full cause or UnknownException - const cause = error as - | Cause.Cause - | Cause.UnknownException; - // if the cause is UnknownException, we always return true and throw it - if (Cause.isUnknownException(cause)) { - return true; - } - const failureOrCause = Cause.failureOrCause(cause); - if (throwOnDefect) { - // in this case options.throwOnError expects a TError - // the cause was a fail, so we have TError - if (Either.isLeft(failureOrCause)) { - // this is safe because if throwOnDefect is true then TExposedError is TError - const exposedError = - failureOrCause.left as unknown as TExposedError; - return throwOnError(exposedError, query); - } else { - // the cause was a die or interrupt, so we return true - return true; - } - } else { - // in this case options.throwOnError expects a Cause - // this is safe because if throwOnDefect is false then TExposedError is Cause - const exposedError = cause as unknown as TExposedError; + throwOnError: (error, query) => { + // this is safe because internally when we call useQuery we always throw the full cause or UnknownException + const cause = error as + | Cause.Cause + | Cause.UnknownException; + // if the cause is UnknownException, we always return true and throw it + if (Cause.isUnknownException(cause)) { + return true; + } + const failureOrCause = Cause.failureOrCause(cause); + if (throwOnDefect) { + // in this case options.throwOnError expects a TError + // the cause was a fail, so we have TError + if (Either.isLeft(failureOrCause)) { + // this is safe because if throwOnDefect is true then TExposedError is TError + const exposedError = + failureOrCause.left as unknown as TExposedError; return throwOnError(exposedError, query); + } else { + // the cause was a die or interrupt, so we return true + return true; } - }, - } + } else { + // in this case options.throwOnError expects a Cause + // this is safe because if throwOnDefect is false then TExposedError is Cause + const exposedError = cause as unknown as TExposedError; + return throwOnError(exposedError, query); + } + }, + } : {}), }); @@ -127,36 +127,36 @@ export function makeUseEffectQuery( return target.error ? throwOnDefect ? Either.match( - Cause.failureOrCause( - target.error as unknown as Cause.Cause, // this is safe because we always throw the full cause and we know that error is not null - ), - { - onLeft: (error) => error as unknown as TExposedError, // if throwOnDefect is true then TExposedError is TError - onRight: (_cause) => { - throw new Error( - "non fail cause with throwOnDefect: true should have thrown already", - ); - }, + Cause.failureOrCause( + target.error as unknown as Cause.Cause, // this is safe because we always throw the full cause and we know that error is not null + ), + { + onLeft: (error) => error as unknown as TExposedError, // if throwOnDefect is true then TExposedError is TError + onRight: (_cause) => { + throw new Error( + "non fail cause with throwOnDefect: true should have thrown already", + ); }, - ) + }, + ) : target.error // if throwOnDefect is false then TExposedError is Cause, and base error is always Cause : null; } else if (prop === "failureReason") { return target.failureReason ? throwOnDefect ? Either.match( - Cause.failureOrCause( - target.failureReason as unknown as Cause.Cause, // this is safe because we always throw the full cause and we know that error is not null - ), - { - onLeft: (error) => error as unknown as TExposedError, // if throwOnDefect is true then TExposedError is TError - onRight: (_cause) => { - throw new Error( - "non fail cause with throwOnDefect: true should have thrown already", - ); - }, + Cause.failureOrCause( + target.failureReason as unknown as Cause.Cause, // this is safe because we always throw the full cause and we know that error is not null + ), + { + onLeft: (error) => error as unknown as TExposedError, // if throwOnDefect is true then TExposedError is TError + onRight: (_cause) => { + throw new Error( + "non fail cause with throwOnDefect: true should have thrown already", + ); }, - ) + }, + ) : target.failureReason // if throwOnDefect is false then TExposedError is Cause, and base error is always Cause : null; } @@ -246,69 +246,69 @@ export function makeUseEffectMutation( const runtime = useEffectRuntime(); const baseResults = useMutation({ - ...options, + ...(options as any), mutationFn: typeof mutationFn === "function" ? async (variables: TVariables) => { - let mutationEffect: Effect.Effect; - try { - mutationEffect = mutationFn(variables); - } catch (e) { - throw new Cause.UnknownException(e, "mutationFn threw"); - } - const effectToRun = mutationEffect; - // .pipe( - // Effect.withSpan("useEffectMutation", { - // attributes: { - // mutationFn: mutationFn.toString(), - // }, - // }) - // ); - const result = await runtime.runPromiseExit(effectToRun); - console.log({ result }); - if (Exit.isFailure(result)) { - // we always throw the cause - throw result.cause; - } else { - return result.value; - } + let mutationEffect: Effect.Effect; + try { + mutationEffect = mutationFn(variables); + } catch (e) { + throw new Cause.UnknownException(e, "mutationFn threw"); + } + const effectToRun = mutationEffect; + // .pipe( + // Effect.withSpan("useEffectMutation", { + // attributes: { + // mutationFn: mutationFn.toString(), + // }, + // }) + // ); + const result = await runtime.runPromiseExit(effectToRun); + console.log({ result }); + if (Exit.isFailure(result)) { + // we always throw the cause + throw result.cause; + } else { + return result.value; } + } : mutationFn, throwOnError: typeof throwOnError === "function" ? (error: Cause.Cause) => { - // this is safe because internally when we call useQuery we always throw the full cause or UnknownException - const cause = error as - | Cause.Cause - | Cause.UnknownException; + // this is safe because internally when we call useQuery we always throw the full cause or UnknownException + const cause = error as + | Cause.Cause + | Cause.UnknownException; - // if the cause is UnknownException, we always return true and throw it - if (Cause.isUnknownException(cause)) { - return true; - } + // if the cause is UnknownException, we always return true and throw it + if (Cause.isUnknownException(cause)) { + return true; + } - const failureOrCause = Cause.failureOrCause(cause); + const failureOrCause = Cause.failureOrCause(cause); - if (throwOnDefect) { - // in this case options.throwOnError expects a TError + if (throwOnDefect) { + // in this case options.throwOnError expects a TError - // the cause was a fail, so we have TError - if (Either.isLeft(failureOrCause)) { - // this is safe because if throwOnDefect is true then TExposedError is TError - const exposedError = - failureOrCause.left as unknown as TExposedError; - return throwOnError(exposedError); - } else { - // the cause was a die or interrupt, so we return true - return true; - } - } else { - // in this case options.throwOnError expects a Cause - // this is safe because if throwOnDefect is false then TExposedError is Cause - const exposedError = cause as unknown as TExposedError; + // the cause was a fail, so we have TError + if (Either.isLeft(failureOrCause)) { + // this is safe because if throwOnDefect is true then TExposedError is TError + const exposedError = + failureOrCause.left as unknown as TExposedError; return throwOnError(exposedError); + } else { + // the cause was a die or interrupt, so we return true + return true; } + } else { + // in this case options.throwOnError expects a Cause + // this is safe because if throwOnDefect is false then TExposedError is Cause + const exposedError = cause as unknown as TExposedError; + return throwOnError(exposedError); } + } : throwOnError, // onMutate: // typeof onMutate === "function" @@ -403,36 +403,36 @@ export function makeUseEffectMutation( return target.error ? throwOnDefect ? Either.match( - Cause.failureOrCause( - target.error as unknown as Cause.Cause, // this is safe because we always throw the full cause and we know that error is not null - ), - { - onLeft: (error) => error as unknown as TExposedError, // if throwOnDefect is true then TExposedError is TError - onRight: (_cause) => { - throw new Error( - "non fail cause with throwOnDefect: true should have thrown already", - ); - }, + Cause.failureOrCause( + target.error as unknown as Cause.Cause, // this is safe because we always throw the full cause and we know that error is not null + ), + { + onLeft: (error) => error as unknown as TExposedError, // if throwOnDefect is true then TExposedError is TError + onRight: (_cause) => { + throw new Error( + "non fail cause with throwOnDefect: true should have thrown already", + ); }, - ) + }, + ) : target.error // if throwOnDefect is false then TExposedError is Cause, and base error is always Cause : null; } else if (prop === "failureReason") { return target.failureReason ? throwOnDefect ? Either.match( - Cause.failureOrCause( - target.failureReason as unknown as Cause.Cause, // this is safe because we always throw the full cause and we know that error is not null - ), - { - onLeft: (error) => error as unknown as TExposedError, // if throwOnDefect is true then TExposedError is TError - onRight: (_cause) => { - throw new Error( - "non fail cause with throwOnDefect: true should have thrown already", - ); - }, + Cause.failureOrCause( + target.failureReason as unknown as Cause.Cause, // this is safe because we always throw the full cause and we know that error is not null + ), + { + onLeft: (error) => error as unknown as TExposedError, // if throwOnDefect is true then TExposedError is TError + onRight: (_cause) => { + throw new Error( + "non fail cause with throwOnDefect: true should have thrown already", + ); }, - ) + }, + ) : target.failureReason // if throwOnDefect is false then TExposedError is Cause, and base error is always Cause : null; } else if (prop === "mutate") { diff --git a/apps/web/lib/folder.ts b/apps/web/lib/folder.ts index 907a8daea2..ff2e13253a 100644 --- a/apps/web/lib/folder.ts +++ b/apps/web/lib/folder.ts @@ -113,10 +113,9 @@ async function getSharedSpacesForVideos(videoIds: string[]) { // Add space-level sharing spaceSharing.forEach((space) => { - if (!sharedSpacesMap[space.videoId]) { - sharedSpacesMap[space.videoId] = []; - } - sharedSpacesMap[space.videoId].push({ + const spaces = sharedSpacesMap[space.videoId] ?? [] + sharedSpacesMap[space.videoId] = spaces; + spaces.push({ id: space.id, name: space.name, organizationId: space.organizationId, @@ -127,10 +126,10 @@ async function getSharedSpacesForVideos(videoIds: string[]) { // Add organization-level sharing orgSharing.forEach((org) => { - if (!sharedSpacesMap[org.videoId]) { - sharedSpacesMap[org.videoId] = []; - } - sharedSpacesMap[org.videoId].push({ + const spaces = sharedSpacesMap[org.videoId] ?? []; + sharedSpacesMap[org.videoId] = spaces; + + spaces.push({ id: org.id, name: org.name, organizationId: org.organizationId, @@ -216,8 +215,8 @@ export async function getVideosByFolderId(folderId: string) { totalReactions: video.totalReactions, sharedOrganizations: Array.isArray(video.sharedOrganizations) ? video.sharedOrganizations.filter( - (organization) => organization.id !== null, - ) + (organization) => organization.id !== null, + ) : [], sharedSpaces: Array.isArray(sharedSpacesMap[video.id]) ? sharedSpacesMap[video.id] @@ -225,9 +224,9 @@ export async function getVideosByFolderId(folderId: string) { ownerName: video.ownerName ?? "", metadata: video.metadata as | { - customCreatedAt?: string; - [key: string]: unknown; - } + customCreatedAt?: string; + [key: string]: unknown; + } | undefined, hasPassword: video.hasPassword === 1, foldersData: [], // Empty array since videos in a folder don't need folder data diff --git a/apps/web/scripts/reset-ai-processing-flags.ts b/apps/web/scripts/reset-ai-processing-flags.ts index a550d04772..b3ec488366 100644 --- a/apps/web/scripts/reset-ai-processing-flags.ts +++ b/apps/web/scripts/reset-ai-processing-flags.ts @@ -12,7 +12,7 @@ async function resetStuckAiProcessingFlags() { .select() .from(videos) .where(sql` - (metadata->>'aiProcessing')::boolean = true + (metadata->>'aiProcessing')::boolean = true AND updated_at < ${tenMinutesAgo} `); @@ -32,7 +32,7 @@ async function resetStuckAiProcessingFlags() { metadata: { ...metadata, aiProcessing: false, - generationError: "AI processing was stuck and has been reset", + // generationError: "AI processing was stuck and has been reset", }, }) .where(sql`id = ${video.id}`); diff --git a/apps/web/utils/changelog.ts b/apps/web/utils/changelog.ts index 781fe20e65..1527f1f305 100644 --- a/apps/web/utils/changelog.ts +++ b/apps/web/utils/changelog.ts @@ -14,11 +14,12 @@ function parseFrontmatter(fileContent: string) { const match = frontmatterRegex.exec(fileContent); const frontMatterBlock = match![1]; const content = fileContent.replace(frontmatterRegex, "").trim(); - const frontMatterLines = frontMatterBlock.trim().split("\n"); + const frontMatterLines = frontMatterBlock!.trim().split("\n"); const metadata: Partial = {}; frontMatterLines.forEach((line) => { const [key, ...valueArr] = line.split(": "); + if (!key) return; let value = valueArr.join(": ").trim(); value = value.replace(/^['"](.*)['"]$/, "$1"); // Remove quotes metadata[key.trim() as keyof ChangelogMetadata] = value; diff --git a/packages/database/schema.ts b/packages/database/schema.ts index db44f12312..b58c8dd0a8 100644 --- a/packages/database/schema.ts +++ b/packages/database/schema.ts @@ -1,4 +1,4 @@ -import type { Folder } from "@cap/web-domain"; +import { Video, type Folder } from "@cap/web-domain"; import { boolean, customType, @@ -231,7 +231,7 @@ export const folders = mysqlTable( export const videos = mysqlTable( "videos", { - id: nanoId("id").notNull().primaryKey().unique(), + id: nanoId("id").notNull().primaryKey().unique().$type(), ownerId: nanoId("ownerId").notNull(), name: varchar("name", { length: 255 }).notNull().default("My Video"), bucket: nanoIdNullable("bucket"), @@ -452,7 +452,7 @@ export const sessionsRelations = relations(sessions, ({ one }) => ({ export const verificationTokensRelations = relations( verificationTokens, - ({}) => ({ + ({ }) => ({ // No relations defined }), ); diff --git a/packages/ui/src/components/Dropdown.tsx b/packages/ui/src/components/Dropdown.tsx index 290a5c6f6a..b764298b0d 100644 --- a/packages/ui/src/components/Dropdown.tsx +++ b/packages/ui/src/components/Dropdown.tsx @@ -57,9 +57,13 @@ DropdownMenuSubContent.displayName = const DropdownMenuContent = React.forwardRef< React.ElementRef, - React.ComponentPropsWithoutRef ->(({ className, sideOffset = 4, ...props }, ref) => ( - + React.ComponentPropsWithoutRef & { + container?: React.ComponentPropsWithoutRef< + typeof DropdownMenuPrimitive.Portal + >["container"]; + } +>(({ className, sideOffset = 4, container, ...props }, ref) => ( + Date: Mon, 25 Aug 2025 20:56:09 +0800 Subject: [PATCH 2/2] formatting --- .../[id]/components/NewSubfolderButton.tsx | 2 +- .../app/api/upload/[...route]/multipart.ts | 2 +- apps/web/app/s/[videoId]/Share.tsx | 2 +- apps/web/lib/effect-react-query.ts | 304 +++++++++--------- apps/web/lib/folder.ts | 12 +- packages/database/schema.ts | 4 +- 6 files changed, 163 insertions(+), 163 deletions(-) diff --git a/apps/web/app/(org)/dashboard/folder/[id]/components/NewSubfolderButton.tsx b/apps/web/app/(org)/dashboard/folder/[id]/components/NewSubfolderButton.tsx index efbad54854..77e4ce789a 100644 --- a/apps/web/app/(org)/dashboard/folder/[id]/components/NewSubfolderButton.tsx +++ b/apps/web/app/(org)/dashboard/folder/[id]/components/NewSubfolderButton.tsx @@ -1,10 +1,10 @@ "use client"; import { Button } from "@cap/ui"; +import type { Folder } from "@cap/web-domain"; import { faFolderPlus } from "@fortawesome/free-solid-svg-icons"; import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; import { useState } from "react"; -import { Folder } from "@cap/web-domain"; import { SubfolderDialog } from "./SubfolderDialog"; interface NewSubfolderButtonProps { diff --git a/apps/web/app/api/upload/[...route]/multipart.ts b/apps/web/app/api/upload/[...route]/multipart.ts index d9e59ae8d3..6f0bb1674a 100644 --- a/apps/web/app/api/upload/[...route]/multipart.ts +++ b/apps/web/app/api/upload/[...route]/multipart.ts @@ -2,7 +2,7 @@ import { db, updateIfDefined } from "@cap/database"; import { s3Buckets, videos } from "@cap/database/schema"; import { serverEnv } from "@cap/env"; import { zValidator } from "@hono/zod-validator"; -import { eq, and } from "drizzle-orm"; +import { and, eq } from "drizzle-orm"; import { Hono } from "hono"; import { z } from "zod"; import { withAuth } from "@/app/api/utils"; diff --git a/apps/web/app/s/[videoId]/Share.tsx b/apps/web/app/s/[videoId]/Share.tsx index 7788c19987..72d8260832 100644 --- a/apps/web/app/s/[videoId]/Share.tsx +++ b/apps/web/app/s/[videoId]/Share.tsx @@ -2,6 +2,7 @@ import type { userSelectProps } from "@cap/database/auth/session"; import type { comments as commentsSchema, videos } from "@cap/database/schema"; +import type { Video } from "@cap/web-domain"; import { useQuery } from "@tanstack/react-query"; import { startTransition, @@ -19,7 +20,6 @@ import { import { ShareVideo } from "./_components/ShareVideo"; import { Sidebar } from "./_components/Sidebar"; import { Toolbar } from "./_components/Toolbar"; -import { Video } from "@cap/web-domain"; const formatTime = (time: number) => { const minutes = Math.floor(time / 60); diff --git a/apps/web/lib/effect-react-query.ts b/apps/web/lib/effect-react-query.ts index c98cb3a946..f9d0d651fd 100644 --- a/apps/web/lib/effect-react-query.ts +++ b/apps/web/lib/effect-react-query.ts @@ -20,8 +20,8 @@ import * as Exit from "effect/Exit"; type Override = { [AKey in keyof TTargetA]: AKey extends keyof TTargetB - ? TTargetB[AKey] - : TTargetA[AKey]; + ? TTargetB[AKey] + : TTargetA[AKey]; }; export function makeUseEffectQuery( @@ -40,10 +40,10 @@ export function makeUseEffectQuery( UseQueryOptions, { queryFn?: - | (( - context: QueryFunctionContext, - ) => Effect.Effect) - | SkipToken; + | (( + context: QueryFunctionContext, + ) => Effect.Effect) + | SkipToken; } >, ): UseQueryResult { @@ -57,66 +57,66 @@ export function makeUseEffectQuery( ...(options as any), ...(typeof queryFn === "function" ? { - queryFn: async (args) => { - let queryEffect: Effect.Effect; - try { - queryEffect = queryFn(args); - } catch (e) { - throw new Cause.UnknownException(e, "queryFn threw"); - } - const effectToRun = queryEffect; - // .pipe( - // Effect.withSpan("useEffectQuery", { - // attributes: { - // queryKey: args.queryKey, - // queryFn: queryFn.toString(), - // }, - // }) - // ); - const result = await runtime.runPromiseExit(effectToRun, { - signal: args.signal, - }); - if (Exit.isFailure(result)) { - // we always throw the cause - throw result.cause; - } else { - return result.value; - } - }, - } + queryFn: async (args) => { + let queryEffect: Effect.Effect; + try { + queryEffect = queryFn(args); + } catch (e) { + throw new Cause.UnknownException(e, "queryFn threw"); + } + const effectToRun = queryEffect; + // .pipe( + // Effect.withSpan("useEffectQuery", { + // attributes: { + // queryKey: args.queryKey, + // queryFn: queryFn.toString(), + // }, + // }) + // ); + const result = await runtime.runPromiseExit(effectToRun, { + signal: args.signal, + }); + if (Exit.isFailure(result)) { + // we always throw the cause + throw result.cause; + } else { + return result.value; + } + }, + } : { queryFn }), ...(typeof throwOnError === "function" ? { - throwOnError: (error, query) => { - // this is safe because internally when we call useQuery we always throw the full cause or UnknownException - const cause = error as - | Cause.Cause - | Cause.UnknownException; - // if the cause is UnknownException, we always return true and throw it - if (Cause.isUnknownException(cause)) { - return true; - } - const failureOrCause = Cause.failureOrCause(cause); - if (throwOnDefect) { - // in this case options.throwOnError expects a TError - // the cause was a fail, so we have TError - if (Either.isLeft(failureOrCause)) { - // this is safe because if throwOnDefect is true then TExposedError is TError - const exposedError = - failureOrCause.left as unknown as TExposedError; - return throwOnError(exposedError, query); - } else { - // the cause was a die or interrupt, so we return true + throwOnError: (error, query) => { + // this is safe because internally when we call useQuery we always throw the full cause or UnknownException + const cause = error as + | Cause.Cause + | Cause.UnknownException; + // if the cause is UnknownException, we always return true and throw it + if (Cause.isUnknownException(cause)) { return true; } - } else { - // in this case options.throwOnError expects a Cause - // this is safe because if throwOnDefect is false then TExposedError is Cause - const exposedError = cause as unknown as TExposedError; - return throwOnError(exposedError, query); - } - }, - } + const failureOrCause = Cause.failureOrCause(cause); + if (throwOnDefect) { + // in this case options.throwOnError expects a TError + // the cause was a fail, so we have TError + if (Either.isLeft(failureOrCause)) { + // this is safe because if throwOnDefect is true then TExposedError is TError + const exposedError = + failureOrCause.left as unknown as TExposedError; + return throwOnError(exposedError, query); + } else { + // the cause was a die or interrupt, so we return true + return true; + } + } else { + // in this case options.throwOnError expects a Cause + // this is safe because if throwOnDefect is false then TExposedError is Cause + const exposedError = cause as unknown as TExposedError; + return throwOnError(exposedError, query); + } + }, + } : {}), }); @@ -127,36 +127,36 @@ export function makeUseEffectQuery( return target.error ? throwOnDefect ? Either.match( - Cause.failureOrCause( - target.error as unknown as Cause.Cause, // this is safe because we always throw the full cause and we know that error is not null - ), - { - onLeft: (error) => error as unknown as TExposedError, // if throwOnDefect is true then TExposedError is TError - onRight: (_cause) => { - throw new Error( - "non fail cause with throwOnDefect: true should have thrown already", - ); + Cause.failureOrCause( + target.error as unknown as Cause.Cause, // this is safe because we always throw the full cause and we know that error is not null + ), + { + onLeft: (error) => error as unknown as TExposedError, // if throwOnDefect is true then TExposedError is TError + onRight: (_cause) => { + throw new Error( + "non fail cause with throwOnDefect: true should have thrown already", + ); + }, }, - }, - ) + ) : target.error // if throwOnDefect is false then TExposedError is Cause, and base error is always Cause : null; } else if (prop === "failureReason") { return target.failureReason ? throwOnDefect ? Either.match( - Cause.failureOrCause( - target.failureReason as unknown as Cause.Cause, // this is safe because we always throw the full cause and we know that error is not null - ), - { - onLeft: (error) => error as unknown as TExposedError, // if throwOnDefect is true then TExposedError is TError - onRight: (_cause) => { - throw new Error( - "non fail cause with throwOnDefect: true should have thrown already", - ); + Cause.failureOrCause( + target.failureReason as unknown as Cause.Cause, // this is safe because we always throw the full cause and we know that error is not null + ), + { + onLeft: (error) => error as unknown as TExposedError, // if throwOnDefect is true then TExposedError is TError + onRight: (_cause) => { + throw new Error( + "non fail cause with throwOnDefect: true should have thrown already", + ); + }, }, - }, - ) + ) : target.failureReason // if throwOnDefect is false then TExposedError is Cause, and base error is always Cause : null; } @@ -250,65 +250,65 @@ export function makeUseEffectMutation( mutationFn: typeof mutationFn === "function" ? async (variables: TVariables) => { - let mutationEffect: Effect.Effect; - try { - mutationEffect = mutationFn(variables); - } catch (e) { - throw new Cause.UnknownException(e, "mutationFn threw"); - } - const effectToRun = mutationEffect; - // .pipe( - // Effect.withSpan("useEffectMutation", { - // attributes: { - // mutationFn: mutationFn.toString(), - // }, - // }) - // ); - const result = await runtime.runPromiseExit(effectToRun); - console.log({ result }); - if (Exit.isFailure(result)) { - // we always throw the cause - throw result.cause; - } else { - return result.value; + let mutationEffect: Effect.Effect; + try { + mutationEffect = mutationFn(variables); + } catch (e) { + throw new Cause.UnknownException(e, "mutationFn threw"); + } + const effectToRun = mutationEffect; + // .pipe( + // Effect.withSpan("useEffectMutation", { + // attributes: { + // mutationFn: mutationFn.toString(), + // }, + // }) + // ); + const result = await runtime.runPromiseExit(effectToRun); + console.log({ result }); + if (Exit.isFailure(result)) { + // we always throw the cause + throw result.cause; + } else { + return result.value; + } } - } : mutationFn, throwOnError: typeof throwOnError === "function" ? (error: Cause.Cause) => { - // this is safe because internally when we call useQuery we always throw the full cause or UnknownException - const cause = error as - | Cause.Cause - | Cause.UnknownException; + // this is safe because internally when we call useQuery we always throw the full cause or UnknownException + const cause = error as + | Cause.Cause + | Cause.UnknownException; - // if the cause is UnknownException, we always return true and throw it - if (Cause.isUnknownException(cause)) { - return true; - } + // if the cause is UnknownException, we always return true and throw it + if (Cause.isUnknownException(cause)) { + return true; + } - const failureOrCause = Cause.failureOrCause(cause); + const failureOrCause = Cause.failureOrCause(cause); - if (throwOnDefect) { - // in this case options.throwOnError expects a TError + if (throwOnDefect) { + // in this case options.throwOnError expects a TError - // the cause was a fail, so we have TError - if (Either.isLeft(failureOrCause)) { - // this is safe because if throwOnDefect is true then TExposedError is TError - const exposedError = - failureOrCause.left as unknown as TExposedError; - return throwOnError(exposedError); + // the cause was a fail, so we have TError + if (Either.isLeft(failureOrCause)) { + // this is safe because if throwOnDefect is true then TExposedError is TError + const exposedError = + failureOrCause.left as unknown as TExposedError; + return throwOnError(exposedError); + } else { + // the cause was a die or interrupt, so we return true + return true; + } } else { - // the cause was a die or interrupt, so we return true - return true; + // in this case options.throwOnError expects a Cause + // this is safe because if throwOnDefect is false then TExposedError is Cause + const exposedError = cause as unknown as TExposedError; + return throwOnError(exposedError); } - } else { - // in this case options.throwOnError expects a Cause - // this is safe because if throwOnDefect is false then TExposedError is Cause - const exposedError = cause as unknown as TExposedError; - return throwOnError(exposedError); } - } : throwOnError, // onMutate: // typeof onMutate === "function" @@ -403,36 +403,36 @@ export function makeUseEffectMutation( return target.error ? throwOnDefect ? Either.match( - Cause.failureOrCause( - target.error as unknown as Cause.Cause, // this is safe because we always throw the full cause and we know that error is not null - ), - { - onLeft: (error) => error as unknown as TExposedError, // if throwOnDefect is true then TExposedError is TError - onRight: (_cause) => { - throw new Error( - "non fail cause with throwOnDefect: true should have thrown already", - ); + Cause.failureOrCause( + target.error as unknown as Cause.Cause, // this is safe because we always throw the full cause and we know that error is not null + ), + { + onLeft: (error) => error as unknown as TExposedError, // if throwOnDefect is true then TExposedError is TError + onRight: (_cause) => { + throw new Error( + "non fail cause with throwOnDefect: true should have thrown already", + ); + }, }, - }, - ) + ) : target.error // if throwOnDefect is false then TExposedError is Cause, and base error is always Cause : null; } else if (prop === "failureReason") { return target.failureReason ? throwOnDefect ? Either.match( - Cause.failureOrCause( - target.failureReason as unknown as Cause.Cause, // this is safe because we always throw the full cause and we know that error is not null - ), - { - onLeft: (error) => error as unknown as TExposedError, // if throwOnDefect is true then TExposedError is TError - onRight: (_cause) => { - throw new Error( - "non fail cause with throwOnDefect: true should have thrown already", - ); + Cause.failureOrCause( + target.failureReason as unknown as Cause.Cause, // this is safe because we always throw the full cause and we know that error is not null + ), + { + onLeft: (error) => error as unknown as TExposedError, // if throwOnDefect is true then TExposedError is TError + onRight: (_cause) => { + throw new Error( + "non fail cause with throwOnDefect: true should have thrown already", + ); + }, }, - }, - ) + ) : target.failureReason // if throwOnDefect is false then TExposedError is Cause, and base error is always Cause : null; } else if (prop === "mutate") { diff --git a/apps/web/lib/folder.ts b/apps/web/lib/folder.ts index ff2e13253a..ce55abdd93 100644 --- a/apps/web/lib/folder.ts +++ b/apps/web/lib/folder.ts @@ -113,7 +113,7 @@ async function getSharedSpacesForVideos(videoIds: string[]) { // Add space-level sharing spaceSharing.forEach((space) => { - const spaces = sharedSpacesMap[space.videoId] ?? [] + const spaces = sharedSpacesMap[space.videoId] ?? []; sharedSpacesMap[space.videoId] = spaces; spaces.push({ id: space.id, @@ -215,8 +215,8 @@ export async function getVideosByFolderId(folderId: string) { totalReactions: video.totalReactions, sharedOrganizations: Array.isArray(video.sharedOrganizations) ? video.sharedOrganizations.filter( - (organization) => organization.id !== null, - ) + (organization) => organization.id !== null, + ) : [], sharedSpaces: Array.isArray(sharedSpacesMap[video.id]) ? sharedSpacesMap[video.id] @@ -224,9 +224,9 @@ export async function getVideosByFolderId(folderId: string) { ownerName: video.ownerName ?? "", metadata: video.metadata as | { - customCreatedAt?: string; - [key: string]: unknown; - } + customCreatedAt?: string; + [key: string]: unknown; + } | undefined, hasPassword: video.hasPassword === 1, foldersData: [], // Empty array since videos in a folder don't need folder data diff --git a/packages/database/schema.ts b/packages/database/schema.ts index b58c8dd0a8..21afc96f28 100644 --- a/packages/database/schema.ts +++ b/packages/database/schema.ts @@ -1,4 +1,4 @@ -import { Video, type Folder } from "@cap/web-domain"; +import type { Folder, Video } from "@cap/web-domain"; import { boolean, customType, @@ -452,7 +452,7 @@ export const sessionsRelations = relations(sessions, ({ one }) => ({ export const verificationTokensRelations = relations( verificationTokens, - ({ }) => ({ + ({}) => ({ // No relations defined }), );