This commit is contained in:
Manoj Aggarwal 2026-01-22 20:57:48 -08:00
commit 49d51ba569
70 changed files with 4266 additions and 1362 deletions

View file

@ -1,5 +1,19 @@
import { z } from "zod";
/**
* Raw comment
*/
export const rawComment = z.object({
id: z.number(),
message_id: z.number(),
thread_id: z.number(), // Denormalized for efficient Electric subscriptions
parent_id: z.number().nullable(),
author_id: z.string().nullable(),
content: z.string(),
created_at: z.string(),
updated_at: z.string(),
});
export const author = z.object({
id: z.string().uuid(),
display_name: z.string().nullable(),
@ -122,6 +136,7 @@ export const getMentionsResponse = z.object({
total_count: z.number(),
});
export type RawComment = z.infer<typeof rawComment>;
export type Author = z.infer<typeof author>;
export type CommentReply = z.infer<typeof commentReply>;
export type Comment = z.infer<typeof comment>;

View file

@ -0,0 +1,15 @@
import { z } from "zod";
/**
* Raw message from database (Electric SQL sync)
*/
export const rawMessage = z.object({
id: z.number(),
thread_id: z.number(),
role: z.string(),
content: z.unknown(),
author_id: z.string().nullable(),
created_at: z.string(),
});
export type RawMessage = z.infer<typeof rawMessage>;

View file

@ -0,0 +1,24 @@
import { z } from "zod";
/**
* Chat session state for live collaboration.
* Tracks which user the AI is currently responding to.
*/
export const chatSessionState = z.object({
id: z.number(),
thread_id: z.number(),
ai_responding_to_user_id: z.string().uuid().nullable(),
updated_at: z.string(),
});
/**
* User currently being responded to by the AI.
*/
export const respondingUser = z.object({
id: z.string().uuid(),
display_name: z.string().nullable(),
email: z.string(),
});
export type ChatSessionState = z.infer<typeof chatSessionState>;
export type RespondingUser = z.infer<typeof respondingUser>;

View file

@ -0,0 +1,281 @@
import { z } from "zod";
import { searchSourceConnectorTypeEnum } from "./connector.types";
import { documentTypeEnum } from "./document.types";
/**
* Inbox item type enum - matches backend notification types
*/
export const inboxItemTypeEnum = z.enum([
"connector_indexing",
"document_processing",
"new_mention",
]);
/**
* Inbox item status enum - used in metadata
*/
export const inboxItemStatusEnum = z.enum(["in_progress", "completed", "failed"]);
/**
* Document processing stage enum
*/
export const documentProcessingStageEnum = z.enum([
"queued",
"parsing",
"chunking",
"embedding",
"storing",
"completed",
"failed",
]);
/**
* Base metadata schema shared across inbox item types
*/
export const baseInboxItemMetadata = z.object({
operation_id: z.string().optional(),
status: inboxItemStatusEnum.optional(),
started_at: z.string().optional(),
completed_at: z.string().optional(),
});
/**
* Connector indexing metadata schema
*/
export const connectorIndexingMetadata = baseInboxItemMetadata.extend({
connector_id: z.number(),
connector_name: z.string(),
connector_type: searchSourceConnectorTypeEnum,
start_date: z.string().nullable().optional(),
end_date: z.string().nullable().optional(),
indexed_count: z.number(),
total_count: z.number().optional(),
progress_percent: z.number().optional(),
error_message: z.string().nullable().optional(),
// Google Drive specific fields
folder_count: z.number().optional(),
file_count: z.number().optional(),
folder_names: z.array(z.string()).optional(),
file_names: z.array(z.string()).optional(),
});
/**
* Document processing metadata schema
*/
export const documentProcessingMetadata = baseInboxItemMetadata.extend({
document_type: documentTypeEnum,
document_name: z.string(),
processing_stage: documentProcessingStageEnum,
file_size: z.number().optional(),
chunks_count: z.number().optional(),
document_id: z.number().optional(),
error_message: z.string().nullable().optional(),
});
/**
* New mention metadata schema
*/
export const newMentionMetadata = z.object({
mention_id: z.number(),
comment_id: z.number(),
message_id: z.number(),
thread_id: z.number(),
thread_title: z.string(),
author_id: z.string(),
author_name: z.string(),
author_avatar_url: z.string().nullable().optional(),
author_email: z.string().optional(),
content_preview: z.string(),
});
/**
* Union of all inbox item metadata types
* Use this when the inbox item type is unknown
*/
export const inboxItemMetadata = z.union([
connectorIndexingMetadata,
documentProcessingMetadata,
newMentionMetadata,
baseInboxItemMetadata,
]);
/**
* Main inbox item schema
*/
export const inboxItem = z.object({
id: z.number(),
user_id: z.string(),
search_space_id: z.number().nullable(),
type: inboxItemTypeEnum,
title: z.string(),
message: z.string(),
read: z.boolean(),
metadata: z.record(z.string(), z.unknown()),
created_at: z.string(),
updated_at: z.string().nullable(),
});
/**
* Typed inbox item schemas for specific types
*/
export const connectorIndexingInboxItem = inboxItem.extend({
type: z.literal("connector_indexing"),
metadata: connectorIndexingMetadata,
});
export const documentProcessingInboxItem = inboxItem.extend({
type: z.literal("document_processing"),
metadata: documentProcessingMetadata,
});
export const newMentionInboxItem = inboxItem.extend({
type: z.literal("new_mention"),
metadata: newMentionMetadata,
});
// =============================================================================
// API Request/Response Schemas
// =============================================================================
/**
* Request schema for getting notifications
*/
export const getNotificationsRequest = z.object({
queryParams: z.object({
search_space_id: z.number().optional(),
type: inboxItemTypeEnum.optional(),
before_date: z.string().optional(),
limit: z.number().min(1).max(100).optional(),
offset: z.number().min(0).optional(),
}),
});
/**
* Response schema for listing notifications
*/
export const getNotificationsResponse = z.object({
items: z.array(inboxItem),
total: z.number(),
has_more: z.boolean(),
next_offset: z.number().nullable(),
});
/**
* Request schema for marking a single notification as read
*/
export const markNotificationReadRequest = z.object({
notificationId: z.number(),
});
/**
* Response schema for mark as read operations
*/
export const markNotificationReadResponse = z.object({
success: z.boolean(),
message: z.string(),
});
/**
* Response schema for mark all as read operation
*/
export const markAllNotificationsReadResponse = z.object({
success: z.boolean(),
message: z.string(),
updated_count: z.number(),
});
/**
* Request schema for getting unread count
*/
export const getUnreadCountRequest = z.object({
search_space_id: z.number().optional(),
});
/**
* Response schema for unread count
* Returns both total and recent counts for split tracking
*/
export const getUnreadCountResponse = z.object({
total_unread: z.number(),
recent_unread: z.number(), // Within SYNC_WINDOW_DAYS (14 days)
});
// =============================================================================
// Type Guards for Metadata
// =============================================================================
/**
* Type guard for ConnectorIndexingMetadata
*/
export function isConnectorIndexingMetadata(
metadata: unknown
): metadata is ConnectorIndexingMetadata {
return connectorIndexingMetadata.safeParse(metadata).success;
}
/**
* Type guard for DocumentProcessingMetadata
*/
export function isDocumentProcessingMetadata(
metadata: unknown
): metadata is DocumentProcessingMetadata {
return documentProcessingMetadata.safeParse(metadata).success;
}
/**
* Type guard for NewMentionMetadata
*/
export function isNewMentionMetadata(metadata: unknown): metadata is NewMentionMetadata {
return newMentionMetadata.safeParse(metadata).success;
}
/**
* Safe metadata parser - returns typed metadata or null
*/
export function parseInboxItemMetadata(
type: InboxItemTypeEnum,
metadata: unknown
): ConnectorIndexingMetadata | DocumentProcessingMetadata | NewMentionMetadata | null {
switch (type) {
case "connector_indexing": {
const result = connectorIndexingMetadata.safeParse(metadata);
return result.success ? result.data : null;
}
case "document_processing": {
const result = documentProcessingMetadata.safeParse(metadata);
return result.success ? result.data : null;
}
case "new_mention": {
const result = newMentionMetadata.safeParse(metadata);
return result.success ? result.data : null;
}
default:
return null;
}
}
// =============================================================================
// Inferred types
// =============================================================================
export type InboxItemTypeEnum = z.infer<typeof inboxItemTypeEnum>;
export type InboxItemStatusEnum = z.infer<typeof inboxItemStatusEnum>;
export type DocumentProcessingStageEnum = z.infer<typeof documentProcessingStageEnum>;
export type BaseInboxItemMetadata = z.infer<typeof baseInboxItemMetadata>;
export type ConnectorIndexingMetadata = z.infer<typeof connectorIndexingMetadata>;
export type DocumentProcessingMetadata = z.infer<typeof documentProcessingMetadata>;
export type NewMentionMetadata = z.infer<typeof newMentionMetadata>;
export type InboxItemMetadata = z.infer<typeof inboxItemMetadata>;
export type InboxItem = z.infer<typeof inboxItem>;
export type ConnectorIndexingInboxItem = z.infer<typeof connectorIndexingInboxItem>;
export type DocumentProcessingInboxItem = z.infer<typeof documentProcessingInboxItem>;
export type NewMentionInboxItem = z.infer<typeof newMentionInboxItem>;
// API Request/Response types
export type GetNotificationsRequest = z.infer<typeof getNotificationsRequest>;
export type GetNotificationsResponse = z.infer<typeof getNotificationsResponse>;
export type MarkNotificationReadRequest = z.infer<typeof markNotificationReadRequest>;
export type MarkNotificationReadResponse = z.infer<typeof markNotificationReadResponse>;
export type MarkAllNotificationsReadResponse = z.infer<typeof markAllNotificationsReadResponse>;
export type GetUnreadCountRequest = z.infer<typeof getUnreadCountRequest>;
export type GetUnreadCountResponse = z.infer<typeof getUnreadCountResponse>;

View file

@ -1,148 +0,0 @@
import { z } from "zod";
import { searchSourceConnectorTypeEnum } from "./connector.types";
import { documentTypeEnum } from "./document.types";
/**
* Notification type enum - matches backend notification types
*/
export const notificationTypeEnum = z.enum([
"connector_indexing",
"document_processing",
"new_mention",
]);
/**
* Notification status enum - used in metadata
*/
export const notificationStatusEnum = z.enum(["in_progress", "completed", "failed"]);
/**
* Document processing stage enum
*/
export const documentProcessingStageEnum = z.enum([
"queued",
"parsing",
"chunking",
"embedding",
"storing",
"completed",
"failed",
]);
/**
* Base metadata schema shared across notification types
*/
export const baseNotificationMetadata = z.object({
operation_id: z.string().optional(),
status: notificationStatusEnum.optional(),
started_at: z.string().optional(),
completed_at: z.string().optional(),
});
/**
* Connector indexing metadata schema
*/
export const connectorIndexingMetadata = baseNotificationMetadata.extend({
connector_id: z.number(),
connector_name: z.string(),
connector_type: searchSourceConnectorTypeEnum,
start_date: z.string().nullable().optional(),
end_date: z.string().nullable().optional(),
indexed_count: z.number(),
total_count: z.number().optional(),
progress_percent: z.number().optional(),
error_message: z.string().nullable().optional(),
// Google Drive specific fields
folder_count: z.number().optional(),
file_count: z.number().optional(),
folder_names: z.array(z.string()).optional(),
file_names: z.array(z.string()).optional(),
});
/**
* Document processing metadata schema
*/
export const documentProcessingMetadata = baseNotificationMetadata.extend({
document_type: documentTypeEnum,
document_name: z.string(),
processing_stage: documentProcessingStageEnum,
file_size: z.number().optional(),
chunks_count: z.number().optional(),
document_id: z.number().optional(),
error_message: z.string().nullable().optional(),
});
/**
* New mention metadata schema
*/
export const newMentionMetadata = z.object({
mention_id: z.number(),
comment_id: z.number(),
message_id: z.number(),
thread_id: z.number(),
thread_title: z.string(),
author_id: z.string(),
author_name: z.string(),
author_avatar_url: z.string().nullable().optional(),
author_email: z.string().optional(),
content_preview: z.string(),
});
/**
* Union of all notification metadata types
* Use this when the notification type is unknown
*/
export const notificationMetadata = z.union([
connectorIndexingMetadata,
documentProcessingMetadata,
newMentionMetadata,
baseNotificationMetadata,
]);
/**
* Main notification schema
*/
export const notification = z.object({
id: z.number(),
user_id: z.string(),
search_space_id: z.number().nullable(),
type: notificationTypeEnum,
title: z.string(),
message: z.string(),
read: z.boolean(),
metadata: z.record(z.string(), z.unknown()),
created_at: z.string(),
updated_at: z.string().nullable(),
});
/**
* Typed notification schemas for specific notification types
*/
export const connectorIndexingNotification = notification.extend({
type: z.literal("connector_indexing"),
metadata: connectorIndexingMetadata,
});
export const documentProcessingNotification = notification.extend({
type: z.literal("document_processing"),
metadata: documentProcessingMetadata,
});
export const newMentionNotification = notification.extend({
type: z.literal("new_mention"),
metadata: newMentionMetadata,
});
// Inferred types
export type NotificationTypeEnum = z.infer<typeof notificationTypeEnum>;
export type NotificationStatusEnum = z.infer<typeof notificationStatusEnum>;
export type DocumentProcessingStageEnum = z.infer<typeof documentProcessingStageEnum>;
export type BaseNotificationMetadata = z.infer<typeof baseNotificationMetadata>;
export type ConnectorIndexingMetadata = z.infer<typeof connectorIndexingMetadata>;
export type DocumentProcessingMetadata = z.infer<typeof documentProcessingMetadata>;
export type NewMentionMetadata = z.infer<typeof newMentionMetadata>;
export type NotificationMetadata = z.infer<typeof notificationMetadata>;
export type Notification = z.infer<typeof notification>;
export type ConnectorIndexingNotification = z.infer<typeof connectorIndexingNotification>;
export type DocumentProcessingNotification = z.infer<typeof documentProcessingNotification>;
export type NewMentionNotification = z.infer<typeof newMentionNotification>;