aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorMohamed Bassem <me@mbassem.com>2025-09-14 08:27:44 +0000
committerMohamed Bassem <me@mbassem.com>2025-09-14 08:42:58 +0000
commitbf5bf996c63cc3af92bc0f302ec37f7dbbc9e94a (patch)
tree24066b4d21a08f35387da3680ae1d549a6d82d08
parenta92ada7727b2596414aafe204e5001eb066569cb (diff)
downloadkarakeep-bf5bf996c63cc3af92bc0f302ec37f7dbbc9e94a.tar.zst
refactor: strongly type the search plugin interface
-rw-r--r--packages/plugins-search-meilisearch/src/index.ts18
-rw-r--r--packages/shared/search.ts22
-rw-r--r--packages/trpc/routers/bookmarks.ts11
3 files changed, 41 insertions, 10 deletions
diff --git a/packages/plugins-search-meilisearch/src/index.ts b/packages/plugins-search-meilisearch/src/index.ts
index d54fb6bc..77d5a57e 100644
--- a/packages/plugins-search-meilisearch/src/index.ts
+++ b/packages/plugins-search-meilisearch/src/index.ts
@@ -3,6 +3,7 @@ import { MeiliSearch } from "meilisearch";
import type {
BookmarkSearchDocument,
+ FilterQuery,
SearchIndexClient,
SearchOptions,
SearchResponse,
@@ -11,6 +12,19 @@ import { PluginProvider } from "@karakeep/shared/plugins";
import { envConfig } from "./env";
+function filterToMeiliSearchFilter(filter: FilterQuery): string {
+ switch (filter.type) {
+ case "eq":
+ return `${filter.field} = "${filter.value}"`;
+ case "in":
+ return `${filter.field} IN [${filter.values.join(",")}]`;
+ default: {
+ const exhaustiveCheck: never = filter;
+ throw new Error(`Unhandled color case: ${exhaustiveCheck}`);
+ }
+ }
+}
+
class MeiliSearchIndexClient implements SearchIndexClient {
constructor(private index: Index<BookmarkSearchDocument>) {}
@@ -28,10 +42,10 @@ class MeiliSearchIndexClient implements SearchIndexClient {
async search(options: SearchOptions): Promise<SearchResponse> {
const result = await this.index.search(options.query, {
- filter: options.filter,
+ filter: options.filter?.map((f) => filterToMeiliSearchFilter(f)),
limit: options.limit,
offset: options.offset,
- sort: options.sort,
+ sort: options.sort?.map((s) => `${s.field}:${s.order}`),
attributesToRetrieve: ["id"],
showRankingScore: true,
});
diff --git a/packages/shared/search.ts b/packages/shared/search.ts
index 5158f30f..d23ab29f 100644
--- a/packages/shared/search.ts
+++ b/packages/shared/search.ts
@@ -24,18 +24,34 @@ export const zBookmarkSearchDocument = z.object({
export type BookmarkSearchDocument = z.infer<typeof zBookmarkSearchDocument>;
+export type SortOrder = "asc" | "desc";
+export type SortableAttributes = "createdAt";
+
+export type FilterableAttributes = "userId" | "id";
+export type FilterQuery =
+ | {
+ type: "eq";
+ field: FilterableAttributes;
+ value: string;
+ }
+ | {
+ type: "in";
+ field: FilterableAttributes;
+ values: string[];
+ };
+
export interface SearchResult {
id: string;
score?: number;
}
export interface SearchOptions {
- // TODO: Make query, filter and sort strongly typed
query: string;
- filter?: string[];
+ // Diffeernt filters are ANDed together
+ filter?: FilterQuery[];
limit?: number;
offset?: number;
- sort?: string[];
+ sort?: { field: SortableAttributes; order: SortOrder }[];
}
export interface SearchResponse {
diff --git a/packages/trpc/routers/bookmarks.ts b/packages/trpc/routers/bookmarks.ts
index 31ffef4a..efd295f7 100644
--- a/packages/trpc/routers/bookmarks.ts
+++ b/packages/trpc/routers/bookmarks.ts
@@ -38,7 +38,7 @@ import {
triggerSearchReindex,
triggerWebhook,
} from "@karakeep/shared/queues";
-import { getSearchClient } from "@karakeep/shared/search";
+import { FilterQuery, getSearchClient } from "@karakeep/shared/search";
import { parseSearchQuery } from "@karakeep/shared/searchQueryParser";
import {
BookmarkTypes,
@@ -765,17 +765,18 @@ export const bookmarksAppRouter = router({
}
const parsedQuery = parseSearchQuery(input.text);
- let filter: string[];
+ let filter: FilterQuery[];
if (parsedQuery.matcher) {
const bookmarkIds = await getBookmarkIdsFromMatcher(
ctx,
parsedQuery.matcher,
);
filter = [
- `userId = '${ctx.user.id}' AND id IN [${bookmarkIds.join(",")}]`,
+ { type: "in", field: "id", values: bookmarkIds },
+ { type: "eq", field: "userId", value: ctx.user.id },
];
} else {
- filter = [`userId = '${ctx.user.id}'`];
+ filter = [{ type: "eq", field: "userId", value: ctx.user.id }];
}
/**
@@ -786,7 +787,7 @@ export const bookmarksAppRouter = router({
const resp = await client.search({
query: parsedQuery.text,
filter,
- sort: [`createdAt:${createdAtSortOrder}`],
+ sort: [{ field: "createdAt", order: createdAtSortOrder }],
limit: input.limit,
...(input.cursor
? {