Timeline
@granit/timeline provides framework-agnostic types and API functions for an entity
activity stream — mirroring Granit.Timeline on the .NET backend. It supports three
entry types (comments, internal notes, system logs), threaded replies, file attachments
via blob IDs, and @mention syntax.
@granit/react-timeline wraps these into a provider and hooks with infinite scroll,
optimistic updates, and follower management.
Peer dependencies: axios, @granit/logger, react ^19, @tanstack/react-query ^5
Package structure
Section titled “Package structure”Directory@granit/timeline/ Entry types, stream/follower API functions (framework-agnostic)
- @granit/react-timeline TimelineProvider, stream/actions/followers hooks
| Package | Role | Depends on |
|---|---|---|
@granit/timeline | DTOs, entry type enum, stream/follower API functions | axios |
@granit/react-timeline | TimelineProvider, useTimeline, useTimelineActions, useTimelineFollowers | @granit/timeline, @granit/react-querying, @granit/logger, react |
import { TimelineProvider } from '@granit/react-timeline';import { api } from './api-client';
function EntityDetail({ children }) { return ( <TimelineProvider apiClient={api} basePath="/api/v1/timeline"> {children} </TimelineProvider> );}import { fetchStream, createEntry, followEntity } from '@granit/timeline';import type { TimelineStreamEntry, CreateTimelineEntryRequest } from '@granit/timeline';TypeScript SDK
Section titled “TypeScript SDK”const TimelineEntryType = { Comment: 0, // Visible to all users InternalNote: 1, // Restricted visibility (internal staff) SystemLog: 2, // Automatic system-generated entries} as const;
type TimelineEntryTypeValue = (typeof TimelineEntryType)[keyof typeof TimelineEntryType];interface TimelineStreamEntry { readonly id: string; readonly entityType: string; readonly entityId: string; readonly entryType: TimelineEntryTypeValue; readonly body: string; readonly authorId: string; readonly authorDisplayName: string; readonly parentEntryId: string | null; // null = top-level, set = threaded reply readonly createdAt: string; readonly attachmentBlobIds: string[];}
interface TimelineStreamPage { readonly items: TimelineStreamEntry[]; readonly totalCount: number; readonly nextCursor: string | null;}
interface CreateTimelineEntryRequest { readonly entryType: TimelineEntryTypeValue; readonly body: string; readonly parentEntryId?: string; readonly attachmentBlobIds?: string[];}
interface TimelineQueryParams { readonly page?: number; readonly pageSize?: number; // default: 20}
interface MentionSuggestion { readonly id: string; readonly displayName: string;}API functions
Section titled “API functions”| Function | Endpoint | Description |
|---|---|---|
fetchStream(client, basePath, entityType, entityId, params?) | GET /{entityType}/{entityId} | Paginated timeline stream |
createEntry(client, basePath, entityType, entityId, request) | POST /{entityType}/{entityId}/entries | Create comment, note, or log |
deleteEntry(client, basePath, entityType, entityId, entryId) | DELETE /{entityType}/{entityId}/entries/{id} | Delete an entry |
followEntity(client, basePath, entityType, entityId) | POST /{entityType}/{entityId}/follow | Follow entity |
unfollowEntity(client, basePath, entityType, entityId) | DELETE /{entityType}/{entityId}/follow | Unfollow entity |
fetchFollowers(client, basePath, entityType, entityId) | GET /{entityType}/{entityId}/followers | List follower user IDs |
Mention syntax
Section titled “Mention syntax”Mentions use the format @[Display Name](user:guid). The server extracts mentioned
user IDs from this pattern to trigger notifications.
React bindings
Section titled “React bindings”TimelineProvider
Section titled “TimelineProvider”interface TimelineProviderProps { apiClient: AxiosInstance; basePath?: string; // default: '/api/v1/timeline' children: React.ReactNode;}
<TimelineProvider apiClient={api}> {children}</TimelineProvider>useTimeline(options)
Section titled “useTimeline(options)”Infinite-scroll timeline stream with optimistic update helpers.
interface UseTimelineReturn { readonly entries: readonly TimelineStreamEntry[]; readonly totalCount: number; readonly loading: boolean; readonly loadingMore: boolean; readonly error: Error | null; readonly hasMore: boolean; readonly loadMore: () => void; readonly refresh: () => void; readonly addOptimisticEntry: (entry: TimelineStreamEntry) => void; readonly removeOptimisticEntry: (entryId: string) => void;}useTimelineActions(options)
Section titled “useTimelineActions(options)”Create and delete timeline entries with callbacks for optimistic updates.
interface UseTimelineActionsOptions { entityType: string; entityId: string; onEntryCreated?: (entry: TimelineStreamEntry) => void; onEntryDeleted?: (entryId: string) => void;}
interface UseTimelineActionsReturn { readonly postEntry: (request: CreateTimelineEntryRequest) => Promise<TimelineStreamEntry>; readonly removeEntry: (entryId: string) => Promise<void>; readonly posting: boolean; readonly deleting: boolean; readonly error: Error | null;}useTimelineFollowers(options)
Section titled “useTimelineFollowers(options)”Follower management with follow/unfollow toggle.
interface UseTimelineFollowersOptions { entityType: string; entityId: string; currentUserId?: string;}
interface UseTimelineFollowersReturn { readonly followers: string[]; readonly isFollowing: boolean; readonly loading: boolean; readonly error: Error | null; readonly follow: () => Promise<void>; readonly unfollow: () => Promise<void>;}Optimistic update pattern
Section titled “Optimistic update pattern”Wire useTimelineActions callbacks into useTimeline for instant UI feedback:
const timeline = useTimeline({ entityType: 'Invoice', entityId: id });const actions = useTimelineActions({ entityType: 'Invoice', entityId: id, onEntryCreated: (entry) => timeline.addOptimisticEntry(entry), onEntryDeleted: (entryId) => timeline.removeOptimisticEntry(entryId),});Threading
Section titled “Threading”Replies are entries with parentEntryId set to the parent entry’s ID. The rendering
and indentation is a client-side concern — the hooks return a flat list that can be
grouped by parentEntryId.
Public API summary
Section titled “Public API summary”| Category | Key exports | Package |
|---|---|---|
| Enums | TimelineEntryType | @granit/timeline |
| Types | TimelineStreamEntry, CreateTimelineEntryRequest, TimelineStreamPage, MentionSuggestion | @granit/timeline |
| Stream API | fetchStream(), createEntry(), deleteEntry() | @granit/timeline |
| Follower API | followEntity(), unfollowEntity(), fetchFollowers() | @granit/timeline |
| Provider | TimelineProvider, useTimelineConfig() | @granit/react-timeline |
| Stream hooks | useTimeline(), useTimelineActions() | @granit/react-timeline |
| Follower hook | useTimelineFollowers() | @granit/react-timeline |
See also
Section titled “See also”- Granit.Timeline module — .NET audit timeline with notification integration
- Notifications — Mentions trigger notification delivery
- Storage —
attachmentBlobIdsreference blobs in the storage system