aboutsummaryrefslogtreecommitdiffstats
path: root/packages/trpc/models/lists.ts
diff options
context:
space:
mode:
authorMohamed Bassem <me@mbassem.com>2026-02-09 01:03:29 +0000
committerGitHub <noreply@github.com>2026-02-09 01:03:29 +0000
commitb3d3602dc5af6768e5b03613a7ca22ad3b47ec8d (patch)
tree800cf46d50a7fa8e90abab186475b5215d1b2ceb /packages/trpc/models/lists.ts
parentb41b5647aa10d22ca83cfd3ba97146681e9f28a3 (diff)
downloadkarakeep-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/lists.ts')
-rw-r--r--packages/trpc/models/lists.ts27
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)