import { kitListAll, kitRequest, unwrapRecord } from './client.ts' import { mutationPreview, rejectBroadcastSend } from './safety.ts' import { parseAuthInput } from './auth.ts' import { broadcastSummary } from './models.ts' import type { BroadcastSummary, JsonRecord, KitAuthInput, MutationInput } from './types.ts' import { clampInt, compactRecord, optionalBoolean, optionalString, requireId, requireRecord, requireString, } from './types.ts' export type BroadcastDraftInput = KitAuthInput & MutationInput & { subject: string content: string preview_text?: string description?: string thumbnail_url?: string thumbnail_alt?: string email_address?: string email_template_id?: number public?: boolean subscriber_filter?: unknown } export type BroadcastDetail = BroadcastSummary & { preview_text?: unknown description?: unknown content?: unknown thumbnail_url?: unknown thumbnail_alt?: unknown email_address?: unknown email_template?: unknown public?: unknown send_at?: unknown subscriber_filter?: unknown public_url?: unknown } function broadcastDetail(broadcast: JsonRecord): BroadcastDetail { const summary = broadcastSummary(broadcast) if (!summary) throw new Error('Kit returned no broadcast id.') return { ...summary, preview_text: broadcast.preview_text, description: broadcast.description, content: broadcast.content, thumbnail_url: broadcast.thumbnail_url, thumbnail_alt: broadcast.thumbnail_alt, email_address: broadcast.email_address, email_template: broadcast.email_template, public: broadcast.public, send_at: broadcast.send_at, subscriber_filter: broadcast.subscriber_filter, public_url: broadcast.public_url, } } function draftBody(input: BroadcastDraftInput | Record): JsonRecord { rejectBroadcastSend(input as JsonRecord, 'broadcast draft') return compactRecord({ subject: input.subject, content: input.content, preview_text: input.preview_text, description: input.description, thumbnail_url: input.thumbnail_url, thumbnail_alt: input.thumbnail_alt, email_address: input.email_address, email_template_id: input.email_template_id, public: input.public, subscriber_filter: input.subscriber_filter, }) } /** * Create a Kit broadcast **draft**. Never sends or schedules. Requires * `confirm: true`, or use `dryRun: true`. A human sends from the Kit UI via * `editUrl`. * @example * import { createBroadcastDraft } from 'kody:@kody/kit/broadcasts' * const preview = await createBroadcastDraft({ * subject: 'Hello', * content: '

Hi

', * dryRun: true, * }) */ export async function createBroadcastDraft(input: BroadcastDraftInput) { const subject = requireString(input.subject, 'subject') const content = requireString(input.content, 'content') const body = draftBody({ ...input, subject, content }) const preview = mutationPreview(input, { method: 'POST', path: '/broadcasts', body, }) if (preview) return preview const result = await kitRequest({ ...input, method: 'POST', path: '/broadcasts', body, confirm: true, }) return broadcastDetail(unwrapRecord(result.data, 'broadcast', 'createBroadcastDraft')) } /** * Update a broadcast draft. Refuses send_at / send statuses. Requires * `confirm: true`, or use `dryRun: true`. * @example * import { updateBroadcast } from 'kody:@kody/kit/broadcasts' * const preview = await updateBroadcast({ id: 1, subject: 'Updated', dryRun: true }) */ export async function updateBroadcast( input: KitAuthInput & MutationInput & Partial & { id: number | string }, ) { const id = requireId(input.id, 'id') const body = draftBody(input) const preview = mutationPreview(input, { method: 'PUT', path: `/broadcasts/${id}`, body, }) if (preview) return preview const result = await kitRequest({ ...input, method: 'PUT', path: `/broadcasts/${id}`, body, confirm: true, }) return broadcastDetail(unwrapRecord(result.data, 'broadcast', 'updateBroadcast')) } /** * Get one broadcast. * @example * import { getBroadcast } from 'kody:@kody/kit/broadcasts' * const draft = await getBroadcast({ id: 123 }) */ export async function getBroadcast(input: KitAuthInput & { id: number | string }) { const id = requireId(input.id, 'id') const result = await kitRequest({ ...input, method: 'GET', path: `/broadcasts/${id}`, }) return broadcastDetail(unwrapRecord(result.data, 'broadcast', 'getBroadcast')) } /** * List broadcast summaries. Always pass `maxItems` for exploratory reads * (default 25). This package never sends broadcasts. * @example * import { listBroadcasts } from 'kody:@kody/kit/broadcasts' * const recent = await listBroadcasts({ maxItems: 10 }) */ export async function listBroadcasts( input: KitAuthInput & { status?: string; maxItems?: number } = {}, ): Promise> { const items = await kitListAll({ ...input, path: '/broadcasts', itemKey: 'broadcasts', maxItems: clampInt(input.maxItems, 1, 500, 25), query: compactRecord({ status: input.status }), }) return items .map((item) => broadcastSummary(item)) .filter((item): item is BroadcastSummary => Boolean(item)) } /** * Delete a broadcast draft. Requires `confirm: true`, or use `dryRun: true`. * @example * import { deleteBroadcast } from 'kody:@kody/kit/broadcasts' * const preview = await deleteBroadcast({ id: 123, dryRun: true }) */ export async function deleteBroadcast(input: KitAuthInput & MutationInput & { id: number | string }) { const id = requireId(input.id, 'id') const preview = mutationPreview(input, { method: 'DELETE', path: `/broadcasts/${id}`, }) if (preview) return preview await kitRequest({ ...input, method: 'DELETE', path: `/broadcasts/${id}`, confirm: true, }) return { id, deleted: true as const } } /** * Read stats for one broadcast. * @example * import { getBroadcastStats } from 'kody:@kody/kit/broadcasts' * const stats = await getBroadcastStats({ id: 123 }) */ export async function getBroadcastStats(input: KitAuthInput & { id: number | string }) { const id = requireId(input.id, 'id') const result = await kitRequest({ ...input, method: 'GET', path: `/broadcasts/${id}/stats`, }) return result.data } /** * Broadcast helpers. Defaults to `list`. Drafts only — never sends. * @example * import broadcasts from 'kody:@kody/kit/broadcasts' * const recent = await broadcasts({ action: 'list', maxItems: 5 }) */ export default async function broadcastsEntrypoint(params: Record = {}) { const input = requireRecord(params, 'broadcasts') const auth = parseAuthInput(input) const action = optionalString(input.action, 'action') ?? 'list' const mutation = { confirm: optionalBoolean(input.confirm, 'confirm'), dryRun: optionalBoolean(input.dryRun, 'dryRun'), } switch (action) { case 'list': return listBroadcasts({ ...auth, status: optionalString(input.status, 'status'), maxItems: input.maxItems as number | undefined, }) case 'get': return getBroadcast({ ...auth, id: requireId(input.id, 'id') }) case 'stats': return getBroadcastStats({ ...auth, id: requireId(input.id, 'id') }) case 'create-draft': return createBroadcastDraft({ ...auth, ...mutation, subject: requireString(input.subject, 'subject'), content: requireString(input.content, 'content'), preview_text: optionalString(input.preview_text, 'preview_text'), description: optionalString(input.description, 'description'), email_address: optionalString(input.email_address, 'email_address'), email_template_id: input.email_template_id as number | undefined, public: optionalBoolean(input.public, 'public'), }) case 'update': return updateBroadcast({ ...auth, ...mutation, id: requireId(input.id, 'id'), subject: optionalString(input.subject, 'subject'), content: optionalString(input.content, 'content'), }) case 'delete': return deleteBroadcast({ ...auth, ...mutation, id: requireId(input.id, 'id') }) default: throw new Error( 'broadcasts action must be one of: list, get, stats, create-draft, update, delete.', ) } } export { broadcastSummary }