@singi-labs/sifa-sdk 0.6.0 → 0.7.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.cjs CHANGED
@@ -1088,7 +1088,7 @@ var ProfileVolunteeringRecordSchema = zod.z.object({
1088
1088
  });
1089
1089
 
1090
1090
  // src/index.ts
1091
- var SIFA_SDK_VERSION = "0.6.0";
1091
+ var SIFA_SDK_VERSION = "0.7.0";
1092
1092
 
1093
1093
  exports.CATEGORY_LABELS = CATEGORY_LABELS;
1094
1094
  exports.CATEGORY_ORDER = CATEGORY_ORDER;
package/dist/index.js CHANGED
@@ -1086,7 +1086,7 @@ var ProfileVolunteeringRecordSchema = z.object({
1086
1086
  });
1087
1087
 
1088
1088
  // src/index.ts
1089
- var SIFA_SDK_VERSION = "0.6.0";
1089
+ var SIFA_SDK_VERSION = "0.7.0";
1090
1090
 
1091
1091
  export { CATEGORY_LABELS, CATEGORY_ORDER, CONTINENTS, COUNTRIES, EndorsementConfirmationRecordSchema, EndorsementRecordSchema, GraphFollowRecordSchema, INDUSTRY_OPTIONS, PLATFORM_LABELS, PLATFORM_OPTIONS, ProfileCertificationRecordSchema, ProfileCourseRecordSchema, ProfileEducationRecordSchema, ProfileExternalAccountRecordSchema, ProfileHonorRecordSchema, ProfileLanguageRecordSchema, ProfilePositionRecordSchema, ProfileProjectRecordSchema, ProfilePublicationRecordSchema, ProfileSelfRecordSchema, ProfileSkillRecordSchema, ProfileVolunteeringRecordSchema, PublicationAuthorSchema, SIFA_SDK_VERSION, SKILL_CATEGORIES, atUriSchema, certDateExtractor, cidSchema, contrastRatio, countryCodeToFlag, dateRangeExtractor, datetimeSchema, dedupeSkills, detectPdsProvider, didSchema, findIndustry, formatDistanceToNow, formatLocation, formatRelativeTime, getContinent, getDisplayLabel, getFaviconUrl, getHandleStem, getIndustryLabelKey, getPdsDisplayName, getPlatformLabel, groupSkillsByCategory, isKnownPlatform, isValidRgbColor, languageTagSchema, lexiconDateExtractor, maxGraphemes, meetsContrastAA, parseLocationString, pdsProviderFromApi, relativeLuminance, rgbToString, sanitizeHandleInput, selfLabelsSchema, singleDateExtractor, sortByDateDesc, strongRefSchema, truncateGraphemes, uriSchema };
1092
1092
  //# sourceMappingURL=index.js.map
@@ -108,6 +108,123 @@ function createPosition(config, data, options = {}) {
108
108
  });
109
109
  }
110
110
 
111
+ // src/query/fetchers/search.ts
112
+ var EMPTY_SEARCH = { profiles: [], total: 0, limit: 20, offset: 0 };
113
+ var EMPTY_FILTERS = { countries: [], industries: [], apps: [] };
114
+ async function fetchSearchProfiles(config, filters, options = {}) {
115
+ const params = new URLSearchParams();
116
+ if (filters.q) params.set("q", filters.q);
117
+ if (filters.skill) params.set("skill", filters.skill);
118
+ if (filters.country) params.set("country", filters.country);
119
+ if (filters.industry) params.set("industry", filters.industry);
120
+ if (filters.domain) params.set("domain", filters.domain);
121
+ if (filters.workplace) params.set("workplace", filters.workplace);
122
+ if (filters.app) params.set("app", filters.app);
123
+ if (filters.limit !== void 0) params.set("limit", String(filters.limit));
124
+ if (params.size === 0) return EMPTY_SEARCH;
125
+ return apiFetch(config, `/api/search/profiles?${params.toString()}`, {
126
+ cache: "no-store",
127
+ ...options
128
+ });
129
+ }
130
+ async function fetchSkillSuggestions(config, query, options = {}) {
131
+ if (!query.trim()) return [];
132
+ const path = `/api/search/skills?q=${encodeURIComponent(query)}&limit=8`;
133
+ const data = await apiFetch(config, path, {
134
+ cache: "no-store",
135
+ ...options
136
+ });
137
+ return data.skills ?? [];
138
+ }
139
+ async function fetchSearchFilters(config, options = {}) {
140
+ try {
141
+ return await apiFetch(config, "/api/search/filters", {
142
+ next: { revalidate: 300 },
143
+ ...options
144
+ });
145
+ } catch {
146
+ return EMPTY_FILTERS;
147
+ }
148
+ }
149
+
150
+ // src/query/fetchers/discovery.ts
151
+ async function fetchSimilarProfiles(config, did, opts = {}) {
152
+ const limit = opts.limit ?? 5;
153
+ const path = `/api/discover/similar/${encodeURIComponent(did)}?limit=${limit}`;
154
+ try {
155
+ const data = await apiFetch(config, path, {
156
+ next: { revalidate: 300 },
157
+ timeoutMs: 5e3,
158
+ ...opts
159
+ });
160
+ return data.profiles ?? [];
161
+ } catch {
162
+ return [];
163
+ }
164
+ }
165
+ async function fetchSuggestions(config, opts = {}) {
166
+ const params = new URLSearchParams();
167
+ if (opts.source) params.set("source", opts.source);
168
+ if (opts.includeDismissed) params.set("include_dismissed", "true");
169
+ if (opts.cursor) params.set("cursor", opts.cursor);
170
+ if (opts.limit) params.set("limit", String(opts.limit));
171
+ const qs = params.toString();
172
+ const headers = { ...opts.headers ?? {} };
173
+ if (opts.cookieHeader) headers.cookie = opts.cookieHeader;
174
+ try {
175
+ return await apiFetch(config, `/api/suggestions${qs ? `?${qs}` : ""}`, {
176
+ credentials: "include",
177
+ cache: "no-store",
178
+ timeoutMs: 8e3,
179
+ ...opts,
180
+ headers
181
+ });
182
+ } catch {
183
+ return { onSifa: [], notOnSifa: [] };
184
+ }
185
+ }
186
+ async function fetchSuggestionCount(config, since, options = {}) {
187
+ const params = since ? `?since=${encodeURIComponent(since)}` : "";
188
+ try {
189
+ const data = await apiFetch(config, `/api/suggestions/count${params}`, {
190
+ credentials: "include",
191
+ cache: "no-store",
192
+ ...options
193
+ });
194
+ return data.count ?? 0;
195
+ } catch {
196
+ return 0;
197
+ }
198
+ }
199
+ async function fetchFeaturedProfile(config, options = {}) {
200
+ try {
201
+ return await apiFetch(config, "/api/featured-profile", {
202
+ next: { revalidate: 900 },
203
+ ...options
204
+ });
205
+ } catch {
206
+ return null;
207
+ }
208
+ }
209
+
210
+ // src/query/fetchers/follow.ts
211
+ async function fetchFollowing(config, opts = {}) {
212
+ const params = new URLSearchParams();
213
+ if (opts.source) params.set("source", opts.source);
214
+ if (opts.cursor) params.set("cursor", opts.cursor);
215
+ if (opts.limit) params.set("limit", String(opts.limit));
216
+ const qs = params.toString();
217
+ try {
218
+ return await apiFetch(config, `/api/following${qs ? `?${qs}` : ""}`, {
219
+ credentials: "include",
220
+ cache: "no-store",
221
+ ...opts
222
+ });
223
+ } catch {
224
+ return { follows: [] };
225
+ }
226
+ }
227
+
111
228
  // src/query/keys.ts
