import type { FigmaComment, FigmaComponent, FigmaFileMeta, FigmaFileSummary, FigmaImageFill, FigmaImageMap, FigmaNodeBundle, FigmaNodeSummary, FigmaStyle, FigmaUserRef, JsonRecord, } from './types.ts' function asRecord(value: unknown): JsonRecord { return value && typeof value === 'object' && !Array.isArray(value) ? (value as JsonRecord) : {} } export function mapUser(value: unknown): FigmaUserRef | null { const record = asRecord(value) const id = typeof record.id === 'string' ? record.id : '' if (!id) return null return { id, handle: typeof record.handle === 'string' ? record.handle : null, } } export function mapNode(value: unknown): FigmaNodeSummary | null { const record = asRecord(value) const id = typeof record.id === 'string' ? record.id : '' if (!id) return null const children = Array.isArray(record.children) ? record.children .map(mapNode) .filter((node): node is FigmaNodeSummary => node !== null) : [] return { id, name: typeof record.name === 'string' ? record.name : '', type: typeof record.type === 'string' ? record.type : '', visible: record.visible !== false, children, } } export function mapFile(fileKey: string, value: unknown): FigmaFileSummary { const record = asRecord(value) const components = record.components && typeof record.components === 'object' ? Object.keys(record.components as JsonRecord).length : 0 const styles = record.styles && typeof record.styles === 'object' ? Object.keys(record.styles as JsonRecord).length : 0 return { fileKey, name: typeof record.name === 'string' ? record.name : '', lastModified: typeof record.lastModified === 'string' ? record.lastModified : null, version: typeof record.version === 'string' ? record.version : null, role: typeof record.role === 'string' ? record.role : null, editorType: typeof record.editorType === 'string' ? record.editorType : null, thumbnailUrl: typeof record.thumbnailUrl === 'string' ? record.thumbnailUrl : null, document: mapNode(record.document), componentCount: components, styleCount: styles, } } export function mapFileMeta(fileKey: string, value: unknown): FigmaFileMeta { const record = asRecord(value) const file = asRecord(record.file) const source = Object.keys(file).length > 0 ? file : record return { fileKey, name: typeof source.name === 'string' ? source.name : '', lastModified: typeof source.last_touched_at === 'string' ? source.last_touched_at : typeof source.lastModified === 'string' ? source.lastModified : null, thumbnailUrl: typeof source.thumbnail_url === 'string' ? source.thumbnail_url : null, creator: mapUser(source.creator), lastTouchedBy: mapUser(source.last_touched_by), folderName: typeof source.folder_name === 'string' ? source.folder_name : null, editorType: typeof source.editor_type === 'string' ? source.editor_type : null, role: typeof source.role === 'string' ? source.role : null, version: typeof source.version === 'string' ? source.version : null, linkAccess: typeof source.link_access === 'string' ? source.link_access : null, url: typeof source.url === 'string' ? source.url : null, } } export function mapNodeBundle(id: string, value: unknown): FigmaNodeBundle { const record = asRecord(value) const components = record.components && typeof record.components === 'object' ? Object.keys(record.components as JsonRecord).length : 0 const styles = record.styles && typeof record.styles === 'object' ? Object.keys(record.styles as JsonRecord).length : 0 return { id, document: mapNode(record.document), componentCount: components, styleCount: styles, } } function mapReactions(value: unknown): Array<{ emoji: string; count: number }> { if (!Array.isArray(value)) return [] const counts = new Map() for (const item of value) { const record = asRecord(item) const emoji = typeof record.emoji === 'string' ? record.emoji : '' if (!emoji) continue counts.set(emoji, (counts.get(emoji) ?? 0) + 1) } return [...counts.entries()].map(([emoji, count]) => ({ emoji, count })) } export function mapComment(fileKey: string, value: unknown): FigmaComment { const record = asRecord(value) return { id: typeof record.id === 'string' ? record.id : '', message: typeof record.message === 'string' ? record.message : null, fileKey, parentId: typeof record.parent_id === 'string' ? record.parent_id : null, createdAt: typeof record.created_at === 'string' ? record.created_at : null, resolvedAt: typeof record.resolved_at === 'string' ? record.resolved_at : null, orderId: typeof record.order_id === 'string' ? record.order_id : null, user: mapUser(record.user), clientMeta: record.client_meta && typeof record.client_meta === 'object' ? (record.client_meta as JsonRecord) : null, reactions: mapReactions(record.reactions), } } function mapLibraryItem(value: unknown): { key: string fileKey: string | null nodeId: string | null name: string description: string | null thumbnailUrl: string | null createdAt: string | null updatedAt: string | null containingFrameName: string | null styleType: string | null } { const record = asRecord(value) const frame = asRecord(record.containing_frame) return { key: typeof record.key === 'string' ? record.key : '', fileKey: typeof record.file_key === 'string' ? record.file_key : null, nodeId: typeof record.node_id === 'string' ? record.node_id : null, name: typeof record.name === 'string' ? record.name : '', description: typeof record.description === 'string' ? record.description : null, thumbnailUrl: typeof record.thumbnail_url === 'string' ? record.thumbnail_url : null, createdAt: typeof record.created_at === 'string' ? record.created_at : null, updatedAt: typeof record.updated_at === 'string' ? record.updated_at : null, containingFrameName: typeof frame.name === 'string' ? frame.name : typeof record.containing_frame === 'string' ? record.containing_frame : null, styleType: typeof record.style_type === 'string' ? record.style_type : null, } } export function mapComponent(value: unknown): FigmaComponent { const item = mapLibraryItem(value) return { key: item.key, fileKey: item.fileKey, nodeId: item.nodeId, name: item.name, description: item.description, thumbnailUrl: item.thumbnailUrl, containingFrameName: item.containingFrameName, createdAt: item.createdAt, updatedAt: item.updatedAt, } } export function mapStyle(value: unknown): FigmaStyle { const item = mapLibraryItem(value) return { key: item.key, fileKey: item.fileKey, nodeId: item.nodeId, name: item.name, description: item.description, styleType: item.styleType, thumbnailUrl: item.thumbnailUrl, createdAt: item.createdAt, updatedAt: item.updatedAt, } } function valuesFromMetaMap(value: unknown, key: 'components' | 'styles'): Array { const root = asRecord(value) const meta = asRecord(root.meta) const collection = meta[key] ?? root[key] if (Array.isArray(collection)) return collection if (collection && typeof collection === 'object') return Object.values(collection) return [] } export function mapComponentList(value: unknown): Array { return valuesFromMetaMap(value, 'components') .map(mapComponent) .filter((item) => item.key.length > 0 || (item.nodeId ?? '').length > 0) } export function mapStyleList(value: unknown): Array { return valuesFromMetaMap(value, 'styles') .map(mapStyle) .filter((item) => item.key.length > 0 || (item.name ?? '').length > 0) } export function mapImageMap(fileKey: string, value: unknown): FigmaImageMap { const record = asRecord(value) const images = record.images && typeof record.images === 'object' ? (record.images as JsonRecord) : {} return { fileKey, images: Object.entries(images).map(([nodeId, url]) => ({ nodeId, url: typeof url === 'string' ? url : null, })), error: typeof record.err === 'string' ? record.err : null, } } export function mapImageFills(value: unknown): Array { const record = asRecord(value) const meta = asRecord(record.meta) const images = meta.images && typeof meta.images === 'object' ? (meta.images as JsonRecord) : record.images && typeof record.images === 'object' ? (record.images as JsonRecord) : {} return Object.entries(images) .filter((entry): entry is [string, string] => typeof entry[1] === 'string') .map(([hash, url]) => ({ hash, url })) } export function asArray(value: T | Array | null | undefined): Array { if (value === undefined || value === null) return [] return Array.isArray(value) ? value : [value] }