diff options
| author | Mohamed Bassem <me@mbassem.com> | 2026-02-09 01:03:29 +0000 |
|---|---|---|
| committer | GitHub <noreply@github.com> | 2026-02-09 01:03:29 +0000 |
| commit | b3d3602dc5af6768e5b03613a7ca22ad3b47ec8d (patch) | |
| tree | 800cf46d50a7fa8e90abab186475b5215d1b2ceb /packages/trpc/models | |
| parent | b41b5647aa10d22ca83cfd3ba97146681e9f28a3 (diff) | |
| download | karakeep-b3d3602dc5af6768e5b03613a7ca22ad3b47ec8d.tar.zst | |
fix: Support nested smart lists with cycle detection (#2470)
* fix: Support nested smart lists and prevent infinite loops
---------
Co-authored-by: Claude <noreply@anthropic.com>
Diffstat (limited to 'packages/trpc/models')
| -rw-r--r-- | packages/trpc/models/lists.ts | 27 |
1 files changed, 22 insertions, 5 deletions
diff --git a/packages/trpc/models/lists.ts b/packages/trpc/models/lists.ts index 29945b92..10d7d9bf 100644 --- a/packages/trpc/models/lists.ts +++ b/packages/trpc/models/lists.ts @@ -809,8 +809,8 @@ export abstract class List { } abstract get type(): "manual" | "smart"; - abstract getBookmarkIds(ctx: AuthedContext): Promise<string[]>; - abstract getSize(ctx: AuthedContext): Promise<number>; + abstract getBookmarkIds(visitedListIds?: Set<string>): Promise<string[]>; + abstract getSize(): Promise<number>; abstract addBookmark(bookmarkId: string): Promise<void>; abstract removeBookmark(bookmarkId: string): Promise<void>; abstract mergeInto( @@ -820,6 +820,8 @@ export abstract class List { } export class SmartList extends List { + private static readonly MAX_VISITED_LISTS = 30; + parsedQuery: ReturnType<typeof parseSearchQuery> | null = null; constructor(ctx: AuthedContext, list: ZBookmarkList & { userId: string }) { @@ -847,12 +849,27 @@ export class SmartList extends List { return this.parsedQuery; } - async getBookmarkIds(): Promise<string[]> { + async getBookmarkIds(visitedListIds = new Set<string>()): Promise<string[]> { + if (visitedListIds.size >= SmartList.MAX_VISITED_LISTS) { + return []; + } + + if (visitedListIds.has(this.list.id)) { + return []; + } + + const newVisitedListIds = new Set(visitedListIds); + newVisitedListIds.add(this.list.id); + const parsedQuery = this.getParsedQuery(); if (!parsedQuery.matcher) { return []; } - return await getBookmarkIdsFromMatcher(this.ctx, parsedQuery.matcher); + return await getBookmarkIdsFromMatcher( + this.ctx, + parsedQuery.matcher, + newVisitedListIds, + ); } async getSize(): Promise<number> { @@ -898,7 +915,7 @@ export class ManualList extends List { return this.list.type; } - async getBookmarkIds(): Promise<string[]> { + async getBookmarkIds(_visitedListIds?: Set<string>): Promise<string[]> { const results = await this.ctx.db .select({ id: bookmarksInLists.bookmarkId }) .from(bookmarksInLists) |
