From f0beddf849f47832cdfcaadfbf22a73e0c6bbccf Mon Sep 17 00:00:00 2001 From: Yanay Date: Wed, 1 Jul 2026 16:38:50 +0300 Subject: [PATCH 1/3] feat(search): add matchUsers wrapper for POST /match/users Thin POST wrapper mirroring searchUsers, typed to the match request body and the { results: [...] } breakdown envelope, exposed via client.search.matchUsers. Co-Authored-By: Claude Opus 4.8 --- src/modules/search/index.ts | 1 + src/modules/search/matchUsers.ts | 45 ++++++++++++++++++++++++++++++++ 2 files changed, 46 insertions(+) create mode 100644 src/modules/search/matchUsers.ts diff --git a/src/modules/search/index.ts b/src/modules/search/index.ts index e305f7e..ed12816 100644 --- a/src/modules/search/index.ts +++ b/src/modules/search/index.ts @@ -2,3 +2,4 @@ export { searchContent } from "./searchContent"; export { searchUsers } from "./searchUsers"; export { searchSpaces } from "./searchSpaces"; export { askContent } from "./askContent"; +export { matchUsers } from "./matchUsers"; diff --git a/src/modules/search/matchUsers.ts b/src/modules/search/matchUsers.ts new file mode 100644 index 0000000..3395898 --- /dev/null +++ b/src/modules/search/matchUsers.ts @@ -0,0 +1,45 @@ +import { SublayHttpClient } from "../../core/client"; +import { User } from "../../interfaces/User"; + +export interface MatchUsersProps { + mode: "passive" | "directed"; + query?: string; + limit?: number; + spaceId?: string; + includeChildSpaces?: boolean; + includeSampleContent?: boolean; + excludeSelf?: boolean; +} + +export interface MatchFacetRef { + id: string; + hotness: number; +} + +export interface MatchedFacet { + similarity: number; + askerFacet?: MatchFacetRef; + candidateFacet: MatchFacetRef; + sampleContent?: any[]; +} + +export interface UserMatchResult { + user: User; + score: number; + matchedFacets: MatchedFacet[]; +} + +export interface MatchUsersResponse { + results: UserMatchResult[]; +} + +export async function matchUsers( + client: SublayHttpClient, + data: MatchUsersProps +): Promise { + const response = await client.projectInstance.post( + "/match/users", + data + ); + return response.data; +} From 285dc9de11ac63ca25f7007d96bc8c39702d7606 Mon Sep 17 00:00:00 2001 From: Yanay Date: Wed, 1 Jul 2026 17:21:50 +0300 Subject: [PATCH 2/3] test(search): cover matchUsers request/response shaping Co-Authored-By: Claude Opus 4.8 --- __tests__/search.test.ts | 56 +++++++++++++++++++++++++++++++++++++++- 1 file changed, 55 insertions(+), 1 deletion(-) diff --git a/__tests__/search.test.ts b/__tests__/search.test.ts index 4a8c3c6..bebcad9 100644 --- a/__tests__/search.test.ts +++ b/__tests__/search.test.ts @@ -1,4 +1,5 @@ -import { askContent, searchContent, searchSpaces, searchUsers } from "../src/modules/search"; +import { askContent, matchUsers, searchContent, searchSpaces, searchUsers } from "../src/modules/search"; +import type { MatchUsersResponse } from "../src/modules/search/matchUsers"; import { makeClient } from "./helpers/mockClient"; describe("node-sdk search — request shaping", () => { @@ -49,6 +50,36 @@ describe("node-sdk search — request shaping", () => { { params: { spaceReputationId: "rep1", spaceReputationDescendants: false } }, ); }); + + it("matchUsers POSTs the full body to /match/users", async () => { + const { client, projectInstance } = makeClient(); + await matchUsers(client, { + mode: "directed", + query: "biotech", + limit: 10, + spaceId: "space-1", + includeChildSpaces: true, + includeSampleContent: true, + excludeSelf: false, + }); + expect(projectInstance.post).toHaveBeenCalledWith("/match/users", { + mode: "directed", + query: "biotech", + limit: 10, + spaceId: "space-1", + includeChildSpaces: true, + includeSampleContent: true, + excludeSelf: false, + }); + }); + + it("matchUsers passes a minimal passive body through unchanged", async () => { + const { client, projectInstance } = makeClient(); + await matchUsers(client, { mode: "passive" }); + expect(projectInstance.post).toHaveBeenCalledWith("/match/users", { + mode: "passive", + }); + }); }); describe("node-sdk search — response mapping", () => { @@ -81,4 +112,27 @@ describe("node-sdk search — response mapping", () => { askContent(client, { query: "what is this space about?" }), ).resolves.toEqual(result); }); + + it("matchUsers returns the { results } envelope from the response body", async () => { + const { client, projectInstance } = makeClient(); + const envelope: MatchUsersResponse = { + results: [ + { + user: { id: "u1" } as MatchUsersResponse["results"][number]["user"], + score: 1.5, + matchedFacets: [ + { + similarity: 0.7, + askerFacet: { id: "af", hotness: 3 }, + candidateFacet: { id: "cf", hotness: 4 }, + }, + ], + }, + ], + }; + projectInstance.post.mockResolvedValueOnce({ data: envelope }); + const res = await matchUsers(client, { mode: "passive" }); + expect(res).toEqual(envelope); + expect(res.results[0].matchedFacets[0].candidateFacet.hotness).toBe(4); + }); }); From d1eeea53362afbfd56206d81d8d884c3f8037ff3 Mon Sep 17 00:00:00 2001 From: Yanay Date: Wed, 1 Jul 2026 18:14:24 +0300 Subject: [PATCH 3/3] refactor(search): type matchUsers sampleContent concretely Replace the placeholder any[] with a SampleContent type mirroring the server's { sourceType, recordId, content, similarity } shape. Co-Authored-By: Claude Opus 4.8 --- src/modules/search/matchUsers.ts | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/src/modules/search/matchUsers.ts b/src/modules/search/matchUsers.ts index 3395898..d271dd2 100644 --- a/src/modules/search/matchUsers.ts +++ b/src/modules/search/matchUsers.ts @@ -16,11 +16,18 @@ export interface MatchFacetRef { hotness: number; } +export interface SampleContent { + sourceType: "entity" | "comment" | "message"; + recordId: string; + content: string; + similarity: number; +} + export interface MatchedFacet { similarity: number; askerFacet?: MatchFacetRef; candidateFacet: MatchFacetRef; - sampleContent?: any[]; + sampleContent?: SampleContent[]; } export interface UserMatchResult {