Skip to content
← Public packages

@kentcdodds/linkedin

Headless helpers for LinkedIn identity, requests, and guarded posting using the saved linkedin OAuth integration.

src/create-post-input.ts

93 lines · 2.5 KB · TypeScript
import {
	ValidationError,
	any,
	boolean,
	object,
	optional,
	parse,
	string,
} from 'remix/data-schema'
import { formatSchemaValidationError } from './schema-input-errors.ts'
import type { CreatePostInput, JsonRecord, LinkedInVisibility } from './types.ts'

const KNOWN_KEYS = [
	'commentary',
	'text',
	'message',
	'body',
	'content',
	'author',
	'visibility',
	'distribution',
	'isReshareDisabledByAuthor',
	'dryRun',
	'confirm',
] as const

const HINT =
	`Pass commentary (aliases: text, message, body) plus optional author/visibility/content/distribution. ` +
	`Unknown top-level keys are rejected (no silent drop). ` +
	`For structured LinkedIn content, use the \`content\` object — do not invent reply/parent aliases here.`

const CreatePostInputSchema = object(
	{
		commentary: optional(string()),
		text: optional(string()),
		message: optional(string()),
		body: optional(string()),
		content: optional(any()), // LinkedIn API content bag (controlled extension)
		author: optional(string()),
		visibility: optional(string()),
		distribution: optional(any()),
		isReshareDisabledByAuthor: optional(boolean()),
		dryRun: optional(boolean()),
		confirm: optional(boolean()),
	},
	{ unknownKeys: 'error' },
).transform((raw): CreatePostInput => {
	const commentary =
		[raw.commentary, raw.text, raw.message, raw.body]
			.find((v) => typeof v === 'string' && v.trim().length > 0)
			?.trim()
	const content =
		raw.content && typeof raw.content === 'object' && !Array.isArray(raw.content)
			? (raw.content as JsonRecord)
			: undefined
	const distribution =
		raw.distribution && typeof raw.distribution === 'object' && !Array.isArray(raw.distribution)
			? (raw.distribution as JsonRecord)
			: undefined
	return {
		commentary,
		text: commentary,
		author: raw.author,
		visibility: raw.visibility as LinkedInVisibility | undefined,
		content,
		distribution,
		isReshareDisabledByAuthor: raw.isReshareDisabledByAuthor,
		dryRun: raw.dryRun,
		confirm: raw.confirm,
	}
})

export function parseCreatePostInput(input: unknown): CreatePostInput {
	try {
		const normalized = parse(CreatePostInputSchema, input ?? {})
		if (!normalized.commentary?.trim()) {
			throw new Error(
				'create-post requires commentary (aliases: text, message, body).',
			)
		}
		return normalized
	} catch (error) {
		if (error instanceof ValidationError) {
			throw formatSchemaValidationError(error, {
				surface: 'create-post',
				knownKeys: KNOWN_KEYS,
				hint: HINT,
				input,
			})
		}
		throw error
	}
}