112
229
  var sifaQueryKeys = {
113
230
  all: () => ["sifa"],
@@ -118,10 +235,36 @@ var sifaQueryKeys = {
118
235
  position: {
119
236
  all: () => ["sifa", "position"],
120
237
  byOwner: (did) => ["sifa", "position", "by-owner", did]
238
+ },
239
+ search: {
240
+ all: () => ["sifa", "search"],
241
+ profiles: (filters) => ["sifa", "search", "profiles", filters],
242
+ skills: (query) => ["sifa", "search", "skills", query],
243
+ filters: () => ["sifa", "search", "filters"]
244
+ },
245
+ discovery: {
246
+ all: () => ["sifa", "discovery"],
247
+ similar: (did, limit) => ["sifa", "discovery", "similar", did, limit],
248
+ suggestions: (opts) => ["sifa", "discovery", "suggestions", opts],
249
+ suggestionCount: (since) => ["sifa", "discovery", "suggestion-count", since ?? null],
250
+ featured: () => ["sifa", "discovery", "featured"]
251
+ },
252
+ follow: {
253
+ all: () => ["sifa", "follow"],
254
+ following: (opts) => ["sifa", "follow", "following", opts]
121
255
  }
122
256
  };
123
257
 
124
- // src/query/hooks/use-create-position.ts
258
+ // src/query/hooks/use-profile.ts
259
+ function useProfile(handleOrDid, options) {
260
+ const config = useSifaConfig();
261
+ return reactQuery.useQuery({
262
+ queryKey: sifaQueryKeys.profile.byHandle(handleOrDid ?? ""),
263
+ queryFn: () => fetchProfile(config, handleOrDid ?? ""),
264
+ enabled: Boolean(handleOrDid) && (options?.enabled ?? true),
265
+ ...options
266
+ });
267
+ }
125
268
  function useCreatePosition(ownerDid, options) {
126
269
  const config = useSifaConfig();
127
270
  const queryClient = reactQuery.useQueryClient();
@@ -137,12 +280,70 @@ function useCreatePosition(ownerDid, options) {
137
280
  ...options
138
281
  });
139
282
  }
