ignore errors when fetching collection items

This commit is contained in:
Hazelnoot 2025-09-16 12:19:27 -04:00
parent 2f11ddb767
commit 2514ebd166

View file

@ -24,6 +24,7 @@ import { isPureRenote } from '@/misc/is-renote.js';
import { CacheService } from '@/core/CacheService.js'; import { CacheService } from '@/core/CacheService.js';
import { trackPromise } from '@/misc/promise-tracker.js'; import { trackPromise } from '@/misc/promise-tracker.js';
import { promiseMap } from '@/misc/promise-map.js'; import { promiseMap } from '@/misc/promise-map.js';
import { renderInlineError } from '@/misc/render-inline-error.js';
import { AnyCollection, getApId, getNullableApId, IObjectWithId, isCollection, isCollectionOrOrderedCollection, isCollectionPage, isOrderedCollection, isOrderedCollectionPage } from './type.js'; import { AnyCollection, getApId, getNullableApId, IObjectWithId, isCollection, isCollectionOrOrderedCollection, isCollectionPage, isOrderedCollection, isOrderedCollectionPage } from './type.js';
import { ApDbResolverService } from './ApDbResolverService.js'; import { ApDbResolverService } from './ApDbResolverService.js';
import { ApRendererService } from './ApRendererService.js'; import { ApRendererService } from './ApRendererService.js';
@ -94,9 +95,10 @@ export class Resolver {
* @param allowAnonymous If true, collection items can be anonymous (lack an ID). If false (default), then an error is thrown when reaching an item without ID. * @param allowAnonymous If true, collection items can be anonymous (lack an ID). If false (default), then an error is thrown when reaching an item without ID.
* @param sentFromUri If collection is an object, this is the URI where it was sent from. * @param sentFromUri If collection is an object, this is the URI where it was sent from.
* @param concurrency Maximum number of items to resolve at once. (default: 4) * @param concurrency Maximum number of items to resolve at once. (default: 4)
* @param ignoreErrors If true (default), inaccessible items will be skipped instead of causing an exception. Inaccessible collections will still throw.
*/ */
@bindThis @bindThis
public async resolveCollectionItems(collection: string | IObject, allowAnonymous = false, sentFromUri?: string, limit?: number | null, concurrency = 4): Promise<IObject[]> { public async resolveCollectionItems(collection: string | IObject, allowAnonymous = false, sentFromUri?: string, limit?: number | null, concurrency = 4, ignoreErrors = true): Promise<IObject[]> {
const resolvedItems: IObject[] = []; const resolvedItems: IObject[] = [];
// This is pulled up to avoid code duplication below // This is pulled up to avoid code duplication below
@ -104,7 +106,7 @@ export class Resolver {
const sentFrom = current.id; const sentFrom = current.id;
const itemArr = toArray(items); const itemArr = toArray(items);
const itemLimit = limit ?? Number.MAX_SAFE_INTEGER; const itemLimit = limit ?? Number.MAX_SAFE_INTEGER;
await this.resolveItemArray(itemArr, sentFrom, itemLimit, concurrency, allowAnonymous, resolvedItems); await this.resolveItemArray(itemArr, sentFrom, itemLimit, concurrency, allowAnonymous, resolvedItems, ignoreErrors);
}; };
let current: AnyCollection | null = await this.resolveCollection(collection, allowAnonymous, sentFromUri); let current: AnyCollection | null = await this.resolveCollection(collection, allowAnonymous, sentFromUri);
@ -138,29 +140,38 @@ export class Resolver {
return resolvedItems; return resolvedItems;
} }
private async resolveItemArray(source: (string | IObject)[], sentFrom: undefined, itemLimit: number, concurrency: number, allowAnonymousItems: true, destination: IAnonymousObject[]): Promise<void>; private async resolveItemArray(source: (string | IObject)[], sentFrom: string | undefined, itemLimit: number, concurrency: number, allowAnonymousItems: boolean, destination: IObject[], ignoreErrors?: boolean): Promise<void> {
private async resolveItemArray(source: (string | IObject)[], sentFrom: string, itemLimit: number, concurrency: number, allowAnonymousItems: boolean, destination: IObjectWithId[]): Promise<void>;
private async resolveItemArray(source: (string | IObject)[], sentFrom: string | undefined, itemLimit: number, concurrency: number, allowAnonymousItems: boolean, destination: IObject[]): Promise<void>;
private async resolveItemArray(source: (string | IObject)[], sentFrom: string | undefined, itemLimit: number, concurrency: number, allowAnonymousItems: boolean, destination: IObject[]): Promise<void> {
const recursionLimit = this.recursionLimit - this.history.size; const recursionLimit = this.recursionLimit - this.history.size;
const batchLimit = Math.min(source.length, recursionLimit, itemLimit); const batchLimit = Math.min(source.length, recursionLimit, itemLimit);
const batch = await promiseMap(source.slice(0, batchLimit), async item => { const batch = await promiseMap(source.slice(0, batchLimit), async item => {
if (sentFrom) { try {
// Use secureResolve to avoid re-fetching items that were included inline. if (sentFrom) {
return await this.secureResolve(item, sentFrom, allowAnonymousItems); // Use secureResolve to avoid re-fetching items that were included inline.
} else if (allowAnonymousItems) { return await this.secureResolve(item, sentFrom, allowAnonymousItems);
return await this.resolveAnonymous(item); } else if (allowAnonymousItems) {
} else { return await this.resolveAnonymous(item);
// ID is required if we have neither sentFrom not allowAnonymousItems } else {
const id = getApId(item); // ID is required if we have neither sentFrom not allowAnonymousItems
return await this.resolve(id); const id = getApId(item);
return await this.resolve(id);
}
} catch (err) {
if (ignoreErrors) {
this.logger.warn(`Ignoring error in collection item ${getNullableApId(item)}: ${renderInlineError(err)}`);
return null;
} else {
throw err;
}
} }
}, { }, {
limit: concurrency, limit: concurrency,
}); });
destination.push(...batch); // Items will be null if a request fails and ignoreErrors is true
const batchItems = batch.filter(item => item != null);
destination.push(...batchItems);
}; };
/** /**