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<Array<TagSummary>> {
const items = await kitListAll<JsonRecord>({
...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<JsonRecord>({
...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<JsonRecord>({
...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<ReturnType<typeof subscriberSummary>> => 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<string, unknown> = {}) {
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 }