This commit is contained in:
Nate Kelley 2025-08-14 22:14:08 -06:00
parent 09d1d6d521
commit 5eed7b6cab
No known key found for this signature in database
GPG Key ID: FD90372AB8D98B4F
5 changed files with 923 additions and 0 deletions

View File

@ -9,6 +9,8 @@ import {
} from '@/api/buster_rest/users';
import type { ISidebarGroup } from '@/components/ui/sidebar';
import { useMemoizedFn } from '@/hooks/useMemoizedFn';
import { assetParamsToRoute } from '@/lib/assets/assetParamsToRoute';
import { useMount } from '../../../hooks/useMount';
import { useWhyDidYouUpdate } from '../../../hooks/useWhyDidYouUpdate';
import { assetTypeToIcon } from '../icons/assetIcons';
@ -17,6 +19,10 @@ export const useFavoriteSidebarPanel = () => {
const { mutateAsync: updateUserFavorites } = useUpdateUserFavorites();
const { mutateAsync: deleteUserFavorite } = useDeleteUserFavorite();
useMount(() => {
assetParamsToRoute({ assetType: 'chat', assetId: '123' });
});
// const { chatId, metricId, dashboardId, collectionId, reportId } = useParams() as {
// chatId: string | undefined;
// metricId: string | undefined;

View File

@ -0,0 +1,176 @@
# Asset Route Builder
A type-safe route builder for TanStack Start/Router that dynamically generates routes based on asset types and parameters.
## Overview
The Asset Route Builder provides a clean, type-safe way to generate routes for different asset types (chats, metrics, dashboards, reports) with various parameter combinations. It ensures that the generated routes match the available route files in your TanStack Start application.
## Features
- **Type Safety**: All routes are validated against `FileRouteTypes['id']` from the generated route tree
- **Fluent API**: Clean builder pattern for constructing routes
- **Dynamic Route Generation**: Automatically determines the correct route based on provided parameters
- **Parameter Extraction**: Provides params object for navigation
## Available Routes
The system supports the following route patterns:
### Single Asset Routes
- `/app/chats/$chatId`
- `/app/dashboards/$dashboardId`
- `/app/metrics/$metricId`
- `/app/reports/$reportId`
### Chat Context Routes
- `/app/chats/$chatId/dashboard/$dashboardId`
- `/app/chats/$chatId/metrics/$metricId`
- `/app/chats/$chatId/report/$reportId`
### Nested Context Routes
- `/app/chats/$chatId/dashboard/$dashboardId/metrics/$metricId`
- `/app/chats/$chatId/report/$reportId/metrics/$metricId`
## Usage
### Basic Usage with `assetParamsToRoute`
```typescript
import { assetParamsToRoute } from '@/lib/assets/assetParamsToRoute';
// Navigate to a single asset
const chatRoute = assetParamsToRoute({
assetType: 'chat',
assetId: 'chat-123',
});
// Returns: '/app/chats/$chatId'
// Navigate to a metric within a chat
const metricInChatRoute = assetParamsToRoute({
assetType: 'metric',
assetId: 'metric-456',
chatId: 'chat-123',
});
// Returns: '/app/chats/$chatId/metrics/$metricId'
// Navigate to a metric within a dashboard in a chat
const complexRoute = assetParamsToRoute({
assetType: 'metric',
assetId: 'metric-789',
chatId: 'chat-123',
dashboardId: 'dash-456',
});
// Returns: '/app/chats/$chatId/dashboard/$dashboardId/metrics/$metricId'
```
### Using the RouteBuilder
```typescript
import { createRouteBuilder } from '@/lib/assets/assetParamsToRoute';
// Build routes step by step
const builder = createRouteBuilder()
.withChat('chat-123')
.withDashboard('dash-456')
.withMetric('metric-789');
const route = builder.build();
// Returns: '/app/chats/$chatId/dashboard/$dashboardId/metrics/$metricId'
const params = builder.getParams();
// Returns: { chatId: 'chat-123', dashboardId: 'dash-456', metricId: 'metric-789' }
```
### Integration with TanStack Router
```typescript
import { useNavigate } from '@tanstack/react-router';
import { createAssetNavigation } from '@/lib/assets/assetParamsToRoute.example';
function MyComponent() {
const navigate = useNavigate();
const handleNavigation = () => {
const { route, params } = createAssetNavigation({
assetType: 'dashboard',
assetId: 'dash-123',
chatId: 'chat-456',
});
// Use with your navigation API
navigate({ to: route, params });
};
return <button onClick={handleNavigation}>Go to Dashboard</button>;
}
```
## API Reference
### `assetParamsToRoute(params: AssetParamsToRoute): RouteFilePaths`
Main function to convert asset parameters to a route path.
#### Parameters
- `params`: An object containing:
- `assetType`: The type of asset ('chat' | 'metric' | 'dashboard' | 'report')
- `assetId`: The ID of the main asset
- Additional optional context parameters (chatId, metricId, dashboardId, reportId)
#### Returns
A type-safe route path matching `FileRouteTypes['id']`.
### `createRouteBuilder(): RouteBuilder`
Creates a new RouteBuilder instance for fluent route construction.
#### RouteBuilder Methods
- `withChat(chatId: string)`: Add a chat ID to the route
- `withMetric(metricId: string)`: Add a metric ID to the route
- `withDashboard(dashboardId: string)`: Add a dashboard ID to the route
- `withReport(reportId: string)`: Add a report ID to the route
- `build()`: Build the final route path
- `getParams()`: Get the params object for navigation
## Type Definitions
```typescript
type ChatParamsToRoute = {
assetType: 'chat';
assetId: string;
metricId?: string;
dashboardId?: string;
reportId?: string;
};
type MetricParamsToRoute = {
assetType: 'metric';
assetId: string;
dashboardId?: string;
reportId?: string;
chatId?: string;
};
type DashboardParamsToRoute = {
assetType: 'dashboard';
assetId: string;
metricId?: string;
reportId?: string;
chatId?: string;
};
type ReportParamsToRoute = {
assetType: 'report';
assetId: string;
metricId?: string;
chatId?: string;
};
```
## Examples
See `assetParamsToRoute.example.tsx` for comprehensive usage examples and `assetParamsToRoute.test.ts` for test cases.

View File

@ -0,0 +1,264 @@
import { type Navigate, UseNavigateResult, useNavigate } from '@tanstack/react-router';
import type { FileRouteTypes } from '@/routeTree.gen';
import { assetParamsToRoute, createRouteBuilder } from './assetParamsToRoute';
/**
* Example usage of assetParamsToRoute
*
* Note: These examples show how to get the correct route paths.
* For actual navigation, you would use these routes with your router's navigation API.
*/
// Example 1: Get route for a single asset
export function getSingleAssetRoutes() {
// Chat route
const chatRoute = assetParamsToRoute({
assetType: 'chat',
assetId: 'chat-123',
});
console.log(chatRoute); // '/app/chats/$chatId'
// Dashboard route
const dashboardRoute = assetParamsToRoute({
assetType: 'dashboard',
assetId: 'dash-123',
});
console.log(dashboardRoute); // '/app/dashboards/$dashboardId'
// Metric route
const metricRoute = assetParamsToRoute({
assetType: 'metric',
assetId: 'metric-123',
});
console.log(metricRoute); // '/app/metrics/$metricId'
// Report route
const reportRoute = assetParamsToRoute({
assetType: 'report',
assetId: 'report-123',
});
console.log(reportRoute); // '/app/reports/$reportId'
}
// Example 2: Get routes for assets within chat context
export function getChatContextRoutes() {
// Metric within a chat
const metricInChat = assetParamsToRoute({
assetType: 'metric',
assetId: 'metric-123',
chatId: 'chat-456',
});
console.log(metricInChat); // '/app/chats/$chatId/metrics/$metricId'
// Dashboard within a chat
const dashboardInChat = assetParamsToRoute({
assetType: 'dashboard',
assetId: 'dash-123',
chatId: 'chat-456',
});
console.log(dashboardInChat); // '/app/chats/$chatId/dashboard/$dashboardId'
// Report within a chat
const reportInChat = assetParamsToRoute({
assetType: 'report',
assetId: 'report-123',
chatId: 'chat-456',
});
console.log(reportInChat); // '/app/chats/$chatId/report/$reportId'
}
// Example 3: Get routes for nested contexts
export function getNestedContextRoutes() {
// Metric within a dashboard within a chat
const metricInDashboardInChat = assetParamsToRoute({
assetType: 'metric',
assetId: 'metric-123',
chatId: 'chat-456',
dashboardId: 'dash-789',
});
console.log(metricInDashboardInChat); // '/app/chats/$chatId/dashboard/$dashboardId/metrics/$metricId'
// Metric within a report within a chat
const metricInReportInChat = assetParamsToRoute({
assetType: 'metric',
assetId: 'metric-123',
chatId: 'chat-456',
reportId: 'report-789',
});
console.log(metricInReportInChat); // '/app/chats/$chatId/report/$reportId/metrics/$metricId'
}
// Example 4: Using the RouteBuilder for more control
export function usingRouteBuilder() {
// Build a route step by step
const route1 = createRouteBuilder().withChat('chat-123').withMetric('metric-456').build();
console.log(route1); // '/app/chats/$chatId/metrics/$metricId'
// Get the params for navigation
const builder = createRouteBuilder()
.withChat('chat-123')
.withDashboard('dash-456')
.withMetric('metric-789');
const route = builder.build();
const params = builder.getParams();
console.log(route); // '/app/chats/$chatId/dashboard/$dashboardId/metrics/$metricId'
console.log(params); // { chatId: 'chat-123', dashboardId: 'dash-456', metricId: 'metric-789' }
}
// Example 5: Type-safe route generation
export function typeSafeRoutes() {
// All of these return type-safe FileRouteTypes['id'] values
const routes: FileRouteTypes['id'][] = [
assetParamsToRoute({ assetType: 'chat', assetId: '1' }),
assetParamsToRoute({ assetType: 'dashboard', assetId: '2' }),
assetParamsToRoute({ assetType: 'metric', assetId: '3' }),
assetParamsToRoute({ assetType: 'report', assetId: '4' }),
assetParamsToRoute({ assetType: 'metric', assetId: '5', chatId: '1' }),
assetParamsToRoute({ assetType: 'dashboard', assetId: '6', chatId: '1' }),
assetParamsToRoute({ assetType: 'dashboard', assetId: '7', chatId: '1', metricId: '5' }),
];
return routes;
}
// Example 6: Helper function for navigation params
export function getNavigationParams(params: Parameters<typeof assetParamsToRoute>[0]) {
const route = assetParamsToRoute(params);
// Build the params object for navigation
const navParams: Record<string, string> = {};
// Add the main asset ID
switch (params.assetType) {
case 'chat':
navParams.chatId = params.assetId;
break;
case 'metric':
navParams.metricId = params.assetId;
break;
case 'dashboard':
navParams.dashboardId = params.assetId;
break;
case 'report':
navParams.reportId = params.assetId;
break;
}
// Add any context params
if ('chatId' in params && params.chatId) navParams.chatId = params.chatId;
if ('metricId' in params && params.metricId) navParams.metricId = params.metricId;
if ('dashboardId' in params && params.dashboardId) navParams.dashboardId = params.dashboardId;
if ('reportId' in params && params.reportId) navParams.reportId = params.reportId;
return { route, params: navParams };
}
/**
* Example: Creating links with proper type safety
*/
export function LinkExample() {
// All of these will be type-safe RouteFilePaths
const routes = {
chat: assetParamsToRoute({ assetType: 'chat', assetId: 'chat-123' }),
metric: assetParamsToRoute({ assetType: 'metric', assetId: 'metric-123' }),
dashboard: assetParamsToRoute({ assetType: 'dashboard', assetId: 'dash-123' }),
report: assetParamsToRoute({ assetType: 'report', assetId: 'report-123' }),
// Complex routes
chatWithMetric: assetParamsToRoute({
assetType: 'chat',
assetId: 'chat-123',
metricId: 'metric-456',
}),
dashboardInChat: assetParamsToRoute({
assetType: 'dashboard',
assetId: 'dash-123',
chatId: 'chat-456',
}),
fullRoute: assetParamsToRoute({
assetType: 'dashboard',
assetId: 'dash-123',
chatId: 'chat-456',
metricId: 'metric-789',
}),
};
return (
<nav>
{Object.entries(routes).map(([key, route]) => (
<a key={key} href={route}>
{key}: {route}
</a>
))}
</nav>
);
}
/**
* Example: Helper for creating asset navigation data
*
* This would be used with your router's navigation API.
* For example, with TanStack Router:
* const { route, params } = createAssetNavigation({...});
* navigate({ to: route, params });
*/
export function createAssetNavigation(params: Parameters<typeof assetParamsToRoute>[0]) {
const route = assetParamsToRoute(params);
// Build navigation params based on the route
const navParams: Record<string, string> = {};
// Always include the main asset ID with its proper param name
switch (params.assetType) {
case 'chat':
navParams.chatId = params.assetId;
break;
case 'metric':
navParams.metricId = params.assetId;
break;
case 'dashboard':
navParams.dashboardId = params.assetId;
break;
case 'report':
navParams.reportId = params.assetId;
break;
}
// Add any additional context params
if ('chatId' in params && params.chatId) navParams.chatId = params.chatId;
if ('metricId' in params && params.metricId) navParams.metricId = params.metricId;
if ('dashboardId' in params && params.dashboardId) navParams.dashboardId = params.dashboardId;
if ('reportId' in params && params.reportId) navParams.reportId = params.reportId;
return {
route,
params: navParams,
// Helper methods
assetParamsToRoute,
createRouteBuilder,
};
}
export const TestComponent = () => {
const navigate = useNavigate();
const test = (params: Parameters<typeof Navigate>[0]) => {
console.log(params);
};
test({
to: '/asdf',
});
navigate({
to: '/app/chats/$chatId',
params: {
chatId: '123',
},
});
return <div></div>;
};

View File

@ -0,0 +1,221 @@
import { describe, it, expect } from 'vitest';
import type { FileRouteTypes } from '@/routeTree.gen';
import { assetParamsToRoute, createRouteBuilder } from './assetParamsToRoute';
type RouteFilePaths = FileRouteTypes['id'];
describe('assetParamsToRoute', () => {
describe('RouteBuilder', () => {
it('should build single asset routes correctly', () => {
// Test single chat route
const chatRoute = createRouteBuilder().withChat('chat-123').build();
expect(chatRoute).toBe('/app/chats/$chatId');
// Test single dashboard route
const dashboardRoute = createRouteBuilder().withDashboard('dash-123').build();
expect(dashboardRoute).toBe('/app/dashboards/$dashboardId');
// Test single metric route
const metricRoute = createRouteBuilder().withMetric('metric-123').build();
expect(metricRoute).toBe('/app/metrics/$metricId');
// Test single report route
const reportRoute = createRouteBuilder().withReport('report-123').build();
expect(reportRoute).toBe('/app/reports/$reportId');
});
it('should build chat combination routes correctly', () => {
// Chat + Dashboard
const chatDashRoute = createRouteBuilder()
.withChat('chat-123')
.withDashboard('dash-456')
.build();
expect(chatDashRoute).toBe('/app/chats/$chatId/dashboard/$dashboardId');
// Chat + Metric
const chatMetricRoute = createRouteBuilder()
.withChat('chat-123')
.withMetric('metric-456')
.build();
expect(chatMetricRoute).toBe('/app/chats/$chatId/metrics/$metricId');
// Chat + Report
const chatReportRoute = createRouteBuilder()
.withChat('chat-123')
.withReport('report-456')
.build();
expect(chatReportRoute).toBe('/app/chats/$chatId/report/$reportId');
// Chat + Dashboard + Metric
const chatDashMetricRoute = createRouteBuilder()
.withChat('chat-123')
.withDashboard('dash-456')
.withMetric('metric-789')
.build();
expect(chatDashMetricRoute).toBe(
'/app/chats/$chatId/dashboard/$dashboardId/metrics/$metricId'
);
// Chat + Report + Metric
const chatReportMetricRoute = createRouteBuilder()
.withChat('chat-123')
.withReport('report-456')
.withMetric('metric-789')
.build();
expect(chatReportMetricRoute).toBe('/app/chats/$chatId/report/$reportId/metrics/$metricId');
});
it('should maintain parameter values in state', () => {
const builder = createRouteBuilder()
.withChat('chat-123')
.withDashboard('dash-456')
.withMetric('metric-789');
const params = builder.getParams();
expect(params).toEqual({
chatId: 'chat-123',
dashboardId: 'dash-456',
metricId: 'metric-789',
});
});
});
describe('assetParamsToRoute function', () => {
it('should handle chat asset type correctly', () => {
// Chat only
const chatRoute = assetParamsToRoute({
assetType: 'chat',
assetId: 'chat-123',
});
expect(chatRoute).toBe('/app/chats/$chatId');
// Chat with dashboard
const chatDashRoute = assetParamsToRoute({
assetType: 'chat',
assetId: 'chat-123',
dashboardId: 'dash-456',
});
expect(chatDashRoute).toBe('/app/chats/$chatId/dashboard/$dashboardId');
// Chat with metric
const chatMetricRoute = assetParamsToRoute({
assetType: 'chat',
assetId: 'chat-123',
metricId: 'metric-456',
});
expect(chatMetricRoute).toBe('/app/chats/$chatId/metrics/$metricId');
// Chat with dashboard and metric
const chatDashMetricRoute = assetParamsToRoute({
assetType: 'chat',
assetId: 'chat-123',
dashboardId: 'dash-456',
metricId: 'metric-789',
});
expect(chatDashMetricRoute).toBe(
'/app/chats/$chatId/dashboard/$dashboardId/metrics/$metricId'
);
});
it('should handle metric asset type correctly', () => {
// Metric only
const metricRoute = assetParamsToRoute({
assetType: 'metric',
assetId: 'metric-123',
});
expect(metricRoute).toBe('/app/metrics/$metricId');
// Metric with chat
const metricChatRoute = assetParamsToRoute({
assetType: 'metric',
assetId: 'metric-123',
chatId: 'chat-456',
});
expect(metricChatRoute).toBe('/app/chats/$chatId/metrics/$metricId');
// Metric with chat and dashboard
const metricChatDashRoute = assetParamsToRoute({
assetType: 'metric',
assetId: 'metric-123',
chatId: 'chat-456',
dashboardId: 'dash-789',
});
expect(metricChatDashRoute).toBe(
'/app/chats/$chatId/dashboard/$dashboardId/metrics/$metricId'
);
});
it('should handle dashboard asset type correctly', () => {
// Dashboard only
const dashRoute = assetParamsToRoute({
assetType: 'dashboard',
assetId: 'dash-123',
});
expect(dashRoute).toBe('/app/dashboards/$dashboardId');
// Dashboard with chat
const dashChatRoute = assetParamsToRoute({
assetType: 'dashboard',
assetId: 'dash-123',
chatId: 'chat-456',
});
expect(dashChatRoute).toBe('/app/chats/$chatId/dashboard/$dashboardId');
// Dashboard with chat and metric
const dashChatMetricRoute = assetParamsToRoute({
assetType: 'dashboard',
assetId: 'dash-123',
chatId: 'chat-456',
metricId: 'metric-789',
});
expect(dashChatMetricRoute).toBe(
'/app/chats/$chatId/dashboard/$dashboardId/metrics/$metricId'
);
});
it('should handle report asset type correctly', () => {
// Report only
const reportRoute = assetParamsToRoute({
assetType: 'report',
assetId: 'report-123',
});
expect(reportRoute).toBe('/app/reports/$reportId');
// Report with chat
const reportChatRoute = assetParamsToRoute({
assetType: 'report',
assetId: 'report-123',
chatId: 'chat-456',
});
expect(reportChatRoute).toBe('/app/chats/$chatId/report/$reportId');
// Report with chat and metric
const reportChatMetricRoute = assetParamsToRoute({
assetType: 'report',
assetId: 'report-123',
chatId: 'chat-456',
metricId: 'metric-789',
});
expect(reportChatMetricRoute).toBe('/app/chats/$chatId/report/$reportId/metrics/$metricId');
});
});
describe('Type safety tests', () => {
it('should enforce type safety on routes', () => {
// This is a compile-time test - these should all be valid RouteFilePaths
const routes: RouteFilePaths[] = [
createRouteBuilder().withChat('1').build(),
createRouteBuilder().withDashboard('1').build(),
createRouteBuilder().withMetric('1').build(),
createRouteBuilder().withReport('1').build(),
createRouteBuilder().withChat('1').withDashboard('2').build(),
createRouteBuilder().withChat('1').withMetric('2').build(),
createRouteBuilder().withChat('1').withReport('2').build(),
createRouteBuilder().withChat('1').withDashboard('2').withMetric('3').build(),
createRouteBuilder().withChat('1').withReport('2').withMetric('3').build(),
];
expect(routes).toBeDefined();
});
});
});

View File

@ -0,0 +1,256 @@
import type { AssetType } from '@buster/server-shared/assets';
import type { FileRouteTypes } from '@/routeTree.gen';
type RouteFilePaths = FileRouteTypes['id'];
/**
* Type definitions for asset route parameters
*/
type ChatParamsToRoute = {
assetType: 'chat';
assetId: string;
metricId?: string;
dashboardId?: string;
reportId?: string;
};
type MetricParamsToRoute = {
assetType: 'metric';
assetId: string;
dashboardId?: string;
reportId?: string;
chatId?: string;
};
type DashboardParamsToRoute = {
assetType: 'dashboard';
assetId: string;
metricId?: string;
reportId?: string;
chatId?: string;
};
type ReportParamsToRoute = {
assetType: 'report';
assetId: string;
metricId?: string;
chatId?: string;
};
type AssetParamsToRoute =
| ChatParamsToRoute
| MetricParamsToRoute
| DashboardParamsToRoute
| ReportParamsToRoute;
/**
* Route builder internal state type
*/
type RouteBuilderState = {
chatId?: string;
metricId?: string;
dashboardId?: string;
reportId?: string;
};
/**
* Type-safe route mapping based on parameter combinations
*/
type RouteMap = {
// Single asset routes
chat: '/app/chats/$chatId';
dashboard: '/app/dashboards/$dashboardId';
metric: '/app/metrics/$metricId';
report: '/app/reports/$reportId';
// Chat combination routes
'chat+dashboard': '/app/chats/$chatId/dashboard/$dashboardId';
'chat+metric': '/app/chats/$chatId/metrics/$metricId';
'chat+report': '/app/chats/$chatId/report/$reportId';
'chat+dashboard+metric': '/app/chats/$chatId/dashboard/$dashboardId/metrics/$metricId';
'chat+report+metric': '/app/chats/$chatId/report/$reportId/metrics/$metricId';
};
/**
* Helper type to get valid route keys based on builder state
*/
type GetRouteKey<T extends RouteBuilderState> = T extends {
chatId: string;
dashboardId: string;
metricId: string;
}
? 'chat+dashboard+metric'
: T extends { chatId: string; reportId: string; metricId: string }
? 'chat+report+metric'
: T extends { chatId: string; dashboardId: string }
? 'chat+dashboard'
: T extends { chatId: string; reportId: string }
? 'chat+report'
: T extends { chatId: string; metricId: string }
? 'chat+metric'
: T extends { chatId: string }
? 'chat'
: T extends { dashboardId: string }
? 'dashboard'
: T extends { metricId: string }
? 'metric'
: T extends { reportId: string }
? 'report'
: never;
/**
* Type-safe route builder with fluent API
*/
class RouteBuilder<T extends RouteBuilderState = NonNullable<unknown>> {
private state: T;
constructor(state: T = {} as T) {
this.state = state;
}
/**
* Add chat ID to the route
*/
withChat<U extends string>(chatId: U): RouteBuilder<T & { chatId: U }> {
return new RouteBuilder({ ...this.state, chatId });
}
/**
* Add metric ID to the route
*/
withMetric<U extends string>(metricId: U): RouteBuilder<T & { metricId: U }> {
return new RouteBuilder({ ...this.state, metricId });
}
/**
* Add dashboard ID to the route
*/
withDashboard<U extends string>(dashboardId: U): RouteBuilder<T & { dashboardId: U }> {
return new RouteBuilder({ ...this.state, dashboardId });
}
/**
* Add report ID to the route
*/
withReport<U extends string>(reportId: U): RouteBuilder<T & { reportId: U }> {
return new RouteBuilder({ ...this.state, reportId });
}
/**
* Build the route path with type safety
*/
build(): GetRouteKey<T> extends keyof RouteMap ? RouteMap[GetRouteKey<T>] : never {
const key = this.getRouteKey();
const routeMap: RouteMap = {
chat: '/app/chats/$chatId',
dashboard: '/app/dashboards/$dashboardId',
metric: '/app/metrics/$metricId',
report: '/app/reports/$reportId',
'chat+dashboard': '/app/chats/$chatId/dashboard/$dashboardId',
'chat+metric': '/app/chats/$chatId/metrics/$metricId',
'chat+report': '/app/chats/$chatId/report/$reportId',
'chat+dashboard+metric': '/app/chats/$chatId/dashboard/$dashboardId/metrics/$metricId',
'chat+report+metric': '/app/chats/$chatId/report/$reportId/metrics/$metricId',
};
return routeMap[key as keyof RouteMap] as GetRouteKey<T> extends keyof RouteMap
? RouteMap[GetRouteKey<T>]
: never;
}
/**
* Get the route params object for TanStack Router navigation
*/
getParams(): T {
return this.state;
}
/**
* Determine the route key based on current state
*/
private getRouteKey(): string {
const { chatId, dashboardId, metricId, reportId } = this.state;
// Chat combination routes (most specific first)
if (chatId && dashboardId && metricId) return 'chat+dashboard+metric';
if (chatId && reportId && metricId) return 'chat+report+metric';
if (chatId && dashboardId) return 'chat+dashboard';
if (chatId && reportId) return 'chat+report';
if (chatId && metricId) return 'chat+metric';
// Single asset routes
if (chatId) return 'chat';
if (dashboardId) return 'dashboard';
if (metricId) return 'metric';
if (reportId) return 'report';
throw new Error('No valid route could be determined from the provided parameters');
}
}
/**
* Main function to convert asset params to route
*/
export const assetParamsToRoute = (params: AssetParamsToRoute): RouteFilePaths => {
const builder = new RouteBuilder();
// Build route based on asset type and additional params
switch (params.assetType) {
case 'chat': {
let route = builder.withChat(params.assetId);
if (params.dashboardId) route = route.withDashboard(params.dashboardId);
if (params.reportId) route = route.withReport(params.reportId);
if (params.metricId) route = route.withMetric(params.metricId);
return route.build() as RouteFilePaths;
}
case 'metric': {
let route = builder.withMetric(params.assetId);
if (params.chatId) {
route = route.withChat(params.chatId);
if (params.dashboardId) route = route.withDashboard(params.dashboardId);
if (params.reportId) route = route.withReport(params.reportId);
}
return route.build() as RouteFilePaths;
}
case 'dashboard': {
let route = builder.withDashboard(params.assetId);
if (params.chatId) {
route = route.withChat(params.chatId);
if (params.metricId) route = route.withMetric(params.metricId);
}
return route.build() as RouteFilePaths;
}
case 'report': {
let route = builder.withReport(params.assetId);
if (params.chatId) {
route = route.withChat(params.chatId);
if (params.metricId) route = route.withMetric(params.metricId);
}
return route.build() as RouteFilePaths;
}
default:
throw new Error(`Unknown asset type: ${(params as AssetParamsToRoute).assetType}`);
}
};
/**
* Create a new route builder instance
*/
export const createRouteBuilder = () => new RouteBuilder();
// Example usage:
// const route1 = createRouteBuilder()
// .withChat('chat-123')
// .withMetric('metric-456')
// .build(); // Type: '/app/chats/$chatId/metrics/$metricId'
//
// const route2 = assetParamsToRoute({
// assetType: 'dashboard',
// assetId: 'dash-123',
// chatId: 'chat-456',
// metricId: 'metric-789'
// }); // Returns: '/app/chats/$chatId/dashboard/$dashboardId/metrics/$metricId'