@singi-labs/sifa-sdk 0.5.0 → 0.6.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.
@@ -0,0 +1,161 @@
1
+ 'use strict';
2
+
3
+ var react = require('react');
4
+ var jsxRuntime = require('react/jsx-runtime');
5
+ var reactQuery = require('@tanstack/react-query');
6
+
7
+ // src/query/client.ts
8
+ var ApiError = class extends Error {
9
+ status;
10
+ body;
11
+ constructor(message, status, body) {
12
+ super(message);
13
+ this.name = "ApiError";
14
+ this.status = status;
15
+ this.body = body;
16
+ }
17
+ };
18
+ var DEFAULT_TIMEOUT_MS = 1e4;
19
+ var MAX_RATE_LIMIT_RETRIES = 3;
20
+ var RATE_LIMIT_RETRY_CAP_SECONDS = 3;
21
+ async function apiFetch(config, path, options = {}) {
22
+ const fetchFn = config.fetch ?? globalThis.fetch;
23
+ const url = `${config.baseUrl}${path}`;
24
+ const maxRetries = options.retryOn429 ? MAX_RATE_LIMIT_RETRIES : 0;
25
+ for (let attempt = 0; attempt <= maxRetries; attempt++) {
26
+ const signal = options.signal ?? AbortSignal.timeout(options.timeoutMs ?? DEFAULT_TIMEOUT_MS);
27
+ const headers = { ...options.headers ?? {} };
28
+ let body;
29
+ if (options.body !== void 0) {
30
+ headers["Content-Type"] ??= "application/json";
31
+ body = JSON.stringify(options.body);
32
+ }
33
+ const init = {
34
+ method: options.method ?? "GET",
35
+ headers,
36
+ body,
37
+ signal,
38
+ credentials: options.credentials,
39
+ cache: options.cache,
40
+ ...options.next ? { next: options.next } : {}
41
+ };
42
+ const res = await fetchFn(url, init);
43
+ if (res.status === 429 && attempt < maxRetries) {
44
+ const retryAfterRaw = res.headers.get("retry-after");
45
+ const retryAfter = retryAfterRaw ? Number.parseInt(retryAfterRaw, 10) : 2;
46
+ const waitSeconds = Math.min(
47
+ Number.isFinite(retryAfter) ? retryAfter : 2,
48
+ RATE_LIMIT_RETRY_CAP_SECONDS
49
+ );
50
+ await new Promise((r) => setTimeout(r, waitSeconds * 1e3));
51
+ continue;
52
+ }
53
+ if (!res.ok) {
54
+ let errBody;
55
+ try {
56
+ errBody = await res.json();
57
+ } catch {
58
+ try {
59
+ errBody = await res.text();
60
+ } catch {
61
+ errBody = void 0;
62
+ }
63
+ }
64
+ throw new ApiError(`Sifa API ${res.status} on ${path}`, res.status, errBody);
65
+ }
66
+ return await res.json();
67
+ }
68
+ throw new ApiError(`Sifa API exhausted retries on ${path}`, 429);
69
+ }
70
+ async function apiFetchOrNull(config, path, options = {}) {
71
+ try {
72
+ return await apiFetch(config, path, options);
73
+ } catch (e) {
74
+ if (e instanceof ApiError && e.status === 404) return null;
75
+ throw e;
76
+ }
77
+ }
78
+ var SifaConfigContext = react.createContext(null);
79
+ function SifaProvider({ config, children }) {
80
+ return /* @__PURE__ */ jsxRuntime.jsx(SifaConfigContext.Provider, { value: config, children });
81
+ }
82
+ function useSifaConfig() {
83
+ const ctx = react.useContext(SifaConfigContext);
84
+ if (!ctx) {
85
+ throw new Error(
86
+ "useSifaConfig must be used inside <SifaProvider>. Wrap your app once with <SifaProvider config={...}>."
87
+ );
88
+ }
89
+ return ctx;
90
+ }
91
+
92
+ // src/query/fetchers/profile.ts
93
+ function fetchProfile(config, handleOrDid, options = {}) {
94
+ const path = `/api/profile/${encodeURIComponent(handleOrDid)}`;
95
+ return apiFetchOrNull(config, path, {
96
+ retryOn429: true,
97
+ ...options
98
+ });
99
+ }
100
+
101
+ // src/query/fetchers/positions.ts
102
+ function createPosition(config, data, options = {}) {
103
+ return apiFetch(config, "/api/positions", {
104
+ method: "POST",
105
+ body: data,
106
+ credentials: "include",
107
+ ...options
108
+ });
109
+ }
110
+
111
+ // src/query/keys.ts
112
+ var sifaQueryKeys = {
113
+ all: () => ["sifa"],
114
+ profile: {
115
+ all: () => ["sifa", "profile"],
116
+ byHandle: (handleOrDid) => ["sifa", "profile", handleOrDid]
117
+ },
118
+ position: {
119
+ all: () => ["sifa", "position"],
120
+ byOwner: (did) => ["sifa", "position", "by-owner", did]
121
+ }
122
+ };
123
+
124
+ // src/query/hooks/use-create-position.ts
125
+ function useCreatePosition(ownerDid, options) {
126
+ const config = useSifaConfig();
127
+ const queryClient = reactQuery.useQueryClient();
128
+ return reactQuery.useMutation({
129
+ mutationFn: (data) => createPosition(config, data),
130
+ onSuccess: async (result, variables, onMutateResult, context) => {
131
+ if (result.success) {
132
+ await queryClient.invalidateQueries({ queryKey: sifaQueryKeys.profile.byHandle(ownerDid) });
133
+ await queryClient.invalidateQueries({ queryKey: sifaQueryKeys.position.byOwner(ownerDid) });
134
+ }
135
+ await options?.onSuccess?.(result, variables, onMutateResult, context);
136
+ },
137
+ ...options
138
+ });
139
+ }
140
+ function useProfile(handleOrDid, options) {
141
+ const config = useSifaConfig();
142
+ return reactQuery.useQuery({
143
+ queryKey: sifaQueryKeys.profile.byHandle(handleOrDid ?? ""),
144
+ queryFn: () => fetchProfile(config, handleOrDid ?? ""),
145
+ enabled: Boolean(handleOrDid) && (options?.enabled ?? true),
146
+ ...options
147
+ });
148
+ }
149
+
150
+ exports.ApiError = ApiError;
151
+ exports.SifaProvider = SifaProvider;
152
+ exports.apiFetch = apiFetch;
153
+ exports.apiFetchOrNull = apiFetchOrNull;
154
+ exports.createPosition = createPosition;
155
+ exports.fetchProfile = fetchProfile;
156
+ exports.sifaQueryKeys = sifaQueryKeys;
157
+ exports.useCreatePosition = useCreatePosition;
158
+ exports.useProfile = useProfile;
159
+ exports.useSifaConfig = useSifaConfig;
160
+ //# sourceMappingURL=index.cjs.map
161
+ //# sourceMappingURL=index.cjs.map
@@ -0,0 +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"]}
@@ -0,0 +1,168 @@
1
+ import * as react_jsx_runtime from 'react/jsx-runtime';
2
+ import { ReactNode } from 'react';
3
+ import { P as Profile } from '../index-IDRpze8y.cjs';
4
+ import * as _tanstack_react_query from '@tanstack/react-query';
5
+ import { UseMutationOptions, UseQueryOptions } from '@tanstack/react-query';
6
+
7
+ /**
8
+ * Foundation HTTP client for talking to the Sifa AppView.
9
+ *
10
+ * Stateless. Consumers supply a {@link SifaApiConfig} per call (the React
11
+ * hooks read it from context; non-React consumers pass it explicitly). No
12
+ * singletons, no module-level state.
13
+ */
14
+ /** Configuration passed to every fetcher. */
15
+ interface SifaApiConfig {
16
+ /** Base URL of the sifa-api AppView, e.g. `https://api.sifa.id`. No trailing slash. */
17
+ baseUrl: string;
18
+ /**
19
+ * Optional fetch implementation. Defaults to {@link globalThis.fetch}.
20
+ * Lets Next.js consumers pass their cache-enhanced fetch; node/Expo
21
+ * consumers can leave this unset.
22
+ */
23
+ fetch?: typeof fetch;
24
+ }
25
+ /** Options accepted by {@link apiFetch}. */
26
+ interface ApiFetchOptions {
27
+ method?: 'GET' | 'POST' | 'PUT' | 'DELETE' | 'PATCH';
28
+ /** Request body. Serialized to JSON automatically. */
29
+ body?: unknown;
30
+ /** AbortSignal. Defaults to `AbortSignal.timeout(timeoutMs)` if `timeoutMs` is set. */
31
+ signal?: AbortSignal;
32
+ /** Per-call timeout in milliseconds. Default: 10_000. Ignored if `signal` is provided. */
33
+ timeoutMs?: number;
34
+ /** Retry on HTTP 429 up to 3 times with the server's `Retry-After` delay (capped at 3s). */
35
+ retryOn429?: boolean;
36
+ /** Additional headers. `Content-Type: application/json` is set automatically when `body` is present. */
37
+ headers?: Record<string, string>;
38
+ credentials?: RequestCredentials;
39
+ cache?: RequestCache;
40
+ /**
41
+ * Next.js-specific cache hints. Ignored on non-Next runtimes. Passed
42
+ * through transparently as part of {@link RequestInit}.
43
+ */
44
+ next?: {
45
+ revalidate?: number | false;
46
+ tags?: string[];
47
+ };
48
+ }
49
+ /** Error thrown by {@link apiFetch} on non-2xx responses. */
50
+ declare class ApiError extends Error {
51
+ readonly status: number;
52
+ readonly body: unknown;
53
+ constructor(message: string, status: number, body?: unknown);
54
+ }
55
+ /**
56
+ * Generic fetcher used by all SDK query and mutation functions.
57
+ *
58
+ * Returns parsed JSON typed as `T`. Throws {@link ApiError} on non-2xx
59
+ * responses. Use {@link apiFetchOrNull} when 404 should resolve to `null`
60
+ * instead.
61
+ */
62
+ declare function apiFetch<T = unknown>(config: SifaApiConfig, path: string, options?: ApiFetchOptions): Promise<T>;
63
+ /**
64
+ * Variant of {@link apiFetch} that resolves to `null` on HTTP 404 instead
65
+ * of throwing. Useful for "fetch by handle" reads where missing is
66
+ * expected (e.g. unknown profile).
67
+ */
68
+ declare function apiFetchOrNull<T>(config: SifaApiConfig, path: string, options?: ApiFetchOptions): Promise<T | null>;
69
+
70
+ interface SifaProviderProps {
71
+ config: SifaApiConfig;
72
+ children: ReactNode;
73
+ }
74
+ /**
75
+ * Provides the {@link SifaApiConfig} that hooks under it can read via
76
+ * {@link useSifaConfig}. Wrap the React tree once; hooks consume it.
77
+ *
78
+ * @example
79
+ * ```tsx
80
+ * <QueryClientProvider client={queryClient}>
81
+ * <SifaProvider config={{ baseUrl: process.env.NEXT_PUBLIC_API_URL! }}>
82
+ * <App />
83
+ * </SifaProvider>
84
+ * </QueryClientProvider>
85
+ * ```
86
+ */
87
+ declare function SifaProvider({ config, children }: SifaProviderProps): react_jsx_runtime.JSX.Element;
88
+ /**
89
+ * Read the SDK's {@link SifaApiConfig} from context. Throws if no
90
+ * {@link SifaProvider} is mounted above.
91
+ */
92
+ declare function useSifaConfig(): SifaApiConfig;
93
+
94
+ /**
95
+ * Read the aggregated profile for a handle or DID.
96
+ *
97
+ * Returns `null` when the AppView has no profile for the given identifier
98
+ * (HTTP 404). Throws {@link ApiError} on other non-2xx responses.
99
+ *
100
+ * Server-callable (Next.js RSC) and client-callable (Expo, browser).
101
+ */
102
+ declare function fetchProfile(config: SifaApiConfig, handleOrDid: string, options?: ApiFetchOptions): Promise<Profile | null>;
103
+
104
+ /** Result returned by record-write mutations (create / update / delete). */
105
+ interface WriteResult {
106
+ success: boolean;
107
+ error?: string;
108
+ pdsHost?: string;
109
+ }
110
+ /** Result returned by create mutations. Includes the newly created `rkey`. */
111
+ interface CreateResult extends WriteResult {
112
+ rkey?: string;
113
+ }
114
+ /**
115
+ * Create a new `id.sifa.profile.position` record on the authenticated
116
+ * user's PDS. The AppView signs and writes via the user's OAuth session.
117
+ *
118
+ * `data` should be a lexicon-shaped position record (without `createdAt`
119
+ * or `rkey`; the AppView fills both). Validate with
120
+ * `ProfilePositionRecordSchema.omit({ createdAt: true })` before calling
121
+ * if you want client-side guarantees.
122
+ */
123
+ declare function createPosition(config: SifaApiConfig, data: Record<string, unknown>, options?: ApiFetchOptions): Promise<CreateResult>;
124
+
125
+ /**
126
+ * React hook for creating a new position record. On success, invalidates
127
+ * the owner's profile cache so the new position is reflected on the next
128
+ * read.
129
+ *
130
+ * The owner DID is required so the mutation can target the correct
131
+ * profile cache entry for invalidation.
132
+ */
133
+ declare function useCreatePosition(ownerDid: string, options?: Omit<UseMutationOptions<CreateResult, Error, Record<string, unknown>>, 'mutationFn'>): _tanstack_react_query.UseMutationResult<CreateResult, Error, Record<string, unknown>, unknown>;
134
+
135
+ /**
136
+ * Query key factory for TanStack Query.
137
+ *
138
+ * Keys are read-only tuples; the hierarchy matches the SDK's fetcher
139
+ * grouping. Use these instead of inline arrays so consumers can target
140
+ * `queryClient.invalidateQueries({ queryKey: keys.profile.all() })` and
141
+ * similar patterns without typos.
142
+ *
143
+ * Convention: every leaf key starts with the namespace ('sifa') so
144
+ * consumers can invalidate everything Sifa-related in one call.
145
+ */
146
+ declare const sifaQueryKeys: {
147
+ readonly all: () => readonly ["sifa"];
148
+ readonly profile: {
149
+ readonly all: () => readonly ["sifa", "profile"];
150
+ readonly byHandle: (handleOrDid: string) => readonly ["sifa", "profile", string];
151
+ };
152
+ readonly position: {
153
+ readonly all: () => readonly ["sifa", "position"];
154
+ readonly byOwner: (did: string) => readonly ["sifa", "position", "by-owner", string];
155
+ };
156
+ };
157
+ type SifaQueryKey = ReturnType<typeof sifaQueryKeys.all> | ReturnType<typeof sifaQueryKeys.profile.all> | ReturnType<typeof sifaQueryKeys.profile.byHandle> | ReturnType<typeof sifaQueryKeys.position.all> | ReturnType<typeof sifaQueryKeys.position.byOwner>;
158
+
159
+ /**
160
+ * React hook that reads an aggregated profile by handle or DID via
161
+ * TanStack Query. Returns `null` data when the profile does not exist.
162
+ *
163
+ * Pass `{ enabled: false }` (or an empty `handleOrDid`) to defer the
164
+ * fetch.
165
+ */
166
+ declare function useProfile(handleOrDid: string | undefined | null, options?: Omit<UseQueryOptions<Profile | null, Error, Profile | null, ReturnType<typeof sifaQueryKeys.profile.byHandle>>, 'queryKey' | 'queryFn'>): _tanstack_react_query.UseQueryResult<Profile | null, Error>;
167
+
168
+ export { ApiError, type ApiFetchOptions, type CreateResult, type SifaApiConfig, SifaProvider, type SifaProviderProps, type SifaQueryKey, type WriteResult, apiFetch, apiFetchOrNull, createPosition, fetchProfile, sifaQueryKeys, useCreatePosition, useProfile, useSifaConfig };
@@ -0,0 +1,168 @@
1
+ import * as react_jsx_runtime from 'react/jsx-runtime';
2
+ import { ReactNode } from 'react';
3
+ import { P as Profile } from '../index-IDRpze8y.js';
4
+ import * as _tanstack_react_query from '@tanstack/react-query';
5
+ import { UseMutationOptions, UseQueryOptions } from '@tanstack/react-query';
6
+
7
+ /**
8
+ * Foundation HTTP client for talking to the Sifa AppView.
9
+ *
10
+ * Stateless. Consumers supply a {@link SifaApiConfig} per call (the React
11
+ * hooks read it from context; non-React consumers pass it explicitly). No
12
+ * singletons, no module-level state.
13
+ */
14
+ /** Configuration passed to every fetcher. */
15
+ interface SifaApiConfig {
16
+ /** Base URL of the sifa-api AppView, e.g. `https://api.sifa.id`. No trailing slash. */
17
+ baseUrl: string;
18
+ /**
19
+ * Optional fetch implementation. Defaults to {@link globalThis.fetch}.
20
+ * Lets Next.js consumers pass their cache-enhanced fetch; node/Expo
21
+ * consumers can leave this unset.
22
+ */
23
+ fetch?: typeof fetch;
24
+ }
25
+ /** Options accepted by {@link apiFetch}. */
26
+ interface ApiFetchOptions {
27
+ method?: 'GET' | 'POST' | 'PUT' | 'DELETE' | 'PATCH';
28
+ /** Request body. Serialized to JSON automatically. */
29
+ body?: unknown;
30
+ /** AbortSignal. Defaults to `AbortSignal.timeout(timeoutMs)` if `timeoutMs` is set. */
31
+ signal?: AbortSignal;
32
+ /** Per-call timeout in milliseconds. Default: 10_000. Ignored if `signal` is provided. */
33
+ timeoutMs?: number;
34
+ /** Retry on HTTP 429 up to 3 times with the server's `Retry-After` delay (capped at 3s). */
35
+ retryOn429?: boolean;
36
+ /** Additional headers. `Content-Type: application/json` is set automatically when `body` is present. */
37
+ headers?: Record<string, string>;
38
+ credentials?: RequestCredentials;
39
+ cache?: RequestCache;
40
+ /**
41
+ * Next.js-specific cache hints. Ignored on non-Next runtimes. Passed
42
+ * through transparently as part of {@link RequestInit}.
43
+ */
44
+ next?: {
45
+ revalidate?: number | false;
46
+ tags?: string[];
47
+ };
48
+ }
49
+ /** Error thrown by {@link apiFetch} on non-2xx responses. */
50
+ declare class ApiError extends Error {
51
+ readonly status: number;
52
+ readonly body: unknown;
53
+ constructor(message: string, status: number, body?: unknown);
54
+ }
55
+ /**
56
+ * Generic fetcher used by all SDK query and mutation functions.
57
+ *
58
+ * Returns parsed JSON typed as `T`. Throws {@link ApiError} on non-2xx
59
+ * responses. Use {@link apiFetchOrNull} when 404 should resolve to `null`
60
+ * instead.
61
+ */
62
+ declare function apiFetch<T = unknown>(config: SifaApiConfig, path: string, options?: ApiFetchOptions): Promise<T>;
63
+ /**
64
+ * Variant of {@link apiFetch} that resolves to `null` on HTTP 404 instead
65
+ * of throwing. Useful for "fetch by handle" reads where missing is
66
+ * expected (e.g. unknown profile).
67
+ */
68
+ declare function apiFetchOrNull<T>(config: SifaApiConfig, path: string, options?: ApiFetchOptions): Promise<T | null>;
69
+
70
+ interface SifaProviderProps {
71
+ config: SifaApiConfig;
72
+ children: ReactNode;
73
+ }
74
+ /**
75
+ * Provides the {@link SifaApiConfig} that hooks under it can read via
76
+ * {@link useSifaConfig}. Wrap the React tree once; hooks consume it.
77
+ *
78
+ * @example
79
+ * ```tsx
80
+ * <QueryClientProvider client={queryClient}>
81
+ * <SifaProvider config={{ baseUrl: process.env.NEXT_PUBLIC_API_URL! }}>
82
+ * <App />
83
+ * </SifaProvider>
84
+ * </QueryClientProvider>
85
+ * ```
86
+ */
87
+ declare function SifaProvider({ config, children }: SifaProviderProps): react_jsx_runtime.JSX.Element;
88
+ /**
89
+ * Read the SDK's {@link SifaApiConfig} from context. Throws if no
90
+ * {@link SifaProvider} is mounted above.
91
+ */
92
+ declare function useSifaConfig(): SifaApiConfig;
93
+
94
+ /**
95
+ * Read the aggregated profile for a handle or DID.
96
+ *
97
+ * Returns `null` when the AppView has no profile for the given identifier
98
+ * (HTTP 404). Throws {@link ApiError} on other non-2xx responses.
99
+ *
100
+ * Server-callable (Next.js RSC) and client-callable (Expo, browser).
101
+ */
102
+ declare function fetchProfile(config: SifaApiConfig, handleOrDid: string, options?: ApiFetchOptions): Promise<Profile | null>;
103
+
104
+ /** Result returned by record-write mutations (create / update / delete). */
105
+ interface WriteResult {
106
+ success: boolean;
107
+ error?: string;
108
+ pdsHost?: string;
109
+ }
110
+ /** Result returned by create mutations. Includes the newly created `rkey`. */
111
+ interface CreateResult extends WriteResult {
112
+ rkey?: string;
113
+ }
114
+ /**
115
+ * Create a new `id.sifa.profile.position` record on the authenticated
116
+ * user's PDS. The AppView signs and writes via the user's OAuth session.
117
+ *
118
+ * `data` should be a lexicon-shaped position record (without `createdAt`
119
+ * or `rkey`; the AppView fills both). Validate with
120
+ * `ProfilePositionRecordSchema.omit({ createdAt: true })` before calling
121
+ * if you want client-side guarantees.
122
+ */
123
+ declare function createPosition(config: SifaApiConfig, data: Record<string, unknown>, options?: ApiFetchOptions): Promise<CreateResult>;
124
+
125
+ /**
126
+ * React hook for creating a new position record. On success, invalidates
127
+ * the owner's profile cache so the new position is reflected on the next
128
+ * read.
129
+ *
130
+ * The owner DID is required so the mutation can target the correct
131
+ * profile cache entry for invalidation.
132
+ */
133
+ declare function useCreatePosition(ownerDid: string, options?: Omit<UseMutationOptions<CreateResult, Error, Record<string, unknown>>, 'mutationFn'>): _tanstack_react_query.UseMutationResult<CreateResult, Error, Record<string, unknown>, unknown>;
134
+
135
+ /**
136
+ * Query key factory for TanStack Query.
137
+ *
138
+ * Keys are read-only tuples; the hierarchy matches the SDK's fetcher
139
+ * grouping. Use these instead of inline arrays so consumers can target
140
+ * `queryClient.invalidateQueries({ queryKey: keys.profile.all() })` and
141
+ * similar patterns without typos.
142
+ *
143
+ * Convention: every leaf key starts with the namespace ('sifa') so
144
+ * consumers can invalidate everything Sifa-related in one call.
145
+ */
146
+ declare const sifaQueryKeys: {
147
+ readonly all: () => readonly ["sifa"];
148
+ readonly profile: {
149
+ readonly all: () => readonly ["sifa", "profile"];
150
+ readonly byHandle: (handleOrDid: string) => readonly ["sifa", "profile", string];
151
+ };
152
+ readonly position: {
153
+ readonly all: () => readonly ["sifa", "position"];
154
+ readonly byOwner: (did: string) => readonly ["sifa", "position", "by-owner", string];
155
+ };
156
+ };
157
+ type SifaQueryKey = ReturnType<typeof sifaQueryKeys.all> | ReturnType<typeof sifaQueryKeys.profile.all> | ReturnType<typeof sifaQueryKeys.profile.byHandle> | ReturnType<typeof sifaQueryKeys.position.all> | ReturnType<typeof sifaQueryKeys.position.byOwner>;
158
+
159
+ /**
160
+ * React hook that reads an aggregated profile by handle or DID via
161
+ * TanStack Query. Returns `null` data when the profile does not exist.
162
+ *
163
+ * Pass `{ enabled: false }` (or an empty `handleOrDid`) to defer the
164
+ * fetch.
165
+ */
166
+ declare function useProfile(handleOrDid: string | undefined | null, options?: Omit<UseQueryOptions<Profile | null, Error, Profile | null, ReturnType<typeof sifaQueryKeys.profile.byHandle>>, 'queryKey' | 'queryFn'>): _tanstack_react_query.UseQueryResult<Profile | null, Error>;
167
+
168
+ export { ApiError, type ApiFetchOptions, type CreateResult, type SifaApiConfig, SifaProvider, type SifaProviderProps, type SifaQueryKey, type WriteResult, apiFetch, apiFetchOrNull, createPosition, fetchProfile, sifaQueryKeys, useCreatePosition, useProfile, useSifaConfig };