if(record?.type!=='manifest'||record.format!==ARCHIVE_FORMAT||record.archiveVersion!==ARCHIVE_VERSION)throwarchiveError('This is not a supported Bend Field archive.','UNSUPPORTED_ARCHIVE');
if(record.compression!==compression)throwarchiveError('The archive compression declaration does not match its contents.');
if(record.encoding!=='ndjson')throwarchiveError('The archive encoding is not supported.','UNSUPPORTED_ARCHIVE');
if(!Number.isSafeInteger(record.boardCount)||record.boardCount<1)throwarchiveError('The archive board count is invalid.');
if(!Number.isSafeInteger(record.estimatedRawBytes)||record.estimatedRawBytes<0||record.estimatedRawBytes>MAX_ARCHIVE_RAW_BYTES)throwarchiveError('The archive size declaration is invalid.','ARCHIVE_TOO_LARGE');
return{encode:(records,includeInCrc=true)=>request('encode',{records,includeInCrc}),parse:lines=>request('parse',{lines}),snapshot:()=>request('snapshot'),terminate(){for(constentryofpending.values())entry.reject(archiveError('The archive worker was terminated.','ARCHIVE_WORKER'));pending.clear();worker.terminate()}};
if(phase!=='boards')throwarchiveError('The archive contains records after its footer.');
if(record?.type==='board'){
constnumber=boardNumber(record.id);if(number<0||number<=previousBoardNumber)throwarchiveError('Archive boards are duplicated or not in numeric order.');
if(record.id==='B0'&&boardsDone!==0)throwarchiveError('B0 must be the first board.');if(boardsDone===0&&record.id!=='B0')throwarchiveError('The archive does not begin with B0.');if(boardsDone>=manifest.boardCount)throwarchiveError('The archive contains more boards than declared.');
footer=record;phase='done';if(boardsDone!==manifest.boardCount||record.boardCount!==boardsDone)throwarchiveError('The archive board count does not match its footer.');if(record.rawBytes!==state.rawBytes||String(record.crc32).toLowerCase()!==state.crc32)throwarchiveError('The archive integrity check failed.','CHECKSUM_MISMATCH');return;
}
throwarchiveError('The archive contains an unknown or misplaced record.');
if(codec){constresult=awaitcodec.parse(lines);for(constitemofresult.items){if(item.byteLength>MAX_ARCHIVE_LINE_BYTES)throwarchiveError('An archive record exceeded the size limit.','LINE_TOO_LARGE');awaitprocessRecord(item.record,item)}return}
for(constsourceLineoflines){letparsed;try{parsed=parseRecordLine(sourceLine)}catch(error){throwarchiveError(error.message)}const{line,record,bytes:lineBytes}=parsed;if(!line)throwarchiveError('The archive contains an empty record.');if(lineBytes.byteLength>MAX_ARCHIVE_LINE_BYTES)throwarchiveError('An archive record exceeded the size limit.','LINE_TOO_LARGE');if(record?.type!=='end'){crc=crc32Update(crc,lineBytes);rawBytes+=lineBytes.byteLength;crcHex=crc32Hex(crc)}awaitprocessRecord(record,{rawBytes,crc32:crcHex})}
while(true){throwIfAborted(signal);const{value,done}=awaitreader.read();if(done)break;decodedBytes+=value.byteLength;if(decodedBytes>MAX_ARCHIVE_RAW_BYTES)throwarchiveError('The expanded archive exceeded the size limit.','ARCHIVE_TOO_LARGE');if(decoded.compression==='gzip'&&decodedBytes/Math.max(1,file.size)>MAX_ARCHIVE_EXPANSION_RATIO)throwarchiveError('The archive expansion ratio is unsafe.','EXPANSION_LIMIT');buffer+=decoder.decode(value,{stream:true});if(encodeUtf8(buffer).byteLength>MAX_ARCHIVE_LINE_BYTES&&!buffer.includes('\n'))throwarchiveError('An archive record exceeded the size limit.','LINE_TOO_LARGE');letnewline;while((newline=buffer.indexOf('\n'))>=0){constline=buffer.slice(0,newline);buffer=buffer.slice(newline+1);if(!line)throwarchiveError('The archive contains an empty record.');constestimatedBytes=line.length*3+1;if(pendingLines.length&&(pendingLines.length>=MAX_WORKER_RECORDS||pendingLineBytes+estimatedBytes>WORKER_TARGET_BYTES))awaitflushLines();pendingLines.push(line);pendingLineBytes+=estimatedBytes;if(pendingLines.length>=MAX_WORKER_RECORDS||pendingLineBytes>=WORKER_TARGET_BYTES)awaitflushLines()}}
buffer+=decoder.decode();if(buffer){constestimatedBytes=buffer.length*3+1;if(pendingLines.length&&pendingLineBytes+estimatedBytes>WORKER_TARGET_BYTES)awaitflushLines();pendingLines.push(buffer);pendingLineBytes+=estimatedBytes}awaitflushLines();if(phase!=='done'||!footer)throwarchiveError('The archive footer is missing or incomplete.');report({phase:'validate',boardsDone,boardsTotal:manifest.boardCount,bytesRead:decodedBytes,bytesWritten:0},true);return{manifest,globalState,footer,boardCount:boardsDone,decodedBytes,compression:decoded.compression,worker:codec!=null};