grain.social is a photo sharing platform built on atproto.
grain.social
atproto
photography
appview
1import { infiniteQueryOptions, queryOptions } from "@tanstack/svelte-query";
2import { callXrpc } from "$hatk/client";
3
4type Fetch = typeof globalThis.fetch;
5
6// ─── Feeds ──────────────────────────────────────────────────────────
7
8export const recentFeedQuery = (limit = 50, f?: Fetch) =>
9 queryOptions({
10 queryKey: ["getFeed", "recent"],
11 queryFn: () => callXrpc("dev.hatk.getFeed", { feed: "recent", limit }, f),
12 staleTime: 60_000,
13 });
14
15export const followingFeedQuery = (did: string, limit = 50, f?: Fetch) =>
16 queryOptions({
17 queryKey: ["getFeed", "following", did],
18 queryFn: () => callXrpc("dev.hatk.getFeed", { feed: "following", actor: did, limit }, f),
19 staleTime: 60_000,
20 });
21
22export const forYouFeedQuery = (did: string, limit = 50, f?: Fetch) =>
23 queryOptions({
24 queryKey: ["getFeed", "foryou", did],
25 queryFn: () => callXrpc("dev.hatk.getFeed", { feed: "foryou", actor: did, limit }, f),
26 staleTime: 60_000,
27 });
28
29export const actorFeedQuery = (did: string, f?: Fetch) =>
30 infiniteQueryOptions({
31 queryKey: ["getFeed", "actor", did],
32 initialPageParam: undefined as string | undefined,
33 queryFn: ({ pageParam }) =>
34 callXrpc(
35 "dev.hatk.getFeed",
36 { feed: "actor", actor: did, limit: 30, ...(pageParam ? { cursor: pageParam } : {}) },
37 f,
38 ),
39 getNextPageParam: (lastPage) => lastPage?.cursor,
40 staleTime: 60_000,
41 });
42
43export const cameraFeedQuery = (camera: string, limit = 50, f?: Fetch) =>
44 queryOptions({
45 queryKey: ["getFeed", "camera", camera],
46 queryFn: () => callXrpc("dev.hatk.getFeed", { feed: "camera", camera, limit }, f),
47 staleTime: 60_000,
48 });
49
50export const camerasQuery = (f?: Fetch) =>
51 queryOptions({
52 queryKey: ["cameras"],
53 queryFn: () =>
54 callXrpc("social.grain.unspecced.getCameras", undefined, f).then((r) => r?.cameras ?? []),
55 staleTime: 5 * 60_000,
56 });
57
58export const locationFeedQuery = (location: string, name?: string, limit = 50, f?: Fetch) =>
59 queryOptions({
60 queryKey: ["getFeed", "location", name || location],
61 queryFn: () =>
62 callXrpc(
63 "dev.hatk.getFeed",
64 { feed: "location", location, ...(name ? { name } : {}), limit },
65 f,
66 ),
67 staleTime: 60_000,
68 });
69
70export const locationsQuery = (f?: Fetch) =>
71 queryOptions({
72 queryKey: ["locations"],
73 queryFn: () =>
74 callXrpc("social.grain.unspecced.getLocations", undefined, f).then((r) => r?.locations ?? []),
75 staleTime: 5 * 60_000,
76 });
77
78// ─── Favorites ──────────────────────────────────────────────────────
79
80export const actorFavoritesInfiniteQuery = (did: string, f?: Fetch) =>
81 infiniteQueryOptions({
82 queryKey: ["actorFavorites", did],
83 initialPageParam: undefined as string | undefined,
84 queryFn: ({ pageParam }) =>
85 callXrpc(
86 "social.grain.unspecced.getActorFavorites",
87 { actor: did, limit: 30, ...(pageParam ? { cursor: pageParam } : {}) },
88 f,
89 ),
90 getNextPageParam: (lastPage) => lastPage?.cursor,
91 staleTime: 60_000,
92 });
93
94// ─── Stories ────────────────────────────────────────────────────────
95
96export const storyAuthorsQuery = (f?: Fetch) =>
97 queryOptions({
98 queryKey: ["storyAuthors"],
99 queryFn: () =>
100 callXrpc("social.grain.unspecced.getStoryAuthors", undefined, f).then(
101 (r) => r?.authors ?? [],
102 ),
103 staleTime: 60_000,
104 });
105
106export const storyQuery = (storyUri: string, f?: Fetch) =>
107 queryOptions({
108 queryKey: ["getStory", storyUri],
109 queryFn: () =>
110 callXrpc("social.grain.unspecced.getStory", { story: storyUri }, f).then(
111 (r) => r?.story ?? null,
112 ),
113 staleTime: 60_000,
114 });
115
116export const storiesQuery = (did: string, f?: Fetch) =>
117 queryOptions({
118 queryKey: ["stories", did],
119 queryFn: () =>
120 callXrpc("social.grain.unspecced.getStories", { actor: did }, f).then(
121 (r) => r?.stories ?? [],
122 ),
123 staleTime: 30_000,
124 });
125
126export const storyArchiveQuery = (did: string, f?: Fetch) =>
127 infiniteQueryOptions({
128 queryKey: ["stories", "archive", did],
129 initialPageParam: undefined as string | undefined,
130 queryFn: ({ pageParam }) =>
131 callXrpc(
132 "social.grain.unspecced.getStoryArchive",
133 { actor: did, limit: 30, ...(pageParam ? { cursor: pageParam } : {}) },
134 f,
135 ).then((r) => r ?? { stories: [], cursor: undefined }),
136 getNextPageParam: (lastPage) => lastPage?.cursor,
137 staleTime: 60_000,
138 });
139
140// ─── Preferences ────────────────────────────────────────────────────
141
142export const preferencesQuery = (f?: Fetch) =>
143 queryOptions({
144 queryKey: ["preferences"],
145 queryFn: () =>
146 callXrpc("dev.hatk.getPreferences", undefined, f).then(
147 (r: any) => (r?.preferences ?? {}) as Record<string, unknown>,
148 ),
149 staleTime: 60_000,
150 });
151
152// ─── Profile ────────────────────────────────────────────────────────
153
154export const actorProfileQuery = (did: string, viewer?: string, f?: Fetch) =>
155 queryOptions({
156 queryKey: ["actorProfile", did],
157 queryFn: () =>
158 callXrpc(
159 "social.grain.unspecced.getActorProfile",
160 { actor: did, ...(viewer ? { viewer } : {}) },
161 f,
162 ),
163 staleTime: 60_000,
164 });
165
166// ─── Followers / Following ───────────────────────────────────────────
167
168export const followersQuery = (did: string, f?: Fetch) =>
169 queryOptions({
170 queryKey: ["followers", did],
171 queryFn: () => callXrpc("social.grain.unspecced.getFollowers", { actor: did }, f),
172 staleTime: 60_000,
173 });
174
175export const followingQuery = (did: string, f?: Fetch) =>
176 queryOptions({
177 queryKey: ["following", did],
178 queryFn: () => callXrpc("social.grain.unspecced.getFollowing", { actor: did }, f),
179 staleTime: 60_000,
180 });
181
182// ─── Blocks / Mutes ─────────────────────────────────────────────────
183
184export const blocksQuery = (f?: Fetch) =>
185 queryOptions({
186 queryKey: ["blocks"],
187 queryFn: () => callXrpc("social.grain.unspecced.getBlocks", {}, f),
188 staleTime: 60_000,
189 });
190
191export const mutesQuery = (f?: Fetch) =>
192 queryOptions({
193 queryKey: ["mutes"],
194 queryFn: () => callXrpc("social.grain.unspecced.getMutes", {}, f),
195 staleTime: 60_000,
196 });
197
198export const knownFollowersQuery = (did: string, viewer: string, f?: Fetch) =>
199 queryOptions({
200 queryKey: ["knownFollowers", did, viewer],
201 queryFn: () => callXrpc("social.grain.unspecced.getKnownFollowers", { actor: did, viewer }, f),
202 staleTime: 60_000,
203 });
204
205// ─── Search ─────────────────────────────────────────────────────────
206
207export const searchGalleriesQuery = (q: string) =>
208 queryOptions({
209 queryKey: ["search", "galleries", q],
210 queryFn: () => callXrpc("social.grain.unspecced.searchGalleries", { q, limit: 30 }),
211 enabled: !!q,
212 staleTime: 60_000,
213 });
214
215export const searchProfilesQuery = (q: string) =>
216 queryOptions({
217 queryKey: ["search", "people", q],
218 queryFn: () => callXrpc("social.grain.unspecced.searchProfiles", { q, limit: 30 }),
219 enabled: !!q,
220 staleTime: 60_000,
221 });
222
223// ─── Gallery ────────────────────────────────────────────────────────
224
225export const galleryQuery = (galleryUri: string, f?: Fetch) =>
226 queryOptions({
227 queryKey: ["getGallery", galleryUri],
228 queryFn: () =>
229 callXrpc("social.grain.unspecced.getGallery", { gallery: galleryUri }, f).then(
230 (r) => r.gallery,
231 ),
232 staleTime: 60_000,
233 });
234
235// ─── Gallery Thread (Comments) ──────────────────────────────────────
236
237export const commentThreadQuery = (subjectUri: string, f?: Fetch) =>
238 queryOptions({
239 queryKey: ["getCommentThread", subjectUri],
240 queryFn: () => callXrpc("social.grain.unspecced.getCommentThread", { subject: subjectUri }, f),
241 staleTime: 30_000,
242 });
243
244// ─── Notifications ─────────────────────────────────────────────────
245
246export const notificationsQuery = (viewer: string, f?: Fetch) =>
247 queryOptions({
248 queryKey: ["notifications", viewer],
249 queryFn: () => callXrpc("social.grain.unspecced.getNotifications", { limit: 100 }, f),
250 staleTime: 60_000,
251 });
252
253export const unseenNotificationCountQuery = (viewer: string, f?: Fetch) =>
254 queryOptions({
255 queryKey: ["unseenNotificationCount", viewer],
256 queryFn: () =>
257 callXrpc("social.grain.unspecced.getNotifications", { countOnly: true }, f).then(
258 (r: any) => (r?.unseenCount ?? 0) as number,
259 ),
260 staleTime: 60_000,
261 });