140
- function useProfile(handleOrDid, options) {
283
+ function useSearchProfiles(filters, options) {
141
284
  const config = useSifaConfig();
142
285
  return reactQuery.useQuery({
143
- queryKey: sifaQueryKeys.profile.byHandle(handleOrDid ?? ""),
144
- queryFn: () => fetchProfile(config, handleOrDid ?? ""),
145
- enabled: Boolean(handleOrDid) && (options?.enabled ?? true),
286
+ queryKey: sifaQueryKeys.search.profiles(filters),
287
+ queryFn: () => fetchSearchProfiles(config, filters),
288
+ ...options
289
+ });
290
+ }
291
+ function useSkillSuggestions(query, options) {
292
+ const config = useSifaConfig();
293
+ return reactQuery.useQuery({
294
+ queryKey: sifaQueryKeys.search.skills(query),
295
+ queryFn: () => fetchSkillSuggestions(config, query),
296
+ enabled: query.trim().length > 0 && (options?.enabled ?? true),
297
+ ...options
298
+ });
299
+ }
300
+ function useSearchFilters(options) {
301
+ const config = useSifaConfig();
302
+ return reactQuery.useQuery({
303
+ queryKey: sifaQueryKeys.search.filters(),
304
+ queryFn: () => fetchSearchFilters(config),
305
+ ...options
306
+ });
307
+ }
308
+ function useSimilarProfiles(did, opts = {}, options) {
309
+ const config = useSifaConfig();
310
+ const limit = opts.limit ?? 5;
311
+ return reactQuery.useQuery({
312
+ queryKey: sifaQueryKeys.discovery.similar(did ?? "", limit),
313
+ queryFn: () => fetchSimilarProfiles(config, did ?? "", { limit }),
314
+ enabled: Boolean(did) && (options?.enabled ?? true),
315
+ ...options
316
+ });
317
+ }
318
+ function useSuggestions(opts = {}, options) {
319
+ const config = useSifaConfig();
320
+ return reactQuery.useQuery({
321
+ queryKey: sifaQueryKeys.discovery.suggestions(opts),
322
+ queryFn: () => fetchSuggestions(config, opts),
323
+ ...options
324
+ });
325
+ }
326
+ function useSuggestionCount(since, options) {
327
+ const config = useSifaConfig();
328
+ return reactQuery.useQuery({
329
+ queryKey: sifaQueryKeys.discovery.suggestionCount(since),
330
+ queryFn: () => fetchSuggestionCount(config, since),
331
+ ...options
332
+ });
333
+ }
334
+ function useFeaturedProfile(options) {
335
+ const config = useSifaConfig();
336
+ return reactQuery.useQuery({
337
+ queryKey: sifaQueryKeys.discovery.featured(),
338
+ queryFn: () => fetchFeaturedProfile(config),
339
+ ...options
340
+ });
341
+ }
342
+ function useFollowing(opts = {}, options) {
343
+ const config = useSifaConfig();
344
+ return reactQuery.useQuery({
345
+ queryKey: sifaQueryKeys.follow.following(opts),
346
+ queryFn: () => fetchFollowing(config, opts),
146
347
  ...options
147
348
  });
148
349
  }
@@ -152,10 +353,26 @@ exports.SifaProvider = SifaProvider;
152
353
  exports.apiFetch = apiFetch;
153
354
  exports.apiFetchOrNull = apiFetchOrNull;
154
355
  exports.createPosition = createPosition;
356
+ exports.fetchFeaturedProfile = fetchFeaturedProfile;
357
+ exports.fetchFollowing = fetchFollowing;
155
358
  exports.fetchProfile = fetchProfile;
359
+ exports.fetchSearchFilters = fetchSearchFilters;
360
+ exports.fetchSearchProfiles = fetchSearchProfiles;
361
+ exports.fetchSimilarProfiles = fetchSimilarProfiles;
362
+ exports.fetchSkillSuggestions = fetchSkillSuggestions;
363
+ exports.fetchSuggestionCount = fetchSuggestionCount;
364
+ exports.fetchSuggestions = fetchSuggestions;
156
365
  exports.sifaQueryKeys = sifaQueryKeys;
157
366
  exports.useCreatePosition = useCreatePosition;
367
+ exports.useFeaturedProfile = useFeaturedProfile;
368
+ exports.useFollowing = useFollowing;
158
369
  exports.useProfile = useProfile;
370
+ exports.useSearchFilters = useSearchFilters;
371
+ exports.useSearchProfiles = useSearchProfiles;
159
372
  exports.useSifaConfig = useSifaConfig;
373
+ exports.useSimilarProfiles = useSimilarProfiles;
374
+ exports.useSkillSuggestions = useSkillSuggestions;
375
+ exports.useSuggestionCount = useSuggestionCount;
376
+ exports.useSuggestions = useSuggestions;
160
377
  //# sourceMappingURL=index.cjs.map
161
378
  //# sourceMappingURL=index.cjs.map
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/query/client.ts","../../src/query/config.tsx","../../src/query/fetchers/profile.ts","../../src/query/fetchers/positions.ts","../../src/query/keys.ts","../../src/query/hooks/use-create-position.ts","../../src/query/hooks/use-profile.ts"],"names":["createContext","useContext","useQueryClient","useMutation","useQuery"],"mappings":"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","file":"index.cjs","sourcesContent":["/**\n * Foundation HTTP client for talking to the Sifa AppView.\n *\n * Stateless. Consumers supply a {@link SifaApiConfig} per call (the React\n * hooks read it from context; non-React consumers pass it explicitly). No\n * singletons, no module-level state.\n */\n\n/** Configuration passed to every fetcher. */\nexport interface SifaApiConfig {\n /** Base URL of the sifa-api AppView, e.g. `https://api.sifa.id`. No trailing slash. */\n baseUrl: string;\n /**\n * Optional fetch implementation. Defaults to {@link globalThis.fetch}.\n * Lets Next.js consumers pass their cache-enhanced fetch; node/Expo\n * consumers can leave this unset.\n */\n fetch?: typeof fetch;\n}\n\n/** Options accepted by {@link apiFetch}. */\nexport interface ApiFetchOptions {\n method?: 'GET' | 'POST' | 'PUT' | 'DELETE' | 'PATCH';\n /** Request body. Serialized to JSON automatically. */\n body?: unknown;\n /** AbortSignal. Defaults to `AbortSignal.timeout(timeoutMs)` if `timeoutMs` is set. */\n signal?: AbortSignal;\n /** Per-call timeout in milliseconds. Default: 10_000. Ignored if `signal` is provided. */\n timeoutMs?: number;\n /** Retry on HTTP 429 up to 3 times with the server's `Retry-After` delay (capped at 3s). */\n retryOn429?: boolean;\n /** Additional headers. `Content-Type: application/json` is set automatically when `body` is present. */\n headers?: Record<string, string>;\n credentials?: RequestCredentials;\n cache?: RequestCache;\n /**\n * Next.js-specific cache hints. Ignored on non-Next runtimes. Passed\n * through transparently as part of {@link RequestInit}.\n */\n next?: { revalidate?: number | false; tags?: string[] };\n}\n\n/** Error thrown by {@link apiFetch} on non-2xx responses. */\nexport class ApiError extends Error {\n readonly status: number;\n readonly body: unknown;\n\n constructor(message: string, status: number, body?: unknown) {\n super(message);\n this.name = 'ApiError';\n this.status = status;\n this.body = body;\n }\n}\n\nconst DEFAULT_TIMEOUT_MS = 10_000;\nconst MAX_RATE_LIMIT_RETRIES = 3;\nconst RATE_LIMIT_RETRY_CAP_SECONDS = 3;\n\n/**\n * Generic fetcher used by all SDK query and mutation functions.\n *\n * Returns parsed JSON typed as `T`. Throws {@link ApiError} on non-2xx\n * responses. Use {@link apiFetchOrNull} when 404 should resolve to `null`\n * instead.\n */\nexport async function apiFetch<T = unknown>(\n config: SifaApiConfig,\n path: string,\n options: ApiFetchOptions = {},\n): Promise<T> {\n const fetchFn = config.fetch ?? globalThis.fetch;\n const url = `${config.baseUrl}${path}`;\n const maxRetries = options.retryOn429 ? MAX_RATE_LIMIT_RETRIES : 0;\n\n for (let attempt = 0; attempt <= maxRetries; attempt++) {\n const signal = options.signal ?? AbortSignal.timeout(options.timeoutMs ?? DEFAULT_TIMEOUT_MS);\n\n const headers: Record<string, string> = { ...(options.headers ?? {}) };\n let body: BodyInit | undefined;\n if (options.body !== undefined) {\n headers['Content-Type'] ??= 'application/json';\n body = JSON.stringify(options.body);\n }\n\n // `next` is a Next.js extension to RequestInit; cast through.\n const init = {\n method: options.method ?? 'GET',\n headers,\n body,\n signal,\n credentials: options.credentials,\n cache: options.cache,\n ...(options.next ? { next: options.next } : {}),\n } as RequestInit;\n\n const res = await fetchFn(url, init);\n\n if (res.status === 429 && attempt < maxRetries) {\n const retryAfterRaw = res.headers.get('retry-after');\n const retryAfter = retryAfterRaw ? Number.parseInt(retryAfterRaw, 10) : 2;\n const waitSeconds = Math.min(\n Number.isFinite(retryAfter) ? retryAfter : 2,\n RATE_LIMIT_RETRY_CAP_SECONDS,\n );\n await new Promise((r) => setTimeout(r, waitSeconds * 1000));\n continue;\n }\n\n if (!res.ok) {\n let errBody: unknown;\n try {\n errBody = (await res.json()) as unknown;\n } catch {\n try {\n errBody = await res.text();\n } catch {\n errBody = undefined;\n }\n }\n throw new ApiError(`Sifa API ${res.status} on ${path}`, res.status, errBody);\n }\n\n return (await res.json()) as T;\n }\n\n throw new ApiError(`Sifa API exhausted retries on ${path}`, 429);\n}\n\n/**\n * Variant of {@link apiFetch} that resolves to `null` on HTTP 404 instead\n * of throwing. Useful for \"fetch by handle\" reads where missing is\n * expected (e.g. unknown profile).\n */\nexport async function apiFetchOrNull<T>(\n config: SifaApiConfig,\n path: string,\n options: ApiFetchOptions = {},\n): Promise<T | null> {\n try {\n return await apiFetch<T>(config, path, options);\n } catch (e) {\n if (e instanceof ApiError && e.status === 404) return null;\n throw e;\n }\n}\n","'use client';\n\nimport { createContext, useContext, type ReactNode } from 'react';\n\nimport type { SifaApiConfig } from './client.js';\n\nconst SifaConfigContext = createContext<SifaApiConfig | null>(null);\n\nexport interface SifaProviderProps {\n config: SifaApiConfig;\n children: ReactNode;\n}\n\n/**\n * Provides the {@link SifaApiConfig} that hooks under it can read via\n * {@link useSifaConfig}. Wrap the React tree once; hooks consume it.\n *\n * @example\n * ```tsx\n * <QueryClientProvider client={queryClient}>\n * <SifaProvider config={{ baseUrl: process.env.NEXT_PUBLIC_API_URL! }}>\n * <App />\n * </SifaProvider>\n * </QueryClientProvider>\n * ```\n */\nexport function SifaProvider({ config, children }: SifaProviderProps) {\n return <SifaConfigContext.Provider value={config}>{children}</SifaConfigContext.Provider>;\n}\n\n/**\n * Read the SDK's {@link SifaApiConfig} from context. Throws if no\n * {@link SifaProvider} is mounted above.\n */\nexport function useSifaConfig(): SifaApiConfig {\n const ctx = useContext(SifaConfigContext);\n if (!ctx) {\n throw new Error(\n 'useSifaConfig must be used inside <SifaProvider>. Wrap your app once with <SifaProvider config={...}>.',\n );\n }\n return ctx;\n}\n","import type { Profile } from '../../types/index.js';\nimport { apiFetchOrNull, type ApiFetchOptions, type SifaApiConfig } from '../client.js';\n\n/**\n * Read the aggregated profile for a handle or DID.\n *\n * Returns `null` when the AppView has no profile for the given identifier\n * (HTTP 404). Throws {@link ApiError} on other non-2xx responses.\n *\n * Server-callable (Next.js RSC) and client-callable (Expo, browser).\n */\nexport function fetchProfile(\n config: SifaApiConfig,\n handleOrDid: string,\n options: ApiFetchOptions = {},\n): Promise<Profile | null> {\n const path = `/api/profile/${encodeURIComponent(handleOrDid)}`;\n return apiFetchOrNull<Profile>(config, path, {\n retryOn429: true,\n ...options,\n });\n}\n","import { apiFetch, type ApiFetchOptions, type SifaApiConfig } from '../client.js';\n\n/** Result returned by record-write mutations (create / update / delete). */\nexport interface WriteResult {\n success: boolean;\n error?: string;\n pdsHost?: string;\n}\n\n/** Result returned by create mutations. Includes the newly created `rkey`. */\nexport interface CreateResult extends WriteResult {\n rkey?: string;\n}\n\n/**\n * Create a new `id.sifa.profile.position` record on the authenticated\n * user's PDS. The AppView signs and writes via the user's OAuth session.\n *\n * `data` should be a lexicon-shaped position record (without `createdAt`\n * or `rkey`; the AppView fills both). Validate with\n * `ProfilePositionRecordSchema.omit({ createdAt: true })` before calling\n * if you want client-side guarantees.\n */\nexport function createPosition(\n config: SifaApiConfig,\n data: Record<string, unknown>,\n options: ApiFetchOptions = {},\n): Promise<CreateResult> {\n return apiFetch<CreateResult>(config, '/api/positions', {\n method: 'POST',\n body: data,\n credentials: 'include',\n ...options,\n });\n}\n","/**\n * Query key factory for TanStack Query.\n *\n * Keys are read-only tuples; the hierarchy matches the SDK's fetcher\n * grouping. Use these instead of inline arrays so consumers can target\n * `queryClient.invalidateQueries({ queryKey: keys.profile.all() })` and\n * similar patterns without typos.\n *\n * Convention: every leaf key starts with the namespace ('sifa') so\n * consumers can invalidate everything Sifa-related in one call.\n */\nexport const sifaQueryKeys = {\n all: () => ['sifa'] as const,\n\n profile: {\n all: () => ['sifa', 'profile'] as const,\n byHandle: (handleOrDid: string) => ['sifa', 'profile', handleOrDid] as const,\n },\n\n position: {\n all: () => ['sifa', 'position'] as const,\n byOwner: (did: string) => ['sifa', 'position', 'by-owner', did] as const,\n },\n} as const;\n\nexport type SifaQueryKey =\n | ReturnType<typeof sifaQueryKeys.all>\n | ReturnType<typeof sifaQueryKeys.profile.all>\n | ReturnType<typeof sifaQueryKeys.profile.byHandle>\n | ReturnType<typeof sifaQueryKeys.position.all>\n | ReturnType<typeof sifaQueryKeys.position.byOwner>;\n","'use client';\n\nimport { useMutation, useQueryClient, type UseMutationOptions } from '@tanstack/react-query';\n\nimport { useSifaConfig } from '../config.js';\nimport { createPosition, type CreateResult } from '../fetchers/positions.js';\nimport { sifaQueryKeys } from '../keys.js';\n\n/**\n * React hook for creating a new position record. On success, invalidates\n * the owner's profile cache so the new position is reflected on the next\n * read.\n *\n * The owner DID is required so the mutation can target the correct\n * profile cache entry for invalidation.\n */\nexport function useCreatePosition(\n ownerDid: string,\n options?: Omit<UseMutationOptions<CreateResult, Error, Record<string, unknown>>, 'mutationFn'>,\n) {\n const config = useSifaConfig();\n const queryClient = useQueryClient();\n\n return useMutation({\n mutationFn: (data: Record<string, unknown>) => createPosition(config, data),\n onSuccess: async (result, variables, onMutateResult, context) => {\n if (result.success) {\n await queryClient.invalidateQueries({ queryKey: sifaQueryKeys.profile.byHandle(ownerDid) });\n await queryClient.invalidateQueries({ queryKey: sifaQueryKeys.position.byOwner(ownerDid) });\n }\n await options?.onSuccess?.(result, variables, onMutateResult, context);\n },\n ...options,\n });\n}\n","'use client';\n\nimport { useQuery, type UseQueryOptions } from '@tanstack/react-query';\n\nimport type { Profile } from '../../types/index.js';\nimport { useSifaConfig } from '../config.js';\nimport { fetchProfile } from '../fetchers/profile.js';\nimport { sifaQueryKeys } from '../keys.js';\n\n/**\n * React hook that reads an aggregated profile by handle or DID via\n * TanStack Query. Returns `null` data when the profile does not exist.\n *\n * Pass `{ enabled: false }` (or an empty `handleOrDid`) to defer the\n * fetch.\n */\nexport function useProfile(\n handleOrDid: string | undefined | null,\n options?: Omit<\n UseQueryOptions<\n Profile | null,\n Error,\n Profile | null,\n ReturnType<typeof sifaQueryKeys.profile.byHandle>\n >,\n 'queryKey' | 'queryFn'\n >,\n) {\n const config = useSifaConfig();\n return useQuery({\n queryKey: sifaQueryKeys.profile.byHandle(handleOrDid ?? ''),\n queryFn: () => fetchProfile(config, handleOrDid ?? ''),\n enabled: Boolean(handleOrDid) && (options?.enabled ?? true),\n ...options,\n });\n}\n"]}
1
+ {"version":3,"sources":["../../src/query/client.ts","../../src/query/config.tsx","../../src/query/fetchers/profile.ts","../../src/query/fetchers/positions.ts","../../src/query/fetchers/search.ts","../../src/query/fetchers/discovery.ts","../../src/query/fetchers/follow.ts","../../src/query/keys.ts","../../src/query/hooks/use-profile.ts","../../src/query/hooks/use-create-position.ts","../../src/query/hooks/use-search.ts","../../src/query/hooks/use-discovery.ts","../../src/query/hooks/use-follow.ts"],"names":["createContext","useContext","useQuery","useQueryClient","useMutation"],"mappings":"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","file":"index.cjs","sourcesContent":["/**\n * Foundation HTTP client for talking to the Sifa AppView.\n *\n * Stateless. Consumers supply a {@link SifaApiConfig} per call (the React\n * hooks read it from context; non-React consumers pass it explicitly). No\n * singletons, no module-level state.\n */\n\n/** Configuration passed to every fetcher. */\nexport interface SifaApiConfig {\n /** Base URL of the sifa-api AppView, e.g. `https://api.sifa.id`. No trailing slash. */\n baseUrl: string;\n /**\n * Optional fetch implementation. Defaults to {@link globalThis.fetch}.\n * Lets Next.js consumers pass their cache-enhanced fetch; node/Expo\n * consumers can leave this unset.\n */\n fetch?: typeof fetch;\n}\n\n/** Options accepted by {@link apiFetch}. */\nexport interface ApiFetchOptions {\n method?: 'GET' | 'POST' | 'PUT' | 'DELETE' | 'PATCH';\n /** Request body. Serialized to JSON automatically. */\n body?: unknown;\n /** AbortSignal. Defaults to `AbortSignal.timeout(timeoutMs)` if `timeoutMs` is set. */\n signal?: AbortSignal;\n /** Per-call timeout in milliseconds. Default: 10_000. Ignored if `signal` is provided. */\n timeoutMs?: number;\n /** Retry on HTTP 429 up to 3 times with the server's `Retry-After` delay (capped at 3s). */\n retryOn429?: boolean;\n /** Additional headers. `Content-Type: application/json` is set automatically when `body` is present. */\n headers?: Record<string, string>;\n credentials?: RequestCredentials;\n cache?: RequestCache;\n /**\n * Next.js-specific cache hints. Ignored on non-Next runtimes. Passed\n * through transparently as part of {@link RequestInit}.\n */\n next?: { revalidate?: number | false; tags?: string[] };\n}\n\n/** Error thrown by {@link apiFetch} on non-2xx responses. */\nexport class ApiError extends Error {\n readonly status: number;\n readonly body: unknown;\n\n constructor(message: string, status: number, body?: unknown) {\n super(message);\n this.name = 'ApiError';\n this.status = status;\n this.body = body;\n }\n}\n\nconst DEFAULT_TIMEOUT_MS = 10_000;\nconst MAX_RATE_LIMIT_RETRIES = 3;\nconst RATE_LIMIT_RETRY_CAP_SECONDS = 3;\n\n/**\n * Generic fetcher used by all SDK query and mutation functions.\n *\n * Returns parsed JSON typed as `T`. Throws {@link ApiError} on non-2xx\n * responses. Use {@link apiFetchOrNull} when 404 should resolve to `null`\n * instead.\n */\nexport async function apiFetch<T = unknown>(\n config: SifaApiConfig,\n path: string,\n options: ApiFetchOptions = {},\n): Promise<T> {\n const fetchFn = config.fetch ?? globalThis.fetch;\n const url = `${config.baseUrl}${path}`;\n const maxRetries = options.retryOn429 ? MAX_RATE_LIMIT_RETRIES : 0;\n\n for (let attempt = 0; attempt <= maxRetries; attempt++) {\n const signal = options.signal ?? AbortSignal.timeout(options.timeoutMs ?? DEFAULT_TIMEOUT_MS);\n\n const headers: Record<string, string> = { ...(options.headers ?? {}) };\n let body: BodyInit | undefined;\n if (options.body !== undefined) {\n headers['Content-Type'] ??= 'application/json';\n body = JSON.stringify(options.body);\n }\n\n // `next` is a Next.js extension to RequestInit; cast through.\n const init = {\n method: options.method ?? 'GET',\n headers,\n body,\n signal,\n credentials: options.credentials,\n cache: options.cache,\n ...(options.next ? { next: options.next } : {}),\n } as RequestInit;\n\n const res = await fetchFn(url, init);\n\n if (res.status === 429 && attempt < maxRetries) {\n const retryAfterRaw = res.headers.get('retry-after');\n const retryAfter = retryAfterRaw ? Number.parseInt(retryAfterRaw, 10) : 2;\n const waitSeconds = Math.min(\n Number.isFinite(retryAfter) ? retryAfter : 2,\n RATE_LIMIT_RETRY_CAP_SECONDS,\n );\n await new Promise((r) => setTimeout(r, waitSeconds * 1000));\n continue;\n }\n\n if (!res.ok) {\n let errBody: unknown;\n try {\n errBody = (await res.json()) as unknown;\n } catch {\n try {\n errBody = await res.text();\n } catch {\n errBody = undefined;\n }\n }\n throw new ApiError(`Sifa API ${res.status} on ${path}`, res.status, errBody);\n }\n\n return (await res.json()) as T;\n }\n\n throw new ApiError(`Sifa API exhausted retries on ${path}`, 429);\n}\n\n/**\n * Variant of {@link apiFetch} that resolves to `null` on HTTP 404 instead\n * of throwing. Useful for \"fetch by handle\" reads where missing is\n * expected (e.g. unknown profile).\n */\nexport async function apiFetchOrNull<T>(\n config: SifaApiConfig,\n path: string,\n options: ApiFetchOptions = {},\n): Promise<T | null> {\n try {\n return await apiFetch<T>(config, path, options);\n } catch (e) {\n if (e instanceof ApiError && e.status === 404) return null;\n throw e;\n }\n}\n","'use client';\n\nimport { createContext, useContext, type ReactNode } from 'react';\n\nimport type { SifaApiConfig } from './client.js';\n\nconst SifaConfigContext = createContext<SifaApiConfig | null>(null);\n\nexport interface SifaProviderProps {\n config: SifaApiConfig;\n children: ReactNode;\n}\n\n/**\n * Provides the {@link SifaApiConfig} that hooks under it can read via\n * {@link useSifaConfig}. Wrap the React tree once; hooks consume it.\n *\n * @example\n * ```tsx\n * <QueryClientProvider client={queryClient}>\n * <SifaProvider config={{ baseUrl: process.env.NEXT_PUBLIC_API_URL! }}>\n * <App />\n * </SifaProvider>\n * </QueryClientProvider>\n * ```\n */\nexport function SifaProvider({ config, children }: SifaProviderProps) {\n return <SifaConfigContext.Provider value={config}>{children}</SifaConfigContext.Provider>;\n}\n\n/**\n * Read the SDK's {@link SifaApiConfig} from context. Throws if no\n * {@link SifaProvider} is mounted above.\n */\nexport function useSifaConfig(): SifaApiConfig {\n const ctx = useContext(SifaConfigContext);\n if (!ctx) {\n throw new Error(\n 'useSifaConfig must be used inside <SifaProvider>. Wrap your app once with <SifaProvider config={...}>.',\n );\n }\n return ctx;\n}\n","import type { Profile } from '../../types/index.js';\nimport { apiFetchOrNull, type ApiFetchOptions, type SifaApiConfig } from '../client.js';\n\n/**\n * Read the aggregated profile for a handle or DID.\n *\n * Returns `null` when the AppView has no profile for the given identifier\n * (HTTP 404). Throws {@link ApiError} on other non-2xx responses.\n *\n * Server-callable (Next.js RSC) and client-callable (Expo, browser).\n */\nexport function fetchProfile(\n config: SifaApiConfig,\n handleOrDid: string,\n options: ApiFetchOptions = {},\n): Promise<Profile | null> {\n const path = `/api/profile/${encodeURIComponent(handleOrDid)}`;\n return apiFetchOrNull<Profile>(config, path, {\n retryOn429: true,\n ...options,\n });\n}\n","import { apiFetch, type ApiFetchOptions, type SifaApiConfig } from '../client.js';\n\n/** Result returned by record-write mutations (create / update / delete). */\nexport interface WriteResult {\n success: boolean;\n error?: string;\n pdsHost?: string;\n}\n\n/** Result returned by create mutations. Includes the newly created `rkey`. */\nexport interface CreateResult extends WriteResult {\n rkey?: string;\n}\n\n/**\n * Create a new `id.sifa.profile.position` record on the authenticated\n * user's PDS. The AppView signs and writes via the user's OAuth session.\n *\n * `data` should be a lexicon-shaped position record (without `createdAt`\n * or `rkey`; the AppView fills both). Validate with\n * `ProfilePositionRecordSchema.omit({ createdAt: true })` before calling\n * if you want client-side guarantees.\n */\nexport function createPosition(\n config: SifaApiConfig,\n data: Record<string, unknown>,\n options: ApiFetchOptions = {},\n): Promise<CreateResult> {\n return apiFetch<CreateResult>(config, '/api/positions', {\n method: 'POST',\n body: data,\n credentials: 'include',\n ...options,\n });\n}\n","import { apiFetch, type ApiFetchOptions, type SifaApiConfig } from '../client.js';\n\n/** Profile entry returned by the search endpoint. */\nexport interface ProfileSearchResult {\n did?: string;\n handle: string;\n displayName?: string;\n headline?: string;\n avatar?: string;\n about?: string;\n currentRole?: string;\n currentCompany?: string;\n industry?: string;\n domain?: string;\n countryCode?: string;\n locationCountry?: string;\n preferredWorkplace?: string[];\n claimed?: boolean;\n blueskyVerified?: boolean;\n blueskyVerifiedAt?: string | null;\n}\n\nexport interface SearchFilters {\n q?: string;\n skill?: string;\n country?: string;\n industry?: string;\n domain?: string;\n workplace?: string;\n app?: string;\n limit?: number;\n}\n\nexport interface SearchResponse {\n profiles: ProfileSearchResult[];\n total: number;\n limit: number;\n offset: number;\n}\n\n/** Skill typeahead suggestion. */\nexport interface SkillSearchResult {\n name: string;\n slug: string;\n category: string;\n userCount: number;\n}\n\nexport interface FilterOptions {\n countries: { countryCode: string; country: string; count: number }[];\n industries: { industry: string; count: number }[];\n apps: { appId: string; count: number }[];\n}\n\nconst EMPTY_SEARCH: SearchResponse = { profiles: [], total: 0, limit: 20, offset: 0 };\nconst EMPTY_FILTERS: FilterOptions = { countries: [], industries: [], apps: [] };\n\n/**\n * Search profiles by free-text query and optional filters. Returns an\n * empty result set when no filters are provided (matching sifa-web's\n * \"no input, no fetch\" behavior).\n */\nexport async function fetchSearchProfiles(\n config: SifaApiConfig,\n filters: SearchFilters,\n options: ApiFetchOptions = {},\n): Promise<SearchResponse> {\n const params = new URLSearchParams();\n if (filters.q) params.set('q', filters.q);\n if (filters.skill) params.set('skill', filters.skill);\n if (filters.country) params.set('country', filters.country);\n if (filters.industry) params.set('industry', filters.industry);\n if (filters.domain) params.set('domain', filters.domain);\n if (filters.workplace) params.set('workplace', filters.workplace);\n if (filters.app) params.set('app', filters.app);\n if (filters.limit !== undefined) params.set('limit', String(filters.limit));\n\n if (params.size === 0) return EMPTY_SEARCH;\n\n return apiFetch<SearchResponse>(config, `/api/search/profiles?${params.toString()}`, {\n cache: 'no-store',\n ...options,\n });\n}\n\n/**\n * Skill typeahead. Returns up to 8 matches for the given prefix. Empty\n * input returns an empty array without hitting the server.\n */\nexport async function fetchSkillSuggestions(\n config: SifaApiConfig,\n query: string,\n options: ApiFetchOptions = {},\n): Promise<SkillSearchResult[]> {\n if (!query.trim()) return [];\n const path = `/api/search/skills?q=${encodeURIComponent(query)}&limit=8`;\n const data = await apiFetch<{ skills?: SkillSearchResult[] }>(config, path, {\n cache: 'no-store',\n ...options,\n });\n return data.skills ?? [];\n}\n\n/** Available filter facets (countries, industries, apps) for search UI. */\nexport async function fetchSearchFilters(\n config: SifaApiConfig,\n options: ApiFetchOptions = {},\n): Promise<FilterOptions> {\n try {\n return await apiFetch<FilterOptions>(config, '/api/search/filters', {\n next: { revalidate: 300 },\n ...options,\n });\n } catch {\n return EMPTY_FILTERS;\n }\n}\n","import { apiFetch, type ApiFetchOptions, type SifaApiConfig } from '../client.js';\n\n/** Lightweight profile representation used by discovery endpoints. */\nexport interface SimilarProfile {\n did: string;\n handle: string;\n displayName?: string | null;\n avatar?: string | null;\n headline?: string | null;\n currentRole?: string | null;\n currentCompany?: string | null;\n industry?: string | null;\n domain?: string | null;\n}\n\nexport interface SuggestionProfile {\n did: string;\n handle: string;\n displayName?: string;\n headline?: string;\n avatarUrl?: string;\n source: string;\n dismissed: boolean;\n blueskyVerified?: boolean;\n}\n\nexport interface SuggestionsResponse {\n onSifa: SuggestionProfile[];\n notOnSifa: SuggestionProfile[];\n cursor?: string;\n}\n\nexport interface FeaturedProfile {\n did: string;\n handle: string;\n displayName?: string;\n avatar?: string;\n pronouns?: string;\n headline?: string;\n about?: string;\n currentRole?: string;\n currentCompany?: string;\n locationCountry?: string;\n locationRegion?: string;\n /** Legacy alias for `locationLocality`; emitted by sifa-api during the additive response window. */\n locationCity?: string;\n /** community.lexicon.location.address field name -- prefer over `locationCity`. */\n locationLocality?: string;\n countryCode?: string;\n location?: string;\n website?: string;\n openTo?: string[];\n preferredWorkplace?: string[];\n availableFromUtc?: number;\n availableToUtc?: number;\n followersCount?: number;\n atprotoFollowersCount?: number;\n pdsProvider?: { name: string; host: string } | null;\n claimed: boolean;\n featuredDate: string;\n}\n\n/** Profiles similar to the given DID (matchmaking). Returns `[]` on error. */\nexport async function fetchSimilarProfiles(\n config: SifaApiConfig,\n did: string,\n opts: { limit?: number } & ApiFetchOptions = {},\n): Promise<SimilarProfile[]> {\n const limit = opts.limit ?? 5;\n const path = `/api/discover/similar/${encodeURIComponent(did)}?limit=${limit}`;\n try {\n const data = await apiFetch<{ profiles?: SimilarProfile[] }>(config, path, {\n next: { revalidate: 300 },\n timeoutMs: 5000,\n ...opts,\n });\n return data.profiles ?? [];\n } catch {\n return [];\n }\n}\n\nexport interface FetchSuggestionsOptions extends ApiFetchOptions {\n source?: string;\n includeDismissed?: boolean;\n cursor?: string;\n limit?: number;\n /**\n * Pass the caller's `Cookie` header on Next.js RSC server-side calls.\n * `credentials: 'include'` does NOT propagate browser cookies in RSC,\n * so authenticated server fetches must forward the header explicitly.\n */\n cookieHeader?: string;\n}\n\n/** Discovery suggestions feed. Resolves to empty arrays on error. */\nexport async function fetchSuggestions(\n config: SifaApiConfig,\n opts: FetchSuggestionsOptions = {},\n): Promise<SuggestionsResponse> {\n const params = new URLSearchParams();\n if (opts.source) params.set('source', opts.source);\n if (opts.includeDismissed) params.set('include_dismissed', 'true');\n if (opts.cursor) params.set('cursor', opts.cursor);\n if (opts.limit) params.set('limit', String(opts.limit));\n\n const qs = params.toString();\n const headers: Record<string, string> = { ...(opts.headers ?? {}) };\n if (opts.cookieHeader) headers.cookie = opts.cookieHeader;\n\n try {\n return await apiFetch<SuggestionsResponse>(config, `/api/suggestions${qs ? `?${qs}` : ''}`, {\n credentials: 'include',\n cache: 'no-store',\n timeoutMs: 8000,\n ...opts,\n headers,\n });\n } catch {\n return { onSifa: [], notOnSifa: [] };\n }\n}\n\n/** Count of pending suggestions since an optional timestamp. */\nexport async function fetchSuggestionCount(\n config: SifaApiConfig,\n since?: string,\n options: ApiFetchOptions = {},\n): Promise<number> {\n const params = since ? `?since=${encodeURIComponent(since)}` : '';\n try {\n const data = await apiFetch<{ count?: number }>(config, `/api/suggestions/count${params}`, {\n credentials: 'include',\n cache: 'no-store',\n ...options,\n });\n return data.count ?? 0;\n } catch {\n return 0;\n }\n}\n\n/** Featured profile (rotated by sifa-api). Returns `null` when none. */\nexport async function fetchFeaturedProfile(\n config: SifaApiConfig,\n options: ApiFetchOptions = {},\n): Promise<FeaturedProfile | null> {\n try {\n return await apiFetch<FeaturedProfile>(config, '/api/featured-profile', {\n next: { revalidate: 900 },\n ...options,\n });\n } catch {\n return null;\n }\n}\n","import { apiFetch, type ApiFetchOptions, type SifaApiConfig } from '../client.js';\n\nexport interface FollowProfile {\n did: string;\n handle: string;\n displayName?: string;\n headline?: string;\n avatarUrl?: string;\n source: string;\n claimed: boolean;\n followedAt: string;\n blueskyVerified?: boolean;\n blueskyVerifiedAt?: string | null;\n}\n\nexport interface FollowingResponse {\n follows: FollowProfile[];\n cursor?: string;\n}\n\n/** People the authenticated user follows. Empty on error. */\nexport async function fetchFollowing(\n config: SifaApiConfig,\n opts: { source?: string; cursor?: string; limit?: number } & ApiFetchOptions = {},\n): Promise<FollowingResponse> {\n const params = new URLSearchParams();\n if (opts.source) params.set('source', opts.source);\n if (opts.cursor) params.set('cursor', opts.cursor);\n if (opts.limit) params.set('limit', String(opts.limit));\n const qs = params.toString();\n\n try {\n return await apiFetch<FollowingResponse>(config, `/api/following${qs ? `?${qs}` : ''}`, {\n credentials: 'include',\n cache: 'no-store',\n ...opts,\n });\n } catch {\n return { follows: [] };\n }\n}\n","/**\n * Query key factory for TanStack Query.\n *\n * Keys are read-only tuples; the hierarchy matches the SDK's fetcher\n * grouping. Use these instead of inline arrays so consumers can target\n * `queryClient.invalidateQueries({ queryKey: keys.profile.all() })` and\n * similar patterns without typos.\n *\n * Convention: every leaf key starts with the namespace ('sifa') so\n * consumers can invalidate everything Sifa-related in one call.\n */\nexport const sifaQueryKeys = {\n all: () => ['sifa'] as const,\n\n profile: {\n all: () => ['sifa', 'profile'] as const,\n byHandle: (handleOrDid: string) => ['sifa', 'profile', handleOrDid] as const,\n },\n\n position: {\n all: () => ['sifa', 'position'] as const,\n byOwner: (did: string) => ['sifa', 'position', 'by-owner', did] as const,\n },\n\n search: {\n all: () => ['sifa', 'search'] as const,\n profiles: (filters: Record<string, unknown>) =>\n ['sifa', 'search', 'profiles', filters] as const,\n skills: (query: string) => ['sifa', 'search', 'skills', query] as const,\n filters: () => ['sifa', 'search', 'filters'] as const,\n },\n\n discovery: {\n all: () => ['sifa', 'discovery'] as const,\n similar: (did: string, limit: number) => ['sifa', 'discovery', 'similar', did, limit] as const,\n suggestions: (opts: Record<string, unknown>) =>\n ['sifa', 'discovery', 'suggestions', opts] as const,\n suggestionCount: (since: string | undefined) =>\n ['sifa', 'discovery', 'suggestion-count', since ?? null] as const,\n featured: () => ['sifa', 'discovery', 'featured'] as const,\n },\n\n follow: {\n all: () => ['sifa', 'follow'] as const,\n following: (opts: Record<string, unknown>) => ['sifa', 'follow', 'following', opts] as const,\n },\n} as const;\n\nexport type SifaQueryKey =\n | ReturnType<typeof sifaQueryKeys.all>\n | ReturnType<typeof sifaQueryKeys.profile.all>\n | ReturnType<typeof sifaQueryKeys.profile.byHandle>\n | ReturnType<typeof sifaQueryKeys.position.all>\n | ReturnType<typeof sifaQueryKeys.position.byOwner>\n | ReturnType<typeof sifaQueryKeys.search.all>\n | ReturnType<typeof sifaQueryKeys.search.profiles>\n | ReturnType<typeof sifaQueryKeys.search.skills>\n | ReturnType<typeof sifaQueryKeys.search.filters>\n | ReturnType<typeof sifaQueryKeys.discovery.all>\n | ReturnType<typeof sifaQueryKeys.discovery.similar>\n | ReturnType<typeof sifaQueryKeys.discovery.suggestions>\n | ReturnType<typeof sifaQueryKeys.discovery.suggestionCount>\n | ReturnType<typeof sifaQueryKeys.discovery.featured>\n | ReturnType<typeof sifaQueryKeys.follow.all>\n | ReturnType<typeof sifaQueryKeys.follow.following>;\n","'use client';\n\nimport { useQuery, type UseQueryOptions } from '@tanstack/react-query';\n\nimport type { Profile } from '../../types/index.js';\nimport { useSifaConfig } from '../config.js';\nimport { fetchProfile } from '../fetchers/profile.js';\nimport { sifaQueryKeys } from '../keys.js';\n\n/**\n * React hook that reads an aggregated profile by handle or DID via\n * TanStack Query. Returns `null` data when the profile does not exist.\n *\n * Pass `{ enabled: false }` (or an empty `handleOrDid`) to defer the\n * fetch.\n */\nexport function useProfile(\n handleOrDid: string | undefined | null,\n options?: Omit<\n UseQueryOptions<\n Profile | null,\n Error,\n Profile | null,\n ReturnType<typeof sifaQueryKeys.profile.byHandle>\n >,\n 'queryKey' | 'queryFn'\n >,\n) {\n const config = useSifaConfig();\n return useQuery({\n queryKey: sifaQueryKeys.profile.byHandle(handleOrDid ?? ''),\n queryFn: () => fetchProfile(config, handleOrDid ?? ''),\n enabled: Boolean(handleOrDid) && (options?.enabled ?? true),\n ...options,\n });\n}\n","'use client';\n\nimport { useMutation, useQueryClient, type UseMutationOptions } from '@tanstack/react-query';\n\nimport { useSifaConfig } from '../config.js';\nimport { createPosition, type CreateResult } from '../fetchers/positions.js';\nimport { sifaQueryKeys } from '../keys.js';\n\n/**\n * React hook for creating a new position record. On success, invalidates\n * the owner's profile cache so the new position is reflected on the next\n * read.\n *\n * The owner DID is required so the mutation can target the correct\n * profile cache entry for invalidation.\n */\nexport function useCreatePosition(\n ownerDid: string,\n options?: Omit<UseMutationOptions<CreateResult, Error, Record<string, unknown>>, 'mutationFn'>,\n) {\n const config = useSifaConfig();\n const queryClient = useQueryClient();\n\n return useMutation({\n mutationFn: (data: Record<string, unknown>) => createPosition(config, data),\n onSuccess: async (result, variables, onMutateResult, context) => {\n if (result.success) {\n await queryClient.invalidateQueries({ queryKey: sifaQueryKeys.profile.byHandle(ownerDid) });\n await queryClient.invalidateQueries({ queryKey: sifaQueryKeys.position.byOwner(ownerDid) });\n }\n await options?.onSuccess?.(result, variables, onMutateResult, context);\n },\n ...options,\n });\n}\n","'use client';\n\nimport { useQuery, type UseQueryOptions } from '@tanstack/react-query';\n\nimport { useSifaConfig } from '../config.js';\nimport {\n fetchSearchFilters,\n fetchSearchProfiles,\n fetchSkillSuggestions,\n type FilterOptions,\n type SearchFilters,\n type SearchResponse,\n type SkillSearchResult,\n} from '../fetchers/search.js';\nimport { sifaQueryKeys } from '../keys.js';\n\nexport function useSearchProfiles(\n filters: SearchFilters,\n options?: Omit<\n UseQueryOptions<\n SearchResponse,\n Error,\n SearchResponse,\n ReturnType<typeof sifaQueryKeys.search.profiles>\n >,\n 'queryKey' | 'queryFn'\n >,\n) {\n const config = useSifaConfig();\n return useQuery({\n queryKey: sifaQueryKeys.search.profiles(filters as Record<string, unknown>),\n queryFn: () => fetchSearchProfiles(config, filters),\n ...options,\n });\n}\n\nexport function useSkillSuggestions(\n query: string,\n options?: Omit<\n UseQueryOptions<\n SkillSearchResult[],\n Error,\n SkillSearchResult[],\n ReturnType<typeof sifaQueryKeys.search.skills>\n >,\n 'queryKey' | 'queryFn'\n >,\n) {\n const config = useSifaConfig();\n return useQuery({\n queryKey: sifaQueryKeys.search.skills(query),\n queryFn: () => fetchSkillSuggestions(config, query),\n enabled: query.trim().length > 0 && (options?.enabled ?? true),\n ...options,\n });\n}\n\nexport function useSearchFilters(\n options?: Omit<\n UseQueryOptions<\n FilterOptions,\n Error,\n FilterOptions,\n ReturnType<typeof sifaQueryKeys.search.filters>\n >,\n 'queryKey' | 'queryFn'\n >,\n) {\n const config = useSifaConfig();\n return useQuery({\n queryKey: sifaQueryKeys.search.filters(),\n queryFn: () => fetchSearchFilters(config),\n ...options,\n });\n}\n","'use client';\n\nimport { useQuery, type UseQueryOptions } from '@tanstack/react-query';\n\nimport { useSifaConfig } from '../config.js';\nimport {\n fetchFeaturedProfile,\n fetchSimilarProfiles,\n fetchSuggestionCount,\n fetchSuggestions,\n type FeaturedProfile,\n type FetchSuggestionsOptions,\n type SimilarProfile,\n type SuggestionsResponse,\n} from '../fetchers/discovery.js';\nimport { sifaQueryKeys } from '../keys.js';\n\nexport function useSimilarProfiles(\n did: string | undefined | null,\n opts: { limit?: number } = {},\n options?: Omit<\n UseQueryOptions<\n SimilarProfile[],\n Error,\n SimilarProfile[],\n ReturnType<typeof sifaQueryKeys.discovery.similar>\n >,\n 'queryKey' | 'queryFn'\n >,\n) {\n const config = useSifaConfig();\n const limit = opts.limit ?? 5;\n return useQuery({\n queryKey: sifaQueryKeys.discovery.similar(did ?? '', limit),\n queryFn: () => fetchSimilarProfiles(config, did ?? '', { limit }),\n enabled: Boolean(did) && (options?.enabled ?? true),\n ...options,\n });\n}\n\nexport function useSuggestions(\n opts: Omit<\n FetchSuggestionsOptions,\n keyof Omit<FetchSuggestionsOptions, 'source' | 'includeDismissed' | 'cursor' | 'limit'>\n > = {},\n options?: Omit<\n UseQueryOptions<\n SuggestionsResponse,\n Error,\n SuggestionsResponse,\n ReturnType<typeof sifaQueryKeys.discovery.suggestions>\n >,\n 'queryKey' | 'queryFn'\n >,\n) {\n const config = useSifaConfig();\n return useQuery({\n queryKey: sifaQueryKeys.discovery.suggestions(opts as Record<string, unknown>),\n queryFn: () => fetchSuggestions(config, opts),\n ...options,\n });\n}\n\nexport function useSuggestionCount(\n since?: string,\n options?: Omit<\n UseQueryOptions<\n number,\n Error,\n number,\n ReturnType<typeof sifaQueryKeys.discovery.suggestionCount>\n >,\n 'queryKey' | 'queryFn'\n >,\n) {\n const config = useSifaConfig();\n return useQuery({\n queryKey: sifaQueryKeys.discovery.suggestionCount(since),\n queryFn: () => fetchSuggestionCount(config, since),\n ...options,\n });\n}\n\nexport function useFeaturedProfile(\n options?: Omit<\n UseQueryOptions<\n FeaturedProfile | null,\n Error,\n FeaturedProfile | null,\n ReturnType<typeof sifaQueryKeys.discovery.featured>\n >,\n 'queryKey' | 'queryFn'\n >,\n) {\n const config = useSifaConfig();\n return useQuery({\n queryKey: sifaQueryKeys.discovery.featured(),\n queryFn: () => fetchFeaturedProfile(config),\n ...options,\n });\n}\n","'use client';\n\nimport { useQuery, type UseQueryOptions } from '@tanstack/react-query';\n\nimport { useSifaConfig } from '../config.js';\nimport { fetchFollowing, type FollowingResponse } from '../fetchers/follow.js';\nimport { sifaQueryKeys } from '../keys.js';\n\nexport function useFollowing(\n opts: { source?: string; cursor?: string; limit?: number } = {},\n options?: Omit<\n UseQueryOptions<\n FollowingResponse,\n Error,\n FollowingResponse,\n ReturnType<typeof sifaQueryKeys.follow.following>\n >,\n 'queryKey' | 'queryFn'\n >,\n) {\n const config = useSifaConfig();\n return useQuery({\n queryKey: sifaQueryKeys.follow.following(opts as Record<string, unknown>),\n queryFn: () => fetchFollowing(config, opts),\n ...options,\n });\n}\n"]}