Harden dashboard realtime state and controls
This commit is contained in:
1 parent
4b3f00729f
commit
d108903174
21 files changed
+422
-29
No files matched your search
@@ -0,0 +1,51 @@
|
||||
import type { RealtimeEvent } from './event-types.js';
|
||||
import type { DashboardEventPublisher } from './event-publisher.js';
|
||||
|
||||
type Published = RealtimeEvent;
|
||||
type SyncInput =
|
||||
| { phase: 'started'; id: string; kind: 'delta' | 'full' | 'media' }
|
||||
| { phase: 'progress'; id: string; kind: 'delta' | 'full' | 'media'; current: number; total?: number; label?: string }
|
||||
| { phase: 'completed'; id: string; kind: 'delta' | 'full' | 'media'; processed: number }
|
||||
| { phase: 'failed'; id: string; kind: 'delta' | 'full' | 'media'; error: string; code?: string };
|
||||
|
||||
const syncKind = (kind: SyncInput['kind']) => kind === 'delta' ? 'conversations' as const : kind;
|
||||
const errorReason = (error: unknown) => error instanceof Error ? error.message : String(error);
|
||||
|
||||
export function createDashboardEventAdapters(hub: DashboardEventPublisher) {
|
||||
return {
|
||||
bot(input: { running: boolean; dryRun: boolean; transitioning?: 'starting' | 'stopping'; reason?: string }): Published {
|
||||
return hub.publish('bot.status.changed', { status: input.transitioning ?? (input.running ? 'running' : 'stopped'), dryRun: input.dryRun, reason: input.reason });
|
||||
},
|
||||
messageSent(input: { id: string; username: string; conversationId?: string }): Published {
|
||||
return hub.publish('message.sent', { messageId: input.id, recipient: input.username, conversationId: input.conversationId });
|
||||
},
|
||||
messageFailed(input: { username: string; error: unknown; messageId?: string; conversationId?: string; code?: string; retryable?: boolean }): Published {
|
||||
return hub.publish('message.failed', { recipient: input.username, reason: errorReason(input.error), messageId: input.messageId, conversationId: input.conversationId, code: input.code, retryable: input.retryable });
|
||||
},
|
||||
conversation(input: { id: string; state: 'new' | 'awaiting_reply' | 'replied' | 'closed'; unread?: number }): Published {
|
||||
return hub.publish('conversation.updated', { conversationId: input.id, status: input.state === 'awaiting_reply' ? 'awaiting-reply' : input.state, unreadCount: input.unread });
|
||||
},
|
||||
reply(input: { conversationId: string; id: string; username: string; body?: string }): Published {
|
||||
return hub.publish('reply.received', { conversationId: input.conversationId, replyId: input.id, sender: input.username, preview: input.body });
|
||||
},
|
||||
sync(input: SyncInput): Published {
|
||||
const kind = syncKind(input.kind);
|
||||
switch (input.phase) {
|
||||
case 'started': return hub.publish('sync.started', { syncId: input.id, kind });
|
||||
case 'progress': return hub.publish('sync.progress', { syncId: input.id, kind, completed: input.current, total: input.total, message: input.label });
|
||||
case 'completed': return hub.publish('sync.completed', { syncId: input.id, kind, processed: input.processed });
|
||||
case 'failed': return hub.publish('sync.failed', { syncId: input.id, kind, reason: input.error, code: input.code });
|
||||
}
|
||||
},
|
||||
rateLimit(input: { blocked: boolean; remaining?: number; resetAt?: number; reason?: string }): Published {
|
||||
return hub.publish('rate-limit.changed', { limited: input.blocked, remaining: input.remaining, resetsAt: input.resetAt === undefined ? undefined : new Date(input.resetAt).toISOString(), reason: input.reason });
|
||||
},
|
||||
redditSession(input: { state: 'unknown' | 'authenticating' | 'authenticated' | 'expired' | 'login-required' | 'unreachable'; username?: string; reason?: string }): Published {
|
||||
const status = input.state === 'login-required' ? 'logged-out' : input.state === 'unreachable' ? 'error' : input.state;
|
||||
return hub.publish('reddit.session.changed', { status, username: input.username, reason: input.reason });
|
||||
},
|
||||
log(input: { level: 'debug' | 'info' | 'warn' | 'error'; message: string; data?: unknown }): Published {
|
||||
return hub.publish('log.created', input);
|
||||
},
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
import type { RealtimeEvent, RealtimeEventMap, RealtimeEventType } from './event-types.js';
|
||||
import type { RealtimeEventHub } from './realtime-event-hub.js';
|
||||
|
||||
export interface DashboardEventPublisher {
|
||||
publish<TType extends RealtimeEventType>(type: TType, payload: RealtimeEventMap[TType]): RealtimeEvent<TType>;
|
||||
}
|
||||
|
||||
export function createDashboardEventPublisher(hub: RealtimeEventHub): DashboardEventPublisher {
|
||||
return { publish: (type, payload) => hub.publish(type, payload) };
|
||||
}
|
||||
@@ -21,3 +21,5 @@ export {
|
||||
type RealtimeEventSubscription,
|
||||
type SubscribeOptions,
|
||||
} from './realtime-event-hub.js';
|
||||
export { createDashboardEventAdapters } from './event-adapters.js';
|
||||
export { createDashboardEventPublisher, type DashboardEventPublisher } from './event-publisher.js';
|
||||
Reference in new issue
Block a user