(value: unknown)
| 85 | * uploaded files, so callers degrade gracefully instead of throwing. |
| 86 | */ |
| 87 | export function parseInputFormatFiles(value: unknown): InputFormatFile[] { |
| 88 | let raw: unknown = value |
| 89 | if (typeof raw === 'string') { |
| 90 | const trimmed = raw.trim() |
| 91 | if (!trimmed) return [] |
| 92 | try { |
| 93 | raw = JSON.parse(trimmed) |
| 94 | } catch { |
| 95 | return [] |
| 96 | } |
| 97 | } |
| 98 | |
| 99 | if (!Array.isArray(raw)) return [] |
| 100 | |
| 101 | return raw.filter((file): file is InputFormatFile => { |
| 102 | if (file === null || typeof file !== 'object') return false |
| 103 | const f = file as InputFormatFile |
| 104 | // Accept only the run-ready shape `normalizeStartFile` accepts (non-empty |
| 105 | // id/name/url/type + finite size + recoverable key); file normalization is |
| 106 | // all-or-nothing, so anything short of this falls back to the JSON editor |
| 107 | // rather than silently dropping every file at run time. |
| 108 | return ( |
| 109 | typeof f.id === 'string' && |
| 110 | f.id.length > 0 && |
| 111 | typeof f.name === 'string' && |
| 112 | f.name.length > 0 && |
| 113 | typeof f.url === 'string' && |
| 114 | f.url.length > 0 && |
| 115 | typeof f.size === 'number' && |
| 116 | Number.isFinite(f.size) && |
| 117 | typeof f.type === 'string' && |
| 118 | f.type.length > 0 && |
| 119 | hasRecoverableFileKey(f) |
| 120 | ) |
| 121 | }) |
| 122 | } |
| 123 | |
| 124 | /** |
| 125 | * Collects all editor-attached files from the file-typed fields of an |
no test coverage detected