export type JsonRecord = Record<string, unknown>
export type FigmaAuthMode = 'oauth' | 'pat'
export type FigmaAuthInput = {
/** Saved OAuth integration name. Defaults to `figma`. */
integrationName?: string
/** Alias of `integrationName`. */
integration?: string
/**
* Short account alias. `work` → `figma-work`. Values that already start
* with `figma-` are used as-is. `default` / `figma` / omitted → `figma`.
*/
account?: string
/** User secret name for a personal or plan access token. Defaults to `figmaPat`. */
secretName?: string
/** Force OAuth or PAT auth when both exist. */
auth?: FigmaAuthMode
}
export type FigmaUserRef = {
id: string
handle: string | null
}
export type FigmaNodeSummary = {
id: string
name: string
type: string
visible: boolean
children: Array<FigmaNodeSummary>
}
export type FigmaFileSummary = {
fileKey: string
name: string
lastModified: string | null
version: string | null
role: string | null
editorType: string | null
thumbnailUrl: string | null
document: FigmaNodeSummary | null
componentCount: number
styleCount: number
}
export type FigmaFileMeta = {
fileKey: string
name: string
lastModified: string | null
thumbnailUrl: string | null
creator: FigmaUserRef | null
lastTouchedBy: FigmaUserRef | null
folderName: string | null
editorType: string | null
role: string | null
version: string | null
linkAccess: string | null
url: string | null
}
export type FigmaNodeBundle = {
id: string
document: FigmaNodeSummary | null
componentCount: number
styleCount: number
}
export type FigmaComment = {
id: string
message: string | null
fileKey: string
parentId: string | null
createdAt: string | null
resolvedAt: string | null
orderId: string | null
user: FigmaUserRef | null
clientMeta: JsonRecord | null
reactions: Array<{ emoji: string; count: number }>
}
export type FigmaComponent = {
key: string
fileKey: string | null
nodeId: string | null
name: string
description: string | null
thumbnailUrl: string | null
containingFrameName: string | null
createdAt: string | null
updatedAt: string | null
}
export type FigmaStyle = {
key: string
fileKey: string | null
nodeId: string | null
name: string
description: string | null
styleType: string | null
thumbnailUrl: string | null
createdAt: string | null
updatedAt: string | null
}
export type FigmaImageMap = {
fileKey: string
images: Array<{ nodeId: string; url: string | null }>
error: string | null
}
export type FigmaImageFill = {
hash: string
url: string
}
export type DryRunResult<T> = {
dryRun: true
wouldCall: T
}
export function requireRecord(value: unknown, label: string): JsonRecord {
if (!value || typeof value !== 'object' || Array.isArray(value)) {
throw new Error(`${label} must be an object.`)
}
return value as JsonRecord
}
export function requireString(
value: unknown,
label: string,
options: { allowEmpty?: boolean } = {},
): string {
if (typeof value !== 'string' || (!options.allowEmpty && value.trim().length === 0)) {
throw new Error(
`${label} must be ${options.allowEmpty ? 'a string' : 'a non-empty string'}.`,
)
}
return options.allowEmpty ? value : value.trim()
}
export function optionalString(value: unknown, label: string): string | undefined {
if (value === undefined || value === null) return undefined
return requireString(value, label)
}
export function optionalBoolean(value: unknown, label: string): boolean | undefined {
if (value === undefined) return undefined
if (typeof value !== 'boolean') throw new Error(`${label} must be a boolean.`)
return value
}
export function optionalNumber(value: unknown, label: string): number | undefined {
if (value === undefined || value === null) return undefined
if (typeof value !== 'number' || Number.isNaN(value)) {
throw new Error(`${label} must be a number.`)
}
return value
}
export function clampInt(
value: unknown,
min: number,
max: number,
fallback: number,
label = 'limit',
): number {
if (value === undefined || value === null) return fallback
if (typeof value !== 'number' || !Number.isInteger(value)) {
throw new Error(`${label} must be an integer.`)
}
if (value < min || value > max) {
throw new Error(`${label} must be between ${min} and ${max}.`)
}
return value
}
export function compactRecord<T extends JsonRecord>(value: T): T {
const output: JsonRecord = {}
for (const [key, item] of Object.entries(value)) {
if (item === undefined) continue
output[key] = item
}
return output as T
}
export function requireStringArray(value: unknown, label: string): Array<string> {
if (typeof value === 'string') {
return value
.split(',')
.map((item) => item.trim())
.filter((item) => item.length > 0)
}
if (!Array.isArray(value) || value.length === 0) {
throw new Error(`${label} must be a non-empty string or array of strings.`)
}
return value.map((item, index) => requireString(item, `${label}[${index}]`))
}
export function optionalStringArray(value: unknown, label: string): Array<string> | undefined {
if (value === undefined || value === null) return undefined
return requireStringArray(value, label)
}
/**
* Shared Figma types for this package.
* @example
* import types from 'kody:@kody/figma/types'
* const catalog = await types()
*/
export default async function typesOverview() {
return {
auth: ['FigmaAuthInput', 'FigmaAuthMode'],
models: [
'FigmaFileSummary',
'FigmaFileMeta',
'FigmaNodeSummary',
'FigmaComment',
'FigmaComponent',
'FigmaStyle',
'FigmaImageMap',
],
}
}