import { kitListAll, kitRequest, unwrapRecord } from './client.ts' import { mutationPreview } from './safety.ts' import { parseAuthInput } from './auth.ts' import { subscriberSummary, tagSummary } from './models.ts' import type { JsonRecord, KitAuthInput, MutationInput, TagSummary } from './types.ts' import { clampInt, compactRecord, optionalBoolean, optionalString, requireId, requireRecord, requireString, } from './types.ts' export { tagSummary } function requireTag(body: unknown, action: string): TagSummary { const tag = unwrapRecord(body, 'tag', action) const summary = tagSummary(tag) if (!summary) throw new Error(`${action}: Kit returned no tag.`) return summary } /** * List tags. Pass `maxItems` (default 100). * @example * import { listTags } from 'kody:@kody/kit/tags' * const tags = await listTags({ maxItems: 50 }) */ export async function listTags(input: KitAuthInput & { maxItems?: number } = {}): Promise> { const items = await kitListAll({ ...input, path: '/tags', itemKey: 'tags', maxItems: clampInt(input.maxItems, 1, 1000, 100), }) return items.map((item) => tagSummary(item)).filter((item): item is TagSummary => Boolean(item)) } /** * Create a tag. Requires `confirm: true`, or use `dryRun: true`. * @example * import { createTag } from 'kody:@kody/kit/tags' * const preview = await createTag({ name: 'waitlist', dryRun: true }) */ export async function createTag(input: KitAuthInput & MutationInput & { name: string }) { const name = requireString(input.name, 'name') const preview = mutationPreview(input, { method: 'POST', path: '/tags', body: { name }, }) if (preview) return preview const result = await kitRequest({ ...input, method: 'POST', path: '/tags', body: { name }, confirm: true, }) return requireTag(result.data, 'createTag') } /** * Return the tag with this exact name, creating it when missing. * Requires `confirm: true` when a create is needed, or use `dryRun: true`. * @example * import { ensureTag } from 'kody:@kody/kit/tags' * const tag = await ensureTag({ name: 'waitlist', confirm: true }) */ export async function ensureTag(input: KitAuthInput & MutationInput & { name: string }) { const name = requireString(input.name, 'name') const tags = await listTags(input) const existing = tags.find((tag) => tag.name === name) if (existing) return existing return createTag(input) } /** * Tag a subscriber by email. Requires `confirm: true`, or use `dryRun: true`. * @example * import { tagSubscriber } from 'kody:@kody/kit/tags' * const preview = await tagSubscriber({ tagId: 1, email_address: 'ada@example.com', dryRun: true }) */ export async function tagSubscriber( input: KitAuthInput & MutationInput & { tagId: number | string; email_address: string }, ) { const tagId = requireId(input.tagId, 'tagId') const email = requireString(input.email_address, 'email_address') const preview = mutationPreview(input, { method: 'POST', path: `/tags/${tagId}/subscribers`, body: { email_address: email }, }) if (preview) return preview const result = await kitRequest({ ...input, method: 'POST', path: `/tags/${tagId}/subscribers`, body: { email_address: email }, confirm: true, }) return subscriberSummary(unwrapRecord(result.data, 'subscriber', 'tagSubscriber')) } /** * Remove a tag from a subscriber. Requires `confirm: true`, or use `dryRun: true`. * @example * import { untagSubscriber } from 'kody:@kody/kit/tags' * const preview = await untagSubscriber({ tagId: 1, subscriberId: 2, dryRun: true }) */ export async function untagSubscriber( input: KitAuthInput & MutationInput & { tagId: number | string; subscriberId: number | string }, ) { const tagId = requireId(input.tagId, 'tagId') const subscriberId = requireId(input.subscriberId, 'subscriberId') const preview = mutationPreview(input, { method: 'DELETE', path: `/tags/${tagId}/subscribers/${subscriberId}`, }) if (preview) return preview await kitRequest({ ...input, method: 'DELETE', path: `/tags/${tagId}/subscribers/${subscriberId}`, confirm: true, }) return { tagId, subscriberId, untagged: true as const } } /** * List subscribers with a tag. * @example * import { listTagSubscribers } from 'kody:@kody/kit/tags' * const people = await listTagSubscribers({ tagId: 1, maxItems: 10 }) */ export async function listTagSubscribers( input: KitAuthInput & { tagId: number | string; maxItems?: number }, ) { const tagId = requireId(input.tagId, 'tagId') const items = await kitListAll({ ...input, path: `/tags/${tagId}/subscribers`, itemKey: 'subscribers', maxItems: clampInt(input.maxItems, 1, 500, 25), }) return items .map((item) => subscriberSummary(item)) .filter((item): item is NonNullable> => Boolean(item)) } /** * Tag helpers. Defaults to `list`. * @example * import tags from 'kody:@kody/kit/tags' * const all = await tags({ action: 'list' }) */ export default async function tagsEntrypoint(params: Record = {}) { const input = requireRecord(params, 'tags') 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 listTags({ ...auth, maxItems: input.maxItems as number | undefined }) case 'create': return createTag({ ...auth, ...mutation, name: requireString(input.name, 'name') }) case 'ensure': return ensureTag({ ...auth, ...mutation, name: requireString(input.name, 'name') }) case 'tag': return tagSubscriber({ ...auth, ...mutation, tagId: requireId(input.tagId, 'tagId'), email_address: requireString(input.email_address, 'email_address'), }) case 'untag': return untagSubscriber({ ...auth, ...mutation, tagId: requireId(input.tagId, 'tagId'), subscriberId: requireId(input.subscriberId, 'subscriberId'), }) case 'subscribers': return listTagSubscribers({ ...auth, tagId: requireId(input.tagId, 'tagId'), maxItems: input.maxItems as number | undefined, }) default: throw new Error('tags action must be one of: list, create, ensure, tag, untag, subscribers.') } } export { compactRecord }