Files
Zero/apps/mail/app/root.tsx
Adam bcf2d17b44 Integrate Dormroom for durable object management and queryable storage (#1873)
## Description

This PR integrates Dormroom for managing durable objects and enabling queryable storage for email agents. It improves how we handle agent instances and database operations, making our system more robust and maintainable.

## Type of Change

-  New feature (non-breaking change which adds functionality)
-  Performance improvement

## Areas Affected

- [x] Email Integration (Gmail, IMAP, etc.)
- [x] Data Storage/Management
- [x] API Endpoints

## Testing Done

- [x] Manual testing performed

## Checklist

- [x] I have performed a self-review of my code
- [x] My changes generate no new warnings

## Key Changes

1. Added Dormroom dependency for better durable object management
2. Implemented `@Migratable` and `@Queryable` decorators for ZeroDriver class
3. Created new `getZeroClient` function to replace direct agent instantiation
4. Fixed redirect header handling to prevent header persistence after redirects
5. Improved folder sync logic to skip unnecessary operations for aggregate instances
6. Standardized environment variable access through instance properties
7. Added structured database migrations for thread storage
8. Enhanced type safety with proper environment type definitions

These changes improve our agent architecture and make database operations more reliable while maintaining backward compatibility.

<!-- This is an auto-generated comment: release notes by coderabbit.ai -->

## Summary by CodeRabbit

* **New Features**
  * Introduced new methods for database size reporting and conditional folder synchronization.
  * Added structured environment typing for improved configuration management.

* **Refactor**
  * Standardized environment variable access throughout the server and agent components.
  * Migrated database schema management to a migration system.
  * Updated client acquisition logic to utilize execution context.

* **Bug Fixes**
  * Improved redirect handling in the mail provider to clean up response headers after navigation.
  * Adjusted redirect logic to only trigger under specific authorization conditions.

* **Chores**
  * Added the "dormroom" dependency to the server application.

<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2025-08-01 09:43:42 -07:00

220 lines
6.8 KiB
TypeScript

import {
isRouteErrorResponse,
Links,
Meta,
Outlet,
Scripts,
ScrollRestoration,
useNavigate,
type MetaFunction,
} from 'react-router';
import { Analytics as DubAnalytics } from '@dub/analytics/react';
import { ServerProviders } from '@/providers/server-providers';
import { ClientProviders } from '@/providers/client-providers';
import { createTRPCClient, httpBatchLink } from '@trpc/client';
import { useEffect, type PropsWithChildren } from 'react';
import type { AppRouter } from '@zero/server/trpc';
import { Button } from '@/components/ui/button';
import { getLocale } from '@/paraglide/runtime';
import { siteConfig } from '@/lib/site-config';
import { signOut } from '@/lib/auth-client';
import type { Route } from './+types/root';
import { AlertCircle } from 'lucide-react';
import { m } from '@/paraglide/messages';
import { ArrowLeft } from 'lucide-react';
import * as Sentry from '@sentry/react';
import superjson from 'superjson';
import './globals.css';
const getUrl = () => import.meta.env.VITE_PUBLIC_BACKEND_URL + '/api/trpc';
export const getServerTrpc = (req: Request) =>
createTRPCClient<AppRouter>({
links: [
httpBatchLink({
maxItems: 1,
url: getUrl(),
transformer: superjson,
headers: req.headers,
}),
],
});
export const meta: MetaFunction = () => {
return [
{ title: siteConfig.title },
{ name: 'description', content: siteConfig.description },
{ property: 'og:title', content: siteConfig.title },
{ property: 'og:description', content: siteConfig.description },
{ property: 'og:image', content: siteConfig.openGraph.images[0].url },
{ property: 'og:url', content: siteConfig.alternates.canonical },
{ property: 'og:type', content: 'website' },
{ rel: 'manifest', href: '/manifest.webmanifest' },
];
};
export function Layout({ children }: PropsWithChildren) {
return (
<html lang={getLocale()} suppressHydrationWarning>
<head>
<meta charSet="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<meta name="theme-color" content="#141414" media="(prefers-color-scheme: dark)" />
<meta name="theme-color" content="#ffffff" media="(prefers-color-scheme: light)" />
<link rel="manifest" href="/manifest.json" />
<Meta />
{import.meta.env.REACT_SCAN && (
<script crossOrigin="anonymous" src="//unpkg.com/react-scan/dist/auto.global.js" />
)}
<Links />
</head>
<body className="antialiased">
<ServerProviders connectionId={null}>
<ClientProviders>{children}</ClientProviders>
<DubAnalytics
domainsConfig={{
refer: 'mail0.com',
}}
/>
</ServerProviders>
<ScrollRestoration />
<Scripts />
</body>
</html>
);
}
// export function HydrateFallback() {
// return (
// <div className="flex h-screen w-full items-center justify-center">
// <Loader2 className="h-10 w-10 animate-spin" />
// </div>
// );
// }
export default function App() {
return <Outlet />;
}
export function ErrorBoundary({ error }: Route.ErrorBoundaryProps) {
let message = 'Oops!';
let details = 'An unexpected error occurred.';
let stack: string | undefined;
if (isRouteErrorResponse(error)) {
message = error.status === 404 ? '404' : 'Error';
details =
error.status === 404 ? 'The requested page could not be found.' : error.statusText || details;
if (error.status === 404) {
return <NotFound />;
}
} else if (import.meta.env.DEV && error && error instanceof Error) {
details = error.message;
stack = error.stack;
}
useEffect(() => {
console.error(error);
console.error({ message, details, stack });
// Report error to Sentry
if (isRouteErrorResponse(error)) {
Sentry.captureException(new Error(`Route Error ${error.status}: ${error.statusText}`), {
tags: {
type: 'route_error',
status: error.status,
},
extra: {
statusText: error.statusText,
data: error.data,
},
});
} else if (error instanceof Error) {
Sentry.captureException(error, {
tags: {
type: 'app_error',
},
});
} else {
Sentry.captureException(new Error('Unknown error occurred'), {
tags: {
type: 'unknown_error',
},
extra: {
error: error,
},
});
}
}, [error, message, details, stack]);
return (
<div className="dark:bg-background flex w-full items-center justify-center bg-white text-center">
<div className="flex-col items-center justify-center md:flex dark:text-gray-100">
{/* Message */}
<div className="space-y-2">
<h2 className="text-2xl font-semibold tracking-tight">Something went wrong!</h2>
<p className="text-muted-foreground">See the console for more information.</p>
<pre className="text-muted-foreground">{JSON.stringify(error, null, 2)}</pre>
</div>
<div className="mt-2 flex gap-2">
<Button
variant="outline"
onClick={() => window.location.reload()}
className="text-muted-foreground gap-2"
>
Refresh
</Button>
<Button
variant="outline"
onClick={async () => {
await signOut();
window.location.href = '/login';
}}
className="text-muted-foreground gap-2"
>
Log Out and Refresh
</Button>
</div>
</div>
</div>
);
}
function NotFound() {
const navigate = useNavigate();
return (
<div className="dark:bg-background flex w-full items-center justify-center bg-white text-center">
<div className="flex-col items-center justify-center md:flex dark:text-gray-100">
<div className="relative">
<h1 className="text-muted-foreground/20 select-none text-[150px] font-bold">404</h1>
<div className="absolute left-1/2 top-1/2 -translate-x-1/2 -translate-y-1/2">
<AlertCircle className="text-muted-foreground h-20 w-20" />
</div>
</div>
{/* Message */}
<div className="space-y-2">
<h2 className="text-2xl font-semibold tracking-tight">
{m['pages.error.notFound.title']()}
</h2>
<p className="text-muted-foreground">{m['pages.error.notFound.description']()}</p>
</div>
{/* Buttons */}
<div className="mt-2 flex justify-center gap-2">
<Button
variant="outline"
onClick={() => navigate(-1)}
className="text-muted-foreground gap-2"
>
<ArrowLeft className="h-4 w-4" />
{m['pages.error.notFound.goBack']()}
</Button>
</div>
</div>
</div>
);
}