@@ -17,8 +17,11 @@ const ASSET_META_KEYS = ['uid', 'url', 'filename', 'file_name', 'parent_uid'];
1717
1818type AssetRecord = { uid ?: string ; _uid ?: string ; url ?: string ; filename ?: string ; file_name ?: string } ;
1919
20- /** Per-space export counts surfaced to the summary (assets = downloaded binaries; folders = entities). */
21- export type SpaceExportCounts = { assets : number ; folders : number } ;
20+ /**
21+ * Per-space export counts surfaced to the summary (assets = downloaded binaries; folders = entities;
22+ * failedAssets = metadata records missing after permanent page failures + binary download failures).
23+ */
24+ export type SpaceExportCounts = { assets : number ; folders : number ; failedAssets : number } ;
2225
2326export default class ExportAssets extends CSAssetsExportAdapter {
2427 constructor ( apiConfig : CSAssetsAPIConfig , exportContext : ExportContext ) {
@@ -53,11 +56,22 @@ export default class ExportAssets extends CSAssetsExportAdapter {
5356 } ;
5457
5558 log . debug ( `Fetching folders and streaming assets for space ${ workspace . space_uid } ` , this . exportContext . context ) ;
56- const [ folders ] = await Promise . all ( [
59+ const [ folders , streamResult ] = await Promise . all ( [
5760 this . getWorkspaceFolders ( workspace . space_uid , workspace . uid , this . apiPageSize , this . apiFetchConcurrency ) ,
5861 this . streamWorkspaceAssets ( workspace . space_uid , workspace . uid , onPage , this . apiPageSize , this . apiFetchConcurrency ) ,
5962 ] ) ;
6063
64+ // Permanently failed metadata pages (or mid-export drift) — these records never reached
65+ // assets.json, so the download loop can't see them. Count them as failures in the summary;
66+ // they are recoverable via a re-export or a targeted query-export, so don't abort the run.
67+ if ( streamResult . missing > 0 ) {
68+ log . error (
69+ `${ streamResult . missing } asset metadata record(s) could not be fetched for space ${ workspace . space_uid } — ` +
70+ 'they are missing from this export. Re-export (or use query-export) to recover them.' ,
71+ this . exportContext . context ,
72+ ) ;
73+ }
74+
6175 if ( fsWriter ) fsWriter . completeFile ( true ) ;
6276 else await this . writeEmptyChunkedJson ( assetsDir , 'assets.json' ) ;
6377 log . debug ( `Wrote chunked assets metadata (${ totalStreamed } item(s)) under ${ assetsDir } ` , this . exportContext . context ) ;
@@ -78,17 +92,21 @@ export default class ExportAssets extends CSAssetsExportAdapter {
7892 this . tick ( true , `metadata: ${ workspace . space_uid } (${ totalStreamed } )` , null ) ;
7993
8094 log . debug ( `Starting binary downloads for space ${ workspace . space_uid } ` , this . exportContext . context ) ;
81- const assetsDownloaded = await this . downloadWorkspaceAssets ( assetsDir , workspace . space_uid , downloadableCount ) ;
95+ const downloads = await this . downloadWorkspaceAssets ( assetsDir , workspace . space_uid , downloadableCount ) ;
8296
8397 const folderCount = getArrayFromResponse ( folders , 'folders' ) . length ;
84- return { assets : assetsDownloaded , folders : folderCount } ;
98+ return { assets : downloads . ok , folders : folderCount , failedAssets : streamResult . missing + downloads . fail } ;
8599 }
86100
87101 /**
88102 * Download asset binaries by reading the just-written chunked `assets.json` back from disk
89103 * (one chunk at a time), so we never re-materialize the whole asset list in memory.
90104 */
91- private async downloadWorkspaceAssets ( assetsDir : string , spaceUid : string , expectedDownloads : number ) : Promise < number > {
105+ private async downloadWorkspaceAssets (
106+ assetsDir : string ,
107+ spaceUid : string ,
108+ expectedDownloads : number ,
109+ ) : Promise < { ok : number ; fail : number } > {
92110 const filesDir = pResolve ( assetsDir , 'files' ) ;
93111 await mkdir ( filesDir , { recursive : true } ) ;
94112
@@ -202,7 +220,7 @@ export default class ExportAssets extends CSAssetsExportAdapter {
202220 this . exportContext . context ,
203221 ) ;
204222
205- return downloadOk ;
223+ return { ok : downloadOk , fail : downloadFail } ;
206224 }
207225
208226}
0 commit comments