personal web client for Bluesky
typescript solidjs bluesky atcute

refactor: move to new api client

mary.my.id c905ae2e c1452f11

verified
+6 -6
src/api/mutations/post.ts
··· 15 15 shadow: () => PostShadowView, 16 16 ) => { 17 17 const queryClient = useQueryClient(); 18 - const { rpc } = useAgent(); 18 + const { client } = useAgent(); 19 19 const { currentAccount } = useSession(); 20 20 21 21 const postUri = post().uri; ··· 30 30 return prevLikeUri; 31 31 } 32 32 33 - const result = await createRecord(rpc, { 33 + const result = await createRecord(client, { 34 34 repo: currentAccount!.did, 35 35 collection: 'app.bsky.feed.like', 36 36 record: { ··· 47 47 } else if (prevLikeUri) { 48 48 const uri = parseCanonicalResourceUri(prevLikeUri); 49 49 50 - await deleteRecord(rpc, { 50 + await deleteRecord(client, { 51 51 repo: currentAccount!.did, 52 52 collection: 'app.bsky.feed.like', 53 53 rkey: uri.rkey, ··· 74 74 shadow: () => PostShadowView, 75 75 ) => { 76 76 const queryClient = useQueryClient(); 77 - const { rpc } = useAgent(); 77 + const { client } = useAgent(); 78 78 const { currentAccount } = useSession(); 79 79 80 80 const postUri = post().uri; ··· 89 89 return prevRepostUri; 90 90 } 91 91 92 - const result = await createRecord(rpc, { 92 + const result = await createRecord(client, { 93 93 repo: currentAccount!.did, 94 94 collection: 'app.bsky.feed.repost', 95 95 record: { ··· 106 106 } else if (prevRepostUri) { 107 107 const uri = parseCanonicalResourceUri(prevRepostUri); 108 108 109 - await deleteRecord(rpc, { 109 + await deleteRecord(client, { 110 110 repo: currentAccount!.did, 111 111 collection: 'app.bsky.feed.repost', 112 112 rkey: uri.rkey,
+3 -3
src/api/mutations/profile.ts
··· 15 15 shadow: () => ProfileShadowView, 16 16 ) => { 17 17 const queryClient = useQueryClient(); 18 - const { rpc } = useAgent(); 18 + const { client } = useAgent(); 19 19 const { currentAccount } = useSession(); 20 20 21 21 const did = profile().did; ··· 30 30 return prevFollowUri; 31 31 } 32 32 33 - const result = await createRecord(rpc, { 33 + const result = await createRecord(client, { 34 34 repo: currentAccount!.did, 35 35 collection: 'app.bsky.graph.follow', 36 36 record: { ··· 44 44 } else if (prevFollowUri) { 45 45 const uri = parseCanonicalResourceUri(prevFollowUri); 46 46 47 - await deleteRecord(rpc, { 47 + await deleteRecord(client, { 48 48 repo: currentAccount!.did, 49 49 collection: 'app.bsky.graph.follow', 50 50 rkey: uri.rkey,
+8 -3
src/api/queries/blob.ts
··· 1 - import type { XRPC } from '@atcute/client'; 1 + import { type Client, ok } from '@atcute/client'; 2 2 import type { At } from '@atcute/client/lexicons'; 3 3 4 - export const uploadBlob = async (rpc: XRPC, blob: Blob): Promise<At.Blob<any>> => { 5 - const { data } = await rpc.call('com.atproto.repo.uploadBlob', { data: blob }); 4 + export const uploadBlob = async (client: Client, blob: Blob): Promise<At.Blob<any>> => { 5 + const data = await ok( 6 + client.post('com.atproto.repo.uploadBlob', { 7 + input: blob, 8 + }), 9 + ); 10 + 6 11 return data.blob; 7 12 };
+10 -7
src/api/queries/bookmark-feed.ts
··· 1 1 import { tokenize } from '@atcute/bluesky-search-parser'; 2 + import { ok } from '@atcute/client'; 2 3 import type { AppBskyFeedDefs } from '@atcute/client/lexicons'; 3 4 import { mapDefined } from '@mary/array-fns'; 4 5 import { filter, map, take, toArray } from '@mary/async-iterator-fns'; ··· 41 42 42 43 export const createBookmarkFeedQuery = (tagId: () => string, search: () => string) => { 43 44 const bookmarks = inject(BookmarksService); 44 - const { rpc } = useAgent(); 45 + const { client } = useAgent(); 45 46 46 47 const listing = createInfiniteQuery(() => { 47 48 const $tagId = tagId(); ··· 95 96 let map: Map<string, AppBskyFeedDefs.PostView>; 96 97 97 98 try { 98 - const { data } = await rpc.get('app.bsky.feed.getPosts', { 99 - signal: ctx.signal, 100 - params: { 101 - uris: raws.map((item) => item.view.uri), 102 - }, 103 - }); 99 + const data = await ok( 100 + client.get('app.bsky.feed.getPosts', { 101 + signal: ctx.signal, 102 + params: { 103 + uris: raws.map((item) => item.view.uri), 104 + }, 105 + }), 106 + ); 104 107 105 108 map = new Map(data.posts.map((view) => [view.uri, view])); 106 109 } catch {}
+11 -8
src/api/queries/feed.ts
··· 1 1 import { modifyMutable, reconcile } from 'solid-js/store'; 2 2 3 + import { ok } from '@atcute/client'; 3 4 import type { AppBskyFeedDefs, At } from '@atcute/client/lexicons'; 4 5 import { createQuery } from '@mary/solid-query'; 5 6 ··· 14 15 import { resolveHandle } from './handle'; 15 16 16 17 export const createFeedMetaQuery = (feedUri: () => string) => { 17 - const { rpc } = useAgent(); 18 + const { client } = useAgent(); 18 19 const { currentAccount } = useSession(); 19 20 20 21 return createQuery((queryClient) => { ··· 29 30 if (isDid(uri.repo)) { 30 31 did = uri.repo; 31 32 } else { 32 - did = await resolveHandle(rpc, uri.repo, ctx.signal); 33 + did = await resolveHandle(client, uri.repo, ctx.signal); 33 34 } 34 35 35 - const { data } = await rpc.get('app.bsky.feed.getFeedGenerator', { 36 - signal: ctx.signal, 37 - params: { 38 - feed: makeAtUri(did, uri.collection, uri.rkey), 39 - }, 40 - }); 36 + const data = await ok( 37 + client.get('app.bsky.feed.getFeedGenerator', { 38 + signal: ctx.signal, 39 + params: { 40 + feed: makeAtUri(did, uri.collection, uri.rkey), 41 + }, 42 + }), 43 + ); 41 44 42 45 if (currentAccount) { 43 46 const found = currentAccount.preferences.feeds.find((item): item is SavedGeneratorFeed => {
+12 -10
src/api/queries/handle.ts
··· 1 - import { XRPC } from '@atcute/client'; 1 + import { type Client, ok } from '@atcute/client'; 2 2 import type { At } from '@atcute/client/lexicons'; 3 3 import { createQuery } from '@mary/solid-query'; 4 4 5 5 import { useAgent } from '~/lib/states/agent'; 6 6 7 7 export const useResolveHandleQuery = (handle: () => At.Handle) => { 8 - const { rpc } = useAgent(); 8 + const { client } = useAgent(); 9 9 10 10 return createQuery(() => { 11 11 const $handle = handle(); ··· 13 13 return { 14 14 queryKey: ['resolve-handle', $handle], 15 15 async queryFn(ctx) { 16 - return resolveHandle(rpc, $handle, ctx.signal); 16 + return resolveHandle(client, $handle, ctx.signal); 17 17 }, 18 18 }; 19 19 }); 20 20 }; 21 21 22 - export const resolveHandle = async (rpc: XRPC, handle: At.Handle, signal?: AbortSignal) => { 23 - const { data } = await rpc.get('com.atproto.identity.resolveHandle', { 24 - signal: signal, 25 - params: { 26 - handle: handle, 27 - }, 28 - }); 22 + export const resolveHandle = async (client: Client, handle: At.Handle, signal?: AbortSignal) => { 23 + const data = await ok( 24 + client.get('com.atproto.identity.resolveHandle', { 25 + signal: signal, 26 + params: { 27 + handle: handle, 28 + }, 29 + }), 30 + ); 29 31 30 32 return data.did; 31 33 };
+14 -12
src/api/queries/labeler.ts
··· 1 - import { XRPCError } from '@atcute/client'; 1 + import { ClientResponseError, ok } from '@atcute/client'; 2 2 import type { AppBskyLabelerDefs, At } from '@atcute/client/lexicons'; 3 3 import { createQuery } from '@mary/solid-query'; 4 4 ··· 7 7 import { interpretLabelerDefinition } from '../moderation/labeler'; 8 8 9 9 export const createLabelerMetaQuery = (did: () => At.Did) => { 10 - const { rpc } = useAgent(); 10 + const { client } = useAgent(); 11 11 12 12 const query = createQuery(() => { 13 13 const $did = did(); ··· 15 15 return { 16 16 queryKey: ['labeler-definition', $did], 17 17 async queryFn(ctx) { 18 - const { data } = await rpc.get('app.bsky.labeler.getServices', { 19 - signal: ctx.signal, 20 - params: { 21 - dids: [$did], 22 - detailed: true, 23 - }, 24 - }); 18 + const data = await ok( 19 + client.get('app.bsky.labeler.getServices', { 20 + signal: ctx.signal, 21 + params: { 22 + dids: [$did], 23 + detailed: true, 24 + }, 25 + }), 26 + ); 25 27 26 28 const service = data.views[0] as AppBskyLabelerDefs.LabelerViewDetailed; 27 29 28 30 if (!service) { 29 - throw new XRPCError(400, { 30 - kind: 'NotFound', 31 - description: `Labeler not found: ${$did}`, 31 + throw new ClientResponseError({ 32 + status: 400, 33 + data: { error: `NotFound`, message: `Labeler not found: ${$did}` }, 32 34 }); 33 35 } 34 36
+12 -9
src/api/queries/list-members.ts
··· 1 + import { ok } from '@atcute/client'; 1 2 import type { At } from '@atcute/client/lexicons'; 2 3 import { type QueryFunctionContext as QC, createInfiniteQuery } from '@mary/solid-query'; 3 4 4 5 import { useAgent } from '~/lib/states/agent'; 5 6 6 7 export const createListMembersQuery = (listUri: () => At.ResourceUri) => { 7 - const { rpc } = useAgent(); 8 + const { client } = useAgent(); 8 9 9 10 return createInfiniteQuery(() => { 10 11 const $listUri = listUri(); ··· 12 13 return { 13 14 queryKey: ['list-members', $listUri], 14 15 async queryFn(ctx: QC<never, string | undefined>) { 15 - const { data } = await rpc.get('app.bsky.graph.getList', { 16 - signal: ctx.signal, 17 - params: { 18 - list: $listUri, 19 - limit: 50, 20 - cursor: ctx.pageParam, 21 - }, 22 - }); 16 + const data = await ok( 17 + client.get('app.bsky.graph.getList', { 18 + signal: ctx.signal, 19 + params: { 20 + list: $listUri, 21 + limit: 50, 22 + cursor: ctx.pageParam, 23 + }, 24 + }), 25 + ); 23 26 24 27 return { 25 28 cursor: data.cursor,
+12 -9
src/api/queries/list.ts
··· 1 1 import { modifyMutable, reconcile } from 'solid-js/store'; 2 2 3 + import { ok } from '@atcute/client'; 3 4 import type { AppBskyGraphDefs, At } from '@atcute/client/lexicons'; 4 5 import { createQuery } from '@mary/solid-query'; 5 6 ··· 14 15 import { resolveHandle } from './handle'; 15 16 16 17 export const createListMetaQuery = (listUri: () => string) => { 17 - const { rpc } = useAgent(); 18 + const { client } = useAgent(); 18 19 const { currentAccount } = useSession(); 19 20 20 21 return createQuery((queryClient) => { ··· 29 30 if (isDid(uri.repo)) { 30 31 did = uri.repo; 31 32 } else { 32 - did = await resolveHandle(rpc, uri.repo, ctx.signal); 33 + did = await resolveHandle(client, uri.repo, ctx.signal); 33 34 } 34 35 35 - const { data } = await rpc.get('app.bsky.graph.getList', { 36 - signal: ctx.signal, 37 - params: { 38 - list: makeAtUri(did, uri.collection, uri.rkey), 39 - limit: 1, 40 - }, 41 - }); 36 + const data = await ok( 37 + client.get('app.bsky.graph.getList', { 38 + signal: ctx.signal, 39 + params: { 40 + list: makeAtUri(did, uri.collection, uri.rkey), 41 + limit: 1, 42 + }, 43 + }), 44 + ); 42 45 43 46 if (currentAccount) { 44 47 const found = currentAccount.preferences.feeds.find((item): item is SavedListFeed => {
+30 -23
src/api/queries/my-lists.ts
··· 1 + import { ok } from '@atcute/client'; 1 2 import type { AppBskyGraphDefs } from '@atcute/client/lexicons'; 2 3 import { createQuery } from '@mary/solid-query'; 3 4 ··· 7 8 export type MyListsFilter = 'all' | 'curation' | 'moderation' | 'all-including-subscribed'; 8 9 9 10 export const createMyListsQuery = (filter: MyListsFilter) => { 10 - const { rpc } = useAgent(); 11 + const { client } = useAgent(); 11 12 const { currentAccount } = useSession(); 12 13 13 14 return createQuery(() => ({ ··· 15 16 async queryFn({ signal }) { 16 17 const promises = [ 17 18 accumulate(async (cursor) => { 18 - const { data } = await rpc.get('app.bsky.graph.getLists', { 19 - signal, 20 - params: { 21 - actor: currentAccount!.did, 22 - cursor, 23 - limit: 100, 24 - }, 25 - }); 19 + const data = await ok( 20 + client.get('app.bsky.graph.getLists', { 21 + signal, 22 + params: { 23 + actor: currentAccount!.did, 24 + cursor, 25 + limit: 100, 26 + }, 27 + }), 28 + ); 26 29 27 30 return { 28 31 cursor: data.cursor, ··· 34 37 if (filter === 'all-including-subscribed' || filter === 'moderation') { 35 38 promises.push( 36 39 accumulate(async (cursor) => { 37 - const { data } = await rpc.get('app.bsky.graph.getListMutes', { 38 - signal, 39 - params: { 40 - cursor, 41 - limit: 100, 42 - }, 43 - }); 40 + const data = await ok( 41 + client.get('app.bsky.graph.getListMutes', { 42 + signal, 43 + params: { 44 + cursor, 45 + limit: 100, 46 + }, 47 + }), 48 + ); 44 49 45 50 return { 46 51 cursor: data.cursor, ··· 51 56 52 57 promises.push( 53 58 accumulate(async (cursor) => { 54 - const { data } = await rpc.get('app.bsky.graph.getListBlocks', { 55 - signal, 56 - params: { 57 - cursor, 58 - limit: 100, 59 - }, 60 - }); 59 + const data = await ok( 60 + client.get('app.bsky.graph.getListBlocks', { 61 + signal, 62 + params: { 63 + cursor, 64 + limit: 100, 65 + }, 66 + }), 67 + ); 61 68 62 69 return { 63 70 cursor: data.cursor,
+7 -4
src/api/queries/notification-count.tsx
··· 1 + import { ok } from '@atcute/client'; 1 2 import { createQuery } from '@mary/solid-query'; 2 3 3 4 import { useAgent } from '~/lib/states/agent'; ··· 5 6 6 7 export const createNotificationCountQuery = (options?: { readonly disabled?: boolean }) => { 7 8 const { currentAccount } = useSession(); 8 - const { rpc } = useAgent(); 9 + const { client } = useAgent(); 9 10 10 11 const query = createQuery(() => ({ 11 12 queryKey: ['notification', 'count'], 12 13 enabled: currentAccount !== undefined && !options?.disabled, 13 14 async queryFn() { 14 - const { data } = await rpc.get('app.bsky.notification.getUnreadCount', { 15 - params: {}, 16 - }); 15 + const data = await ok( 16 + client.get('app.bsky.notification.getUnreadCount', { 17 + params: {}, 18 + }), 19 + ); 17 20 18 21 return data; 19 22 },
+31 -20
src/api/queries/notification-feed.tsx
··· 1 1 import { createSignal } from 'solid-js'; 2 2 3 + import { ok } from '@atcute/client'; 3 4 import type { AppBskyFeedDefs, AppBskyNotificationListNotifications } from '@atcute/client/lexicons'; 4 5 import { chunked, mapDefined } from '@mary/array-fns'; 5 6 import { type QueryFunctionContext as QC, createInfiniteQuery, useQueryClient } from '@mary/solid-query'; ··· 84 85 export type NotificationsFilter = 'all' | 'mentions'; 85 86 86 87 export const createNotificationFeedQuery = (filter: () => NotificationsFilter) => { 87 - const { rpc } = useAgent(); 88 + const { client } = useAgent(); 88 89 const queryClient = useQueryClient(); 89 90 90 91 const [firstFetchedAt, setFirstFetchedAt] = createSignal(0); ··· 104 105 reasons = ['mention', 'reply', 'quote']; 105 106 } 106 107 107 - const { data } = await rpc.get('app.bsky.notification.listNotifications', { 108 - signal: signal, 109 - params: { 110 - limit: 40, 111 - reasons: reasons, 112 - cursor: pageParam?.cursor, 113 - }, 114 - }); 108 + const data = await ok( 109 + client.get('app.bsky.notification.listNotifications', { 110 + signal: signal, 111 + params: { 112 + limit: 40, 113 + reasons: reasons, 114 + cursor: pageParam?.cursor, 115 + }, 116 + }), 117 + ); 115 118 116 119 const notifs = data.notifications; 117 120 const firstSeenAt = pageParam?.seenAt; ··· 129 132 const subjectUri = item.reasonSubject; 130 133 131 134 // skip if they're not related to posts. 132 - if (!subjectUri || parseCanonicalResourceUri(subjectUri).collection !== 'app.bsky.feed.post') { 135 + if ( 136 + !subjectUri || 137 + parseCanonicalResourceUri(subjectUri).collection !== 'app.bsky.feed.post' 138 + ) { 133 139 return; 134 140 } 135 141 ··· 142 148 143 149 const chunkedPosts = await Promise.all( 144 150 chunked(Array.from(postUris), 25).map(async (uris) => { 145 - const { data } = await rpc.get('app.bsky.feed.getPosts', { 146 - params: { 147 - uris: uris, 148 - }, 149 - }); 151 + const data = await ok( 152 + client.get('app.bsky.feed.getPosts', { 153 + params: { 154 + uris: uris, 155 + }, 156 + }), 157 + ); 150 158 151 159 return data.posts; 152 160 }), ··· 238 246 const indexedAt = new Date(notifs[0]?.indexedAt ?? 0).getTime(); 239 247 const seenAt = Math.max(now, indexedAt); 240 248 241 - const promise = rpc.call('app.bsky.notification.updateSeen', { 242 - data: { 243 - seenAt: new Date(seenAt).toISOString(), 244 - }, 245 - }); 249 + const promise = ok( 250 + client.post('app.bsky.notification.updateSeen', { 251 + as: null, 252 + input: { 253 + seenAt: new Date(seenAt).toISOString(), 254 + }, 255 + }), 256 + ); 246 257 247 258 queryClient.cancelQueries({ 248 259 exact: true,
+12 -9
src/api/queries/post-quotes.ts
··· 1 + import { ok } from '@atcute/client'; 1 2 import type { AppBskyFeedGetQuotes, At } from '@atcute/client/lexicons'; 2 3 import { type QueryFunctionContext as QC, createInfiniteQuery } from '@mary/solid-query'; 3 4 4 5 import { useAgent } from '~/lib/states/agent'; 5 6 6 7 export const createPostQuotesQuery = (uri: () => At.ResourceUri) => { 7 - const { rpc } = useAgent(); 8 + const { client } = useAgent(); 8 9 9 10 return createInfiniteQuery(() => { 10 11 const $uri = uri(); ··· 13 14 queryKey: ['post-quotes', $uri], 14 15 structuralSharing: false, 15 16 async queryFn(ctx: QC<never, string | undefined>): Promise<AppBskyFeedGetQuotes.Output> { 16 - const { data } = await rpc.get('app.bsky.feed.getQuotes', { 17 - signal: ctx.signal, 18 - params: { 19 - uri: $uri, 20 - limit: 50, 21 - cursor: ctx.pageParam, 22 - }, 23 - }); 17 + const data = await ok( 18 + client.get('app.bsky.feed.getQuotes', { 19 + signal: ctx.signal, 20 + params: { 21 + uri: $uri, 22 + limit: 50, 23 + cursor: ctx.pageParam, 24 + }, 25 + }), 26 + ); 24 27 25 28 return data; 26 29 },
+18 -13
src/api/queries/post-thread.ts
··· 1 - import { XRPCError } from '@atcute/client'; 1 + import { ClientResponseError, ok } from '@atcute/client'; 2 2 import type { AppBskyFeedDefs, At, Brand } from '@atcute/client/lexicons'; 3 3 import { createQuery } from '@mary/solid-query'; 4 4 ··· 12 12 type ThreadReturn = Brand.Union<AppBskyFeedDefs.ThreadViewPost | AppBskyFeedDefs.BlockedPost>; 13 13 14 14 export const usePostThreadQuery = (uri: () => At.ResourceUri) => { 15 - const { rpc } = useAgent(); 15 + const { client } = useAgent(); 16 16 17 17 return createQuery((queryClient) => { 18 18 const $uri = uri(); ··· 21 21 queryKey: ['post-thread', $uri], 22 22 structuralSharing: false, 23 23 async queryFn(ctx): Promise<ThreadReturn> { 24 - const { data } = await rpc.get('app.bsky.feed.getPostThread', { 25 - signal: ctx.signal, 26 - params: { 27 - uri: $uri, 28 - depth: MAX_DEPTH, 29 - parentHeight: MAX_HEIGHT, 30 - }, 31 - }); 24 + const data = await ok( 25 + client.get('app.bsky.feed.getPostThread', { 26 + signal: ctx.signal, 27 + params: { 28 + uri: $uri, 29 + depth: MAX_DEPTH, 30 + parentHeight: MAX_HEIGHT, 31 + }, 32 + }), 33 + ); 32 34 33 35 const thread = data.thread; 34 36 35 37 if (thread.$type === 'app.bsky.feed.defs#notFoundPost') { 36 - throw new XRPCError(400, { 37 - kind: 'NotFound', 38 - description: `Post not found: ${$uri}`, 38 + throw new ClientResponseError({ 39 + status: 400, 40 + data: { 41 + error: `NotFound`, 42 + message: `Post not found: ${$uri}`, 43 + }, 39 44 }); 40 45 } 41 46
+11 -8
src/api/queries/post.ts
··· 1 + import { ok } from '@atcute/client'; 1 2 import type { At } from '@atcute/client/lexicons'; 2 3 import { createQuery } from '@mary/solid-query'; 3 4 ··· 10 11 import { resolveHandle } from './handle'; 11 12 12 13 export const createPostQuery = (postUri: () => string) => { 13 - const { rpc } = useAgent(); 14 + const { client } = useAgent(); 14 15 15 16 return createQuery((queryClient) => { 16 17 const $postUri = postUri(); ··· 24 25 if (isDid(uri.repo)) { 25 26 did = uri.repo; 26 27 } else { 27 - did = await resolveHandle(rpc, uri.repo, ctx.signal); 28 + did = await resolveHandle(client, uri.repo, ctx.signal); 28 29 } 29 30 30 - const { data } = await rpc.get('app.bsky.feed.getPosts', { 31 - signal: ctx.signal, 32 - params: { 33 - uris: [makeAtUri(did, uri.collection, uri.rkey)], 34 - }, 35 - }); 31 + const data = await ok( 32 + client.get('app.bsky.feed.getPosts', { 33 + signal: ctx.signal, 34 + params: { 35 + uris: [makeAtUri(did, uri.collection, uri.rkey)], 36 + }, 37 + }), 38 + ); 36 39 37 40 const post = data.posts[0]; 38 41
+11 -8
src/api/queries/profile-autocomplete.ts
··· 1 + import { ok } from '@atcute/client'; 1 2 import { createQuery, keepPreviousData } from '@mary/solid-query'; 2 3 3 4 import { useAgent } from '~/lib/states/agent'; ··· 10 11 query: () => string, 11 12 opts?: ProfileAutocompleteQueryOptions, 12 13 ) => { 13 - const { rpc } = useAgent(); 14 + const { client } = useAgent(); 14 15 15 16 return createQuery(() => { 16 17 const $query = query(); ··· 26 27 enabled: isEnabled, 27 28 placeholderData: isEnabled ? keepPreviousData : undefined, 28 29 async queryFn({ signal }) { 29 - const { data } = await rpc.get('app.bsky.actor.searchActorsTypeahead', { 30 - signal, 31 - params: { 32 - q: trimmed, 33 - limit: 10, 34 - }, 35 - }); 30 + const data = await ok( 31 + client.get('app.bsky.actor.searchActorsTypeahead', { 32 + signal, 33 + params: { 34 + q: trimmed, 35 + limit: 10, 36 + }, 37 + }), 38 + ); 36 39 37 40 return data; 38 41 },
+12 -9
src/api/queries/profile-feeds.ts
··· 1 + import { ok } from '@atcute/client'; 1 2 import type { At } from '@atcute/client/lexicons'; 2 3 import { type QueryFunctionContext as QC, createInfiniteQuery } from '@mary/solid-query'; 3 4 4 5 import { useAgent } from '~/lib/states/agent'; 5 6 6 7 export const createProfileFeedsQuery = (didOrHandle: () => At.Identifier) => { 7 - const { rpc } = useAgent(); 8 + const { client } = useAgent(); 8 9 9 10 return createInfiniteQuery(() => { 10 11 const $didOrHandle = didOrHandle(); ··· 12 13 return { 13 14 queryKey: ['profile-feeds', $didOrHandle], 14 15 async queryFn(ctx: QC<never, string | undefined>) { 15 - const { data } = await rpc.get('app.bsky.feed.getActorFeeds', { 16 - signal: ctx.signal, 17 - params: { 18 - actor: $didOrHandle, 19 - limit: 100, 20 - cursor: ctx.pageParam, 21 - }, 22 - }); 16 + const data = await ok( 17 + client.get('app.bsky.feed.getActorFeeds', { 18 + signal: ctx.signal, 19 + params: { 20 + actor: $didOrHandle, 21 + limit: 100, 22 + cursor: ctx.pageParam, 23 + }, 24 + }), 25 + ); 23 26 24 27 data.feeds.sort((a, b) => (b.likeCount ?? 0) - (a.likeCount ?? 0)); 25 28 return data;
+12 -9
src/api/queries/profile-followers.ts
··· 1 + import { ok } from '@atcute/client'; 1 2 import type { AppBskyActorDefs, At } from '@atcute/client/lexicons'; 2 3 import { type InfiniteData, type QueryFunctionContext as QC, createInfiniteQuery } from '@mary/solid-query'; 3 4 ··· 6 7 import { type ProfilesListWithSubjectPage, toProfilesListWithSubjectPage } from '../types/profile-response'; 7 8 8 9 export const createProfileFollowersQuery = (didOrHandle: () => At.Identifier) => { 9 - const { rpc } = useAgent(); 10 + const { client } = useAgent(); 10 11 11 12 return createInfiniteQuery((queryClient) => { 12 13 const $didOrHandle = didOrHandle(); ··· 14 15 return { 15 16 queryKey: ['profile-followers', $didOrHandle], 16 17 async queryFn(ctx: QC<never, string | undefined>): Promise<ProfilesListWithSubjectPage> { 17 - const { data } = await rpc.get('app.bsky.graph.getFollowers', { 18 - signal: ctx.signal, 19 - params: { 20 - actor: $didOrHandle, 21 - limit: 50, 22 - cursor: ctx.pageParam, 23 - }, 24 - }); 18 + const data = await ok( 19 + client.get('app.bsky.graph.getFollowers', { 20 + signal: ctx.signal, 21 + params: { 22 + actor: $didOrHandle, 23 + limit: 50, 24 + cursor: ctx.pageParam, 25 + }, 26 + }), 27 + ); 25 28 26 29 return toProfilesListWithSubjectPage(data, 'followers'); 27 30 },
+12 -9
src/api/queries/profile-following.ts
··· 1 + import { ok } from '@atcute/client'; 1 2 import type { AppBskyActorDefs, At } from '@atcute/client/lexicons'; 2 3 import { type InfiniteData, type QueryFunctionContext as QC, createInfiniteQuery } from '@mary/solid-query'; 3 4 ··· 6 7 import { type ProfilesListWithSubjectPage, toProfilesListWithSubjectPage } from '../types/profile-response'; 7 8 8 9 export const createProfileFollowingQuery = (didOrHandle: () => At.Identifier) => { 9 - const { rpc } = useAgent(); 10 + const { client } = useAgent(); 10 11 11 12 return createInfiniteQuery((queryClient) => { 12 13 const $didOrHandle = didOrHandle(); ··· 14 15 return { 15 16 queryKey: ['profile-following', $didOrHandle], 16 17 async queryFn(ctx: QC<never, string | undefined>): Promise<ProfilesListWithSubjectPage> { 17 - const { data } = await rpc.get('app.bsky.graph.getFollows', { 18 - signal: ctx.signal, 19 - params: { 20 - actor: $didOrHandle, 21 - limit: 50, 22 - cursor: ctx.pageParam, 23 - }, 24 - }); 18 + const data = await ok( 19 + client.get('app.bsky.graph.getFollows', { 20 + signal: ctx.signal, 21 + params: { 22 + actor: $didOrHandle, 23 + limit: 50, 24 + cursor: ctx.pageParam, 25 + }, 26 + }), 27 + ); 25 28 26 29 return toProfilesListWithSubjectPage(data, 'follows'); 27 30 },
+12 -9
src/api/queries/profile-known-followers.ts
··· 1 + import { ok } from '@atcute/client'; 1 2 import type { AppBskyActorDefs, At } from '@atcute/client/lexicons'; 2 3 import { type InfiniteData, type QueryFunctionContext as QC, createInfiniteQuery } from '@mary/solid-query'; 3 4 ··· 6 7 import { type ProfilesListWithSubjectPage, toProfilesListWithSubjectPage } from '../types/profile-response'; 7 8 8 9 export const createProfileKnownFollowersQuery = (didOrHandle: () => At.Identifier) => { 9 - const { rpc } = useAgent(); 10 + const { client } = useAgent(); 10 11 11 12 return createInfiniteQuery((queryClient) => { 12 13 const $didOrHandle = didOrHandle(); ··· 14 15 return { 15 16 queryKey: ['profile-known-followers', $didOrHandle], 16 17 async queryFn(ctx: QC<never, string | undefined>): Promise<ProfilesListWithSubjectPage> { 17 - const { data } = await rpc.get('app.bsky.graph.getKnownFollowers', { 18 - signal: ctx.signal, 19 - params: { 20 - actor: $didOrHandle, 21 - limit: 50, 22 - cursor: ctx.pageParam, 23 - }, 24 - }); 18 + const data = await ok( 19 + client.get('app.bsky.graph.getKnownFollowers', { 20 + signal: ctx.signal, 21 + params: { 22 + actor: $didOrHandle, 23 + limit: 50, 24 + cursor: ctx.pageParam, 25 + }, 26 + }), 27 + ); 25 28 26 29 return toProfilesListWithSubjectPage(data, 'followers'); 27 30 },
+12 -9
src/api/queries/profile-lists.ts
··· 1 + import { ok } from '@atcute/client'; 1 2 import type { At } from '@atcute/client/lexicons'; 2 3 import { type QueryFunctionContext as QC, createInfiniteQuery } from '@mary/solid-query'; 3 4 4 5 import { useAgent } from '~/lib/states/agent'; 5 6 6 7 export const createProfileListsQuery = (didOrHandle: () => At.Identifier) => { 7 - const { rpc } = useAgent(); 8 + const { client } = useAgent(); 8 9 9 10 const collator = new Intl.Collator('en-US'); 10 11 ··· 14 15 return { 15 16 queryKey: ['profile-lists', $didOrHandle], 16 17 async queryFn(ctx: QC<never, string | undefined>) { 17 - const { data } = await rpc.get('app.bsky.graph.getLists', { 18 - signal: ctx.signal, 19 - params: { 20 - actor: $didOrHandle, 21 - limit: 100, 22 - cursor: ctx.pageParam, 23 - }, 24 - }); 18 + const data = await ok( 19 + client.get('app.bsky.graph.getLists', { 20 + signal: ctx.signal, 21 + params: { 22 + actor: $didOrHandle, 23 + limit: 100, 24 + cursor: ctx.pageParam, 25 + }, 26 + }), 27 + ); 25 28 26 29 data.lists.sort((a, b) => collator.compare(a.name, b.name)); 27 30 return data;
+10 -7
src/api/queries/profile.ts
··· 1 1 import { modifyMutable, reconcile } from 'solid-js/store'; 2 2 3 + import { ok } from '@atcute/client'; 3 4 import type { AppBskyActorDefs, At } from '@atcute/client/lexicons'; 4 5 import { createQuery } from '@mary/solid-query'; 5 6 ··· 14 15 } 15 16 16 17 export const createProfileQuery = (didOrHandle: () => At.Identifier, opts: ProfileQueryOptions = {}) => { 17 - const { rpc } = useAgent(); 18 + const { client } = useAgent(); 18 19 const { currentAccount } = useSession(); 19 20 20 21 return createQuery((queryClient) => { ··· 25 26 staleTime: opts.staleTime, 26 27 gcTime: opts.gcTime, 27 28 async queryFn(ctx): Promise<AppBskyActorDefs.ProfileViewDetailed> { 28 - const { data } = await rpc.get('app.bsky.actor.getProfile', { 29 - signal: ctx.signal, 30 - params: { 31 - actor: $didOrHandle!, 32 - }, 33 - }); 29 + const data = await ok( 30 + client.get('app.bsky.actor.getProfile', { 31 + signal: ctx.signal, 32 + params: { 33 + actor: $didOrHandle!, 34 + }, 35 + }), 36 + ); 34 37 35 38 if (currentAccount !== undefined && currentAccount.did === data.did) { 36 39 // Unset `knownFollowers` as we don't need that on our own profile.
+12 -9
src/api/queries/search-feeds.ts
··· 1 + import { ok } from '@atcute/client'; 1 2 import type { AppBskyUnspeccedGetPopularFeedGenerators } from '@atcute/client/lexicons'; 2 3 import { type QueryFunctionContext as QC, createInfiniteQuery } from '@mary/solid-query'; 3 4 4 5 import { useAgent } from '~/lib/states/agent'; 5 6 6 7 export const createSearchFeedsQuery = (query: () => string) => { 7 - const { rpc } = useAgent(); 8 + const { client } = useAgent(); 8 9 9 10 return createInfiniteQuery(() => { 10 11 const q = query(); ··· 14 15 async queryFn( 15 16 ctx: QC<never, string | undefined>, 16 17 ): Promise<AppBskyUnspeccedGetPopularFeedGenerators.Output> { 17 - const { data } = await rpc.get('app.bsky.unspecced.getPopularFeedGenerators', { 18 - signal: ctx.signal, 19 - params: { 20 - query: q, 21 - limit: 50, 22 - cursor: ctx.pageParam, 23 - }, 24 - }); 18 + const data = await ok( 19 + client.get('app.bsky.unspecced.getPopularFeedGenerators', { 20 + signal: ctx.signal, 21 + params: { 22 + query: q, 23 + limit: 50, 24 + cursor: ctx.pageParam, 25 + }, 26 + }), 27 + ); 25 28 26 29 return data; 27 30 },
+12 -9
src/api/queries/search-profiles.ts
··· 1 + import { ok } from '@atcute/client'; 1 2 import { type QueryFunctionContext as QC, createInfiniteQuery } from '@mary/solid-query'; 2 3 3 4 import { useAgent } from '~/lib/states/agent'; ··· 5 6 import { type ProfilesListPage, toProfilesListPage } from '../types/profile-response'; 6 7 7 8 export const createSearchProfilesQuery = (query: () => string) => { 8 - const { rpc } = useAgent(); 9 + const { client } = useAgent(); 9 10 10 11 return createInfiniteQuery(() => { 11 12 const q = query(); ··· 13 14 return { 14 15 queryKey: ['search-profiles', q], 15 16 async queryFn(ctx: QC<never, string | undefined>): Promise<ProfilesListPage> { 16 - const { data } = await rpc.get('app.bsky.actor.searchActors', { 17 - signal: ctx.signal, 18 - params: { 19 - q: q, 20 - limit: 50, 21 - cursor: ctx.pageParam, 22 - }, 23 - }); 17 + const data = await ok( 18 + client.get('app.bsky.actor.searchActors', { 19 + signal: ctx.signal, 20 + params: { 21 + q: q, 22 + limit: 50, 23 + cursor: ctx.pageParam, 24 + }, 25 + }), 26 + ); 24 27 25 28 return toProfilesListPage(data, 'actors'); 26 29 },
+12 -9
src/api/queries/subject-likers.ts
··· 1 + import { ok } from '@atcute/client'; 1 2 import type { At } from '@atcute/client/lexicons'; 2 3 import type { QueryFunctionContext as QC } from '@mary/solid-query'; 3 4 import { createInfiniteQuery } from '@mary/solid-query'; ··· 7 8 import type { ProfilesListPage } from '../types/profile-response'; 8 9 9 10 export const createSubjectLikersQuery = (uri: () => At.ResourceUri) => { 10 - const { rpc } = useAgent(); 11 + const { client } = useAgent(); 11 12 12 13 return createInfiniteQuery(() => { 13 14 const $uri = uri(); ··· 16 17 queryKey: ['subject-likers', $uri], 17 18 structuralSharing: false, 18 19 async queryFn(ctx: QC<never, string | undefined>): Promise<ProfilesListPage> { 19 - const { data } = await rpc.get('app.bsky.feed.getLikes', { 20 - signal: ctx.signal, 21 - params: { 22 - uri: $uri, 23 - limit: 50, 24 - cursor: ctx.pageParam, 25 - }, 26 - }); 20 + const data = await ok( 21 + client.get('app.bsky.feed.getLikes', { 22 + signal: ctx.signal, 23 + params: { 24 + uri: $uri, 25 + limit: 50, 26 + cursor: ctx.pageParam, 27 + }, 28 + }), 29 + ); 27 30 28 31 return { 29 32 cursor: data.cursor,
+12 -9
src/api/queries/subject-reposters.ts
··· 1 + import { ok } from '@atcute/client'; 1 2 import type { At } from '@atcute/client/lexicons'; 2 3 import type { QueryFunctionContext as QC } from '@mary/solid-query'; 3 4 import { createInfiniteQuery } from '@mary/solid-query'; ··· 7 8 import { type ProfilesListPage, toProfilesListPage } from '../types/profile-response'; 8 9 9 10 export const createSubjectRepostersQuery = (uri: () => At.ResourceUri) => { 10 - const { rpc } = useAgent(); 11 + const { client } = useAgent(); 11 12 12 13 return createInfiniteQuery(() => { 13 14 const $uri = uri(); ··· 16 17 queryKey: ['subject-reposters', $uri], 17 18 structuralSharing: false, 18 19 async queryFn(ctx: QC<never, string | undefined>): Promise<ProfilesListPage> { 19 - const { data } = await rpc.get('app.bsky.feed.getRepostedBy', { 20 - signal: ctx.signal, 21 - params: { 22 - uri: $uri, 23 - limit: 50, 24 - cursor: ctx.pageParam, 25 - }, 26 - }); 20 + const data = await ok( 21 + client.get('app.bsky.feed.getRepostedBy', { 22 + signal: ctx.signal, 23 + params: { 24 + uri: $uri, 25 + limit: 50, 26 + cursor: ctx.pageParam, 27 + }, 28 + }), 29 + ); 27 30 28 31 return toProfilesListPage(data, 'repostedBy'); 29 32 },
+76 -68
src/api/queries/timeline.ts
··· 1 1 import { createEffect, createMemo, createRenderEffect, onCleanup, untrack } from 'solid-js'; 2 2 3 - import type { XRPC } from '@atcute/client'; 3 + import { type Client, ok } from '@atcute/client'; 4 4 import type { AppBskyFeedDefs, AppBskyFeedGetTimeline, AppBskyFeedPost, At } from '@atcute/client/lexicons'; 5 5 import { type FalsyValue, definite } from '@mary/array-fns'; 6 6 import { type InfiniteData, createInfiniteQuery, createQuery, useQueryClient } from '@mary/solid-query'; ··· 126 126 export const useTimelineQuery = (_params: () => TimelineParams) => { 127 127 const getParams = createMemo(() => _params(), EQUALS_DEQUAL); 128 128 129 - const { rpc } = useAgent(); 129 + const { client } = useAgent(); 130 130 const { currentAccount } = useSession(); 131 131 const queryClient = useQueryClient(); 132 132 ··· 195 195 postFilter = createLabelPostFilter(moderation); 196 196 } 197 197 198 - const timeline = await fetchPage(rpc, params, limit, cursor, ctx.signal); 198 + const timeline = await fetchPage(client, params, limit, cursor, ctx.signal); 199 199 200 200 const feed = timeline.feed; 201 201 const newCursor = timeline.cursor; ··· 238 238 // const offset = params.type !== 'profile' ? timelineData!.pages[0].pinAmount : 0; 239 239 const offset = timelineData!.pages[0].pinAmount; 240 240 241 - const timeline = await fetchPage(rpc, params, offset + 1, undefined, ctx.signal); 241 + const timeline = await fetchPage(client, params, offset + 1, undefined, ctx.signal); 242 242 const feed = timeline.feed; 243 243 244 244 return { hash: getTimelineHash(feed) }; ··· 308 308 309 309 //// Raw fetch 310 310 const fetchPage = async ( 311 - rpc: XRPC, 311 + client: Client, 312 312 params: TimelineParams, 313 313 limit: number, 314 314 cursor: string | undefined, ··· 317 317 const type = params.type; 318 318 319 319 if (type === 'following') { 320 - const response = await rpc.get('app.bsky.feed.getTimeline', { 321 - signal: signal, 322 - params: { 323 - algorithm: 'reverse-chronological', 324 - cursor: cursor, 325 - limit: limit, 326 - }, 327 - }); 320 + const data = await ok( 321 + client.get('app.bsky.feed.getTimeline', { 322 + signal: signal, 323 + params: { 324 + algorithm: 'reverse-chronological', 325 + cursor: cursor, 326 + limit: limit, 327 + }, 328 + }), 329 + ); 328 330 329 - return response.data; 331 + return data; 330 332 } else if (type === 'feed') { 331 - const response = await rpc.get('app.bsky.feed.getFeed', { 332 - signal: signal, 333 - headers: { 334 - 'accent-language': navigator.languages.join(','), 335 - }, 336 - params: { 337 - feed: params.uri, 338 - cursor: cursor, 339 - limit: limit, 340 - }, 341 - }); 342 - 343 - const data = response.data; 333 + const data = await ok( 334 + client.get('app.bsky.feed.getFeed', { 335 + signal: signal, 336 + headers: { 337 + 'accent-language': navigator.languages.join(','), 338 + }, 339 + params: { 340 + feed: params.uri, 341 + cursor: cursor, 342 + limit: limit, 343 + }, 344 + }), 345 + ); 344 346 345 347 return { 346 348 // Discover feed, wooo. ··· 348 350 feed: data.feed, 349 351 }; 350 352 } else if (type === 'list') { 351 - const response = await rpc.get('app.bsky.feed.getListFeed', { 352 - signal: signal, 353 - params: { 354 - list: params.uri, 355 - cursor: cursor, 356 - limit: limit, 357 - }, 358 - }); 359 - 360 - return response.data; 361 - } else if (type === 'profile') { 362 - if (params.tab === 'likes') { 363 - const response = await rpc.get('app.bsky.feed.getActorLikes', { 353 + const data = await ok( 354 + client.get('app.bsky.feed.getListFeed', { 364 355 signal: signal, 365 356 params: { 366 - actor: params.actor, 357 + list: params.uri, 367 358 cursor: cursor, 368 359 limit: limit, 369 360 }, 370 - }); 361 + }), 362 + ); 371 363 372 - return response.data; 364 + return data; 365 + } else if (type === 'profile') { 366 + if (params.tab === 'likes') { 367 + const data = await ok( 368 + client.get('app.bsky.feed.getActorLikes', { 369 + signal: signal, 370 + params: { 371 + actor: params.actor, 372 + cursor: cursor, 373 + limit: limit, 374 + }, 375 + }), 376 + ); 377 + 378 + return data; 373 379 } else { 374 - const response = await rpc.get('app.bsky.feed.getAuthorFeed', { 380 + const data = await ok( 381 + client.get('app.bsky.feed.getAuthorFeed', { 382 + signal: signal, 383 + params: { 384 + actor: params.actor, 385 + cursor: cursor, 386 + limit: limit, 387 + includePins: params.tab !== 'media', 388 + filter: 389 + params.tab === 'media' 390 + ? 'posts_with_media' 391 + : params.tab === 'replies' 392 + ? 'posts_with_replies' 393 + : 'posts_and_author_threads', 394 + }, 395 + }), 396 + ); 397 + 398 + return data; 399 + } 400 + } else if (type === 'search') { 401 + const data = await ok( 402 + client.get('app.bsky.feed.searchPosts', { 375 403 signal: signal, 376 404 params: { 377 - actor: params.actor, 405 + sort: params.sort, 406 + q: params.query, 378 407 cursor: cursor, 379 408 limit: limit, 380 - includePins: params.tab !== 'media', 381 - filter: 382 - params.tab === 'media' 383 - ? 'posts_with_media' 384 - : params.tab === 'replies' 385 - ? 'posts_with_replies' 386 - : 'posts_and_author_threads', 387 409 }, 388 - }); 389 - 390 - return response.data; 391 - } 392 - } else if (type === 'search') { 393 - const response = await rpc.get('app.bsky.feed.searchPosts', { 394 - signal: signal, 395 - params: { 396 - sort: params.sort, 397 - q: params.query, 398 - cursor: cursor, 399 - limit: limit, 400 - }, 401 - }); 402 - 403 - const data = response.data; 410 + }), 411 + ); 404 412 405 413 return { cursor: data.cursor, feed: data.posts.map((view) => ({ post: view })) }; 406 414 } else {
+9 -7
src/api/utils/did.ts
··· 1 - import type { XRPC } from '@atcute/client'; 1 + import { type Client, ok } from '@atcute/client'; 2 2 import type { At } from '@atcute/client/lexicons'; 3 3 4 4 import { isDid } from '../types/identity'; 5 5 6 - const getDid = async (rpc: XRPC, actor: At.Handle, signal?: AbortSignal) => { 6 + const getDid = async (client: Client, actor: At.Handle, signal?: AbortSignal) => { 7 7 let did: At.Did; 8 8 if (isDid(actor)) { 9 9 did = actor; 10 10 } else { 11 - const response = await rpc.get('com.atproto.identity.resolveHandle', { 12 - signal: signal, 13 - params: { handle: actor }, 14 - }); 11 + const data = await ok( 12 + client.get('com.atproto.identity.resolveHandle', { 13 + signal: signal, 14 + params: { handle: actor }, 15 + }), 16 + ); 15 17 16 - did = response.data.did; 18 + did = data.did; 17 19 } 18 20 19 21 return did;
+5 -5
src/api/utils/error.ts
··· 1 - import { XRPCError } from '@atcute/client'; 1 + import { ClientResponseError } from '@atcute/client'; 2 2 import { TokenRefreshError } from '@atcute/oauth-browser-client'; 3 3 4 - export const formatXRPCError = (err: XRPCError): string => { 5 - const name = err.kind; 4 + export const formatXRPCError = (err: ClientResponseError): string => { 5 + const name = err.error; 6 6 return (name ? name + ': ' : '') + err.description; 7 7 }; 8 8 ··· 11 11 return `Account session is no longer valid`; 12 12 } 13 13 14 - if (err instanceof XRPCError) { 15 - const kind = err.kind; 14 + if (err instanceof ClientResponseError) { 15 + const kind = err.error; 16 16 17 17 if (kind === 'invalid_token') { 18 18 return `Account session is no longer valid`;
+44 -23
src/api/utils/records.ts
··· 1 - import type { XRPC } from '@atcute/client'; 1 + import { type Client, ok } from '@atcute/client'; 2 2 import type { 3 3 At, 4 4 ComAtprotoRepoGetRecord, ··· 17 17 validate?: boolean; 18 18 } 19 19 20 - export const createRecord = async <K extends RecordType>(rpc: XRPC, options: CreateRecordOptions<K>) => { 21 - const { data } = await rpc.call('com.atproto.repo.createRecord', { data: options }); 20 + export const createRecord = async <K extends RecordType>(client: Client, options: CreateRecordOptions<K>) => { 21 + const data = await ok( 22 + client.post('com.atproto.repo.createRecord', { 23 + input: options, 24 + }), 25 + ); 22 26 23 27 return data; 24 28 }; ··· 33 37 validate?: boolean; 34 38 } 35 39 36 - export const putRecord = async <K extends RecordType>(rpc: XRPC, options: PutRecordOptions<K>) => { 37 - const { data } = await rpc.call('com.atproto.repo.putRecord', { data: options }); 40 + export const putRecord = async <K extends RecordType>(client: Client, options: PutRecordOptions<K>) => { 41 + const data = await ok( 42 + client.post('com.atproto.repo.putRecord', { 43 + input: options, 44 + }), 45 + ); 38 46 39 47 return data; 40 48 }; ··· 47 55 swapRecord?: string; 48 56 } 49 57 50 - export const deleteRecord = async <K extends RecordType>(rpc: XRPC, options: DeleteRecordOptions<K>) => { 51 - await rpc.call('com.atproto.repo.deleteRecord', { 52 - data: options, 53 - }); 58 + export const deleteRecord = async <K extends RecordType>(client: Client, options: DeleteRecordOptions<K>) => { 59 + await ok( 60 + client.post('com.atproto.repo.deleteRecord', { 61 + input: options, 62 + }), 63 + ); 54 64 }; 55 65 56 66 export interface GetRecordOptions<K extends RecordType> { 67 + signal?: AbortSignal; 57 68 repo: At.Did; 58 69 collection: K; 59 70 rkey: string; ··· 65 76 } 66 77 67 78 export const getRecord = async <K extends RecordType>( 68 - rpc: XRPC, 79 + client: Client, 69 80 options: GetRecordOptions<K>, 70 81 ): Promise<GetRecordOutput<Records[K]>> => { 71 - const { data } = await rpc.get('com.atproto.repo.getRecord', { 72 - params: options, 73 - }); 82 + const data = await ok( 83 + client.get('com.atproto.repo.getRecord', { 84 + signal: options.signal, 85 + params: { 86 + repo: options.repo, 87 + collection: options.collection, 88 + rkey: options.rkey, 89 + cid: options.cid, 90 + }, 91 + }), 92 + ); 74 93 75 94 return data as any; 76 95 }; ··· 89 108 } 90 109 91 110 export const listRecords = async <K extends RecordType>( 92 - rpc: XRPC, 111 + client: Client, 93 112 options: ListRecordsOptions<K>, 94 113 ): Promise<ListRecordsOutput<Records[K]>> => { 95 - const { data } = await rpc.get('com.atproto.repo.listRecords', { 96 - signal: options.signal, 97 - params: { 98 - repo: options.repo, 99 - collection: options.collection, 100 - limit: options.limit, 101 - cursor: options.cursor, 102 - }, 103 - }); 114 + const data = await ok( 115 + client.get('com.atproto.repo.listRecords', { 116 + signal: options.signal, 117 + params: { 118 + repo: options.repo, 119 + collection: options.collection, 120 + limit: options.limit, 121 + cursor: options.cursor, 122 + }, 123 + }), 124 + ); 104 125 105 126 return data as any; 106 127 };
+4
src/basa-env.d.ts
··· 57 57 58 58 interface Queries { 59 59 'x.basa.describeServer': { 60 + response: { json: XBasaDescribeServer.Output }; 61 + /** @deprecated */ 60 62 output: XBasaDescribeServer.Output; 61 63 }; 62 64 'x.basa.translate': { 63 65 params: XBasaTranslate.Params; 66 + response: { json: XBasaTranslate.Output }; 67 + /** @deprecated */ 64 68 output: XBasaTranslate.Output; 65 69 }; 66 70 }
+11 -8
src/components/composer/composer-input.tsx
··· 12 12 createSignal, 13 13 } from 'solid-js'; 14 14 15 + import { ok } from '@atcute/client'; 15 16 import type { AppBskyActorDefs } from '@atcute/client/lexicons'; 16 17 17 18 import { safeUrlParse } from '~/api/utils/strings'; ··· 62 63 const onChange = props.onChange; 63 64 const onSubmit = props.onSubmit; 64 65 65 - const { rpc } = useAgent(); 66 + const { client } = useAgent(); 66 67 67 68 const [inputCursor, setInputCursor] = createSignal<number>(); 68 69 const [menuSelection, setMenuSelection] = createSignal<number>(); ··· 135 136 const MATCH_LIMIT = 5; 136 137 137 138 if (type === Suggestion.MENTION) { 138 - const response = await rpc.get('app.bsky.actor.searchActorsTypeahead', { 139 - params: { 140 - q: match.query, 141 - limit: MATCH_LIMIT, 142 - }, 143 - }); 139 + const data = await ok( 140 + client.get('app.bsky.actor.searchActorsTypeahead', { 141 + params: { 142 + q: match.query, 143 + limit: MATCH_LIMIT, 144 + }, 145 + }), 146 + ); 144 147 145 - return response.data.actors.map((item) => ({ type: Suggestion.MENTION, data: item })); 148 + return data.actors.map((item) => ({ type: Suggestion.MENTION, data: item })); 146 149 } 147 150 148 151 assert(false, `expected match`);
+67 -51
src/components/composer/lib/api.ts
··· 1 1 import { nanoid } from 'nanoid/non-secure'; 2 2 3 - import { XRPC, XRPCError, simpleFetchHandler } from '@atcute/client'; 3 + import { Client, ClientResponseError, ok, simpleFetchHandler } from '@atcute/client'; 4 4 import type { 5 5 AppBskyEmbedImages, 6 6 AppBskyEmbedRecord, ··· 57 57 let cidPromise: Promise<typeof import('./cid')>; 58 58 59 59 export const publish = async ({ agent, queryClient, state, onLog: log }: PublishOptions) => { 60 - const rpc = agent.rpc; 60 + const client = agent.client; 61 61 const did = agent.did!; 62 62 63 63 const now = new Date(); ··· 80 80 if (isDid(uri.repo)) { 81 81 did = uri.repo; 82 82 } else { 83 - did = await resolveHandle(rpc, uri.repo, ctx.signal); 83 + did = await resolveHandle(client, uri.repo, ctx.signal); 84 84 } 85 85 86 - const { data } = await rpc.get('app.bsky.feed.getPosts', { 87 - signal: ctx.signal, 88 - params: { 89 - uris: [makeAtUri(did, uri.collection, uri.rkey)], 90 - }, 91 - }); 86 + const data = await ok( 87 + client.get('app.bsky.feed.getPosts', { 88 + signal: ctx.signal, 89 + params: { 90 + uris: [makeAtUri(did, uri.collection, uri.rkey)], 91 + }, 92 + }), 93 + ); 92 94 93 95 const post = data.posts[0]; 94 96 ··· 226 228 227 229 log?.(`Posting`); 228 230 229 - await rpc.call('com.atproto.repo.applyWrites', { 230 - data: { 231 - repo: did, 232 - writes: writes, 233 - }, 234 - }); 231 + await ok( 232 + client.post('com.atproto.repo.applyWrites', { 233 + input: { 234 + repo: did, 235 + writes: writes, 236 + }, 237 + }), 238 + ); 235 239 236 240 if (state.redraftUri) { 237 241 updatePostShadow(queryClient, state.redraftUri, { deleted: true }); ··· 298 302 299 303 switch (source.type) { 300 304 case 'local': { 301 - const uploaded = await uploadBlob(rpc, source.blob); 305 + const uploaded = await uploadBlob(client, source.blob); 302 306 303 307 return { 304 308 image: uploaded, ··· 339 343 340 344 const blob = source.blob; 341 345 342 - const videoRpc = new XRPC({ handler: simpleFetchHandler({ service: 'https://video.bsky.app' }) }); 346 + const videoClient = new Client({ 347 + handler: simpleFetchHandler({ service: 'https://video.bsky.app' }), 348 + }); 343 349 344 350 // Get upload limit status 345 351 { ··· 360 366 // GET https://porcini.us-east.host.bsky.network/xrpc/chat.bsky.convo.getLog 361 367 // atproto-proxy: did:web:api.bsky.chat#bsky_chat 362 368 // 363 - const { data: tokenData } = await rpc.get('com.atproto.server.getServiceAuth', { 364 - params: { 365 - aud: 'did:web:video.bsky.app', 366 - lxm: 'app.bsky.video.getUploadLimits', 367 - }, 368 - }); 369 + const tokenData = await ok( 370 + client.get('com.atproto.server.getServiceAuth', { 371 + params: { 372 + aud: 'did:web:video.bsky.app', 373 + lxm: 'app.bsky.video.getUploadLimits', 374 + }, 375 + }), 376 + ); 369 377 370 - const { data } = await videoRpc.get('app.bsky.video.getUploadLimits', { 371 - headers: { 372 - authorization: `Bearer ${tokenData.token}`, 373 - }, 374 - }); 378 + const data = await ok( 379 + videoClient.get('app.bsky.video.getUploadLimits', { 380 + headers: { 381 + authorization: `Bearer ${tokenData.token}`, 382 + }, 383 + }), 384 + ); 375 385 376 386 if (!data.canUpload) { 377 387 let message = data.message || `You've reached the limit on video uploads`; ··· 408 418 409 419 // Create an access token *to the PDS*, allowing the video service to 410 420 // upload the final blobs to our repository on our behalf. 411 - const { data: tokenData } = await rpc.get('com.atproto.server.getServiceAuth', { 412 - params: { 413 - // `did:web:porcini.us-east.host.bsky.network` 414 - aud: `did:web:${new URL(session.info.aud).host}`, 415 - lxm: 'com.atproto.repo.uploadBlob', 416 - exp: Date.now() / 1000 + 60 * 30, // 30 minutes 417 - }, 418 - }); 421 + const tokenData = await ok( 422 + client.get('com.atproto.server.getServiceAuth', { 423 + params: { 424 + // `did:web:porcini.us-east.host.bsky.network` 425 + aud: `did:web:${new URL(session.info.aud).host}`, 426 + lxm: 'com.atproto.repo.uploadBlob', 427 + exp: Date.now() / 1000 + 60 * 30, // 30 minutes 428 + }, 429 + }), 430 + ); 419 431 420 432 jobId = await new Promise((resolve, reject) => { 421 433 const xhr = new XMLHttpRequest(); ··· 475 487 let status: AppBskyVideoDefs.JobStatus; 476 488 477 489 try { 478 - const { data } = await videoRpc.get('app.bsky.video.getJobStatus', { 479 - params: { 480 - jobId: jobId, 481 - }, 482 - }); 490 + const data = await ok( 491 + videoClient.get('app.bsky.video.getJobStatus', { 492 + params: { 493 + jobId: jobId, 494 + }, 495 + }), 496 + ); 483 497 484 498 status = data.jobStatus; 485 499 pollFailures = 0; ··· 546 560 547 561 log?.(`Uploading GIF thumbnail`); 548 562 const compressed = await compressPostImage(gifBlob); 549 - const blob = await uploadBlob(rpc, compressed.blob); 563 + const blob = await uploadBlob(client, compressed.blob); 550 564 551 565 thumbBlob = blob; 552 566 } ··· 584 598 log?.(`Uploading link thumbnail`); 585 599 586 600 const compressed = await compressPostImage(thumb); 587 - const blob = await uploadBlob(rpc, compressed.blob); 601 + const blob = await uploadBlob(client, compressed.blob); 588 602 589 603 thumbBlob = blob; 590 604 } ··· 679 693 } 680 694 681 695 try { 682 - const response = await rpc.get('com.atproto.identity.resolveHandle', { 683 - params: { 684 - handle: handle, 685 - }, 686 - }); 696 + const data = await ok( 697 + client.get('com.atproto.identity.resolveHandle', { 698 + params: { 699 + handle: handle, 700 + }, 701 + }), 702 + ); 687 703 688 - const did = response.data.did; 704 + const did = data.did; 689 705 690 706 if (!hasSilent) { 691 707 facets.push({ ··· 699 715 }); 700 716 } 701 717 } catch (err) { 702 - if (err instanceof XRPCError && err.kind === 'InvalidRequest') { 718 + if (err instanceof ClientResponseError && err.error === 'InvalidRequest') { 703 719 throw new InvalidHandleError(handle); 704 720 } 705 721 ··· 711 727 features: [{ $type: 'app.bsky.richtext.facet#tag', tag: token.name }], 712 728 }); 713 729 } else if (type === 'emote') { 714 - const { value } = await getRecord(rpc, { 730 + const { value } = await getRecord(client, { 715 731 repo: did, 716 732 collection: 'blue.moji.collection.item', 717 733 rkey: token.name,
+4 -2
src/components/error-view.tsx
··· 1 1 import { Match, Switch } from 'solid-js'; 2 2 3 - import { XRPCError } from '@atcute/client'; 3 + import { ClientResponseError } from '@atcute/client'; 4 4 import type { AppBskyActorDefs } from '@atcute/client/lexicons'; 5 5 import { TokenRefreshError } from '@atcute/oauth-browser-client'; 6 6 import { useQueryClient } from '@mary/solid-query'; ··· 65 65 export default ErrorView; 66 66 67 67 const isInvalidTokenError = (err: unknown): boolean => { 68 - return err instanceof TokenRefreshError || (err instanceof XRPCError && err.kind === 'invalid_token'); 68 + return ( 69 + err instanceof TokenRefreshError || (err instanceof ClientResponseError && err.error === 'invalid_token') 70 + ); 69 71 };
+4 -4
src/components/moderation/block-account-prompt.tsx
··· 56 56 const { close } = useModalContext(); 57 57 58 58 const { currentAccount } = useSession(); 59 - const { rpc } = useAgent(); 59 + const { client } = useAgent(); 60 60 61 61 const mutation = createMutation((queryClient) => ({ 62 62 async mutationFn() { 63 - return await createRecord(rpc, { 63 + return await createRecord(client, { 64 64 repo: currentAccount!.did, 65 65 collection: 'app.bsky.graph.block', 66 66 record: { ··· 128 128 const UnblockPrompt = (props: BlockAccountPromptInnerProps) => { 129 129 const { close } = useModalContext(); 130 130 131 - const { rpc } = useAgent(); 131 + const { client } = useAgent(); 132 132 133 133 const mutation = createMutation((queryClient) => ({ 134 134 async mutationFn() { 135 135 const { repo, rkey } = parseCanonicalResourceUri(props.shadow.blockUri!); 136 136 137 - return await deleteRecord(rpc, { 137 + return await deleteRecord(client, { 138 138 repo: repo as At.Did, 139 139 collection: 'app.bsky.graph.block', 140 140 rkey: rkey,
+19 -12
src/components/moderation/mute-account-prompt.tsx
··· 1 1 import { Match, Switch, onMount } from 'solid-js'; 2 2 3 + import { ok } from '@atcute/client'; 3 4 import type { AppBskyActorDefs } from '@atcute/client/lexicons'; 4 5 import { createMutation } from '@mary/solid-query'; 5 6 ··· 49 50 const MutePrompt = ({ profile }: MuteAccountPromptProps) => { 50 51 const { close } = useModalContext(); 51 52 52 - const { rpc } = useAgent(); 53 + const { client } = useAgent(); 53 54 54 55 const mutation = createMutation((queryClient) => ({ 55 56 async mutationFn() { 56 - await rpc.call('app.bsky.graph.muteActor', { 57 - data: { 58 - actor: profile.did, 59 - }, 60 - }); 57 + await ok( 58 + client.post('app.bsky.graph.muteActor', { 59 + as: null, 60 + input: { 61 + actor: profile.did, 62 + }, 63 + }), 64 + ); 61 65 }, 62 66 onSuccess() { 63 67 close(); ··· 115 119 const UnmutePrompt = ({ profile }: MuteAccountPromptProps) => { 116 120 const { close } = useModalContext(); 117 121 118 - const { rpc } = useAgent(); 122 + const { client } = useAgent(); 119 123 120 124 const mutation = createMutation((queryClient) => ({ 121 125 async mutationFn() { 122 - await rpc.call('app.bsky.graph.unmuteActor', { 123 - data: { 124 - actor: profile.did, 125 - }, 126 - }); 126 + await ok( 127 + client.post('app.bsky.graph.unmuteActor', { 128 + as: null, 129 + input: { 130 + actor: profile.did, 131 + }, 132 + }), 133 + ); 127 134 }, 128 135 onSuccess() { 129 136 close();
+7 -7
src/components/profiles/edit-profile-dialog.tsx
··· 1 1 import { Show, createMemo, createSignal } from 'solid-js'; 2 2 3 - import { XRPCError } from '@atcute/client'; 3 + import { ClientResponseError } from '@atcute/client'; 4 4 import type { AppBskyActorDefs, At } from '@atcute/client/lexicons'; 5 5 import { createMutation } from '@mary/solid-query'; 6 6 ··· 34 34 const EditProfileDialog = ({ profile }: EditProfileDialogProps) => { 35 35 const { close } = useModalContext(); 36 36 37 - const { rpc } = useAgent(); 37 + const { client } = useAgent(); 38 38 const { currentAccount } = useSession(); 39 39 40 40 const snapshot = { ··· 78 78 let bannerPromise: Promise<At.Blob<any>> | undefined; 79 79 80 80 if ($avatar instanceof Blob) { 81 - avatarPromise = compressProfileImage($avatar, 1000, 1000).then((res) => uploadBlob(rpc, res.blob)); 81 + avatarPromise = compressProfileImage($avatar, 1000, 1000).then((res) => uploadBlob(client, res.blob)); 82 82 } 83 83 if ($banner instanceof Blob) { 84 - bannerPromise = compressProfileImage($banner, 3000, 1000).then((res) => uploadBlob(rpc, res.blob)); 84 + bannerPromise = compressProfileImage($banner, 3000, 1000).then((res) => uploadBlob(client, res.blob)); 85 85 } 86 86 87 87 let retriesRemaining = 3; 88 88 while (true) { 89 - const existing = await getRecord(rpc, { 89 + const existing = await getRecord(client, { 90 90 repo, 91 91 collection: 'app.bsky.actor.profile', 92 92 rkey: 'self', ··· 114 114 } 115 115 116 116 try { 117 - await putRecord(rpc, { 117 + await putRecord(client, { 118 118 repo, 119 119 collection: 'app.bsky.actor.profile', 120 120 rkey: 'self', ··· 122 122 swapRecord: existing?.cid ?? null, 123 123 }); 124 124 } catch (err) { 125 - if (err instanceof XRPCError && err.kind === 'InvalidSwapError') { 125 + if (err instanceof ClientResponseError && err.error === 'InvalidSwapError') { 126 126 if (retriesRemaining--) { 127 127 continue; 128 128 }
+11 -8
src/components/search/suggestions/from-actor-autocompletion-view.tsx
··· 1 1 import { For, Show, createMemo } from 'solid-js'; 2 2 3 + import { ok } from '@atcute/client'; 3 4 import { createQuery, keepPreviousData } from '@mary/solid-query'; 4 5 5 6 import { createProfileQuery } from '~/api/queries/profile'; ··· 17 18 }) => { 18 19 const { currentAccount } = useSession(); 19 20 20 - const { rpc } = useAgent(); 21 + const { client } = useAgent(); 21 22 const isFocused = useIsFocused(); 22 23 23 24 const match = createMemo(() => { ··· 32 33 enabled: $match !== '' && isFocused(), 33 34 placeholderData: keepPreviousData, 34 35 async queryFn({ signal }) { 35 - const { data } = await rpc.get('app.bsky.actor.searchActorsTypeahead', { 36 - signal, 37 - params: { 38 - q: $match, 39 - limit: 10, 40 - }, 41 - }); 36 + const data = await ok( 37 + client.get('app.bsky.actor.searchActorsTypeahead', { 38 + signal, 39 + params: { 40 + q: $match, 41 + limit: 10, 42 + }, 43 + }), 44 + ); 42 45 43 46 return data; 44 47 },
+10 -7
src/components/settings/app-passwords/add-app-password-prompt.tsx
··· 1 1 import { Match, Switch, createSignal } from 'solid-js'; 2 2 3 + import { ok } from '@atcute/client'; 3 4 import { createMutation } from '@mary/solid-query'; 4 5 5 6 import { autofocusNode, modelChecked, modelText } from '~/lib/input-refs'; ··· 13 14 export interface AddAppPasswordPromptProps {} 14 15 15 16 const AddAppPasswordPrompt = ({}: AddAppPasswordPromptProps) => { 16 - const { rpc } = useAgent(); 17 + const { client } = useAgent(); 17 18 18 19 const [name, setName] = createSignal(''); 19 20 const [privileged, setPrivileged] = createSignal(false); ··· 22 23 const mutation = createMutation((queryClient) => { 23 24 return { 24 25 async mutationFn() { 25 - const { data } = await rpc.call('com.atproto.server.createAppPassword', { 26 - data: { 27 - name: name().replace(/^\s+|\s+$|(?<=\s)\s+/g, ''), 28 - privileged: privileged(), 29 - }, 30 - }); 26 + const data = await ok( 27 + client.post('com.atproto.server.createAppPassword', { 28 + input: { 29 + name: name().replace(/^\s+|\s+$|(?<=\s)\s+/g, ''), 30 + privileged: privileged(), 31 + }, 32 + }), 33 + ); 31 34 32 35 return data; 33 36 },
+5 -5
src/components/settings/bluemoji/add-emote-prompt.tsx
··· 42 42 const AddEmotePrompt = ({ blob, onAdd }: AddEmotePromptProps) => { 43 43 const { close } = useModalContext(); 44 44 45 - const { rpc } = useAgent(); 45 + const { client } = useAgent(); 46 46 const { currentAccount } = useSession(); 47 47 48 48 const blobUrl = URL.createObjectURL(blob); ··· 71 71 72 72 const { png_128, webp_128 } = await getCompressedEmotes(blob, cover() ? 'cover' : 'contain'); 73 73 74 - const orig_prom = uploadBlob(rpc, blob); 75 - const png_prom = png_128 !== blob ? uploadBlob(rpc, png_128) : orig_prom; 76 - const webp_prom = webp_128 !== blob ? uploadBlob(rpc, webp_128) : orig_prom; 74 + const orig_prom = uploadBlob(client, blob); 75 + const png_prom = png_128 !== blob ? uploadBlob(client, png_128) : orig_prom; 76 + const webp_prom = webp_128 !== blob ? uploadBlob(client, webp_128) : orig_prom; 77 77 78 78 const [orig_blob, png_blob, webp_blob] = await Promise.all([orig_prom, png_prom, webp_prom]); 79 79 80 - await createRecord(rpc, { 80 + await createRecord(client, { 81 81 repo: currentAccount!.did, 82 82 collection: 'blue.moji.collection.item', 83 83 rkey: $name,
+3 -3
src/components/settings/content-translation/add-basa-instance-prompt.tsx
··· 1 1 import { createMemo, createSignal } from 'solid-js'; 2 2 3 - import { XRPC, simpleFetchHandler } from '@atcute/client'; 3 + import { Client, ok, simpleFetchHandler } from '@atcute/client'; 4 4 import { createMutation } from '@mary/solid-query'; 5 5 6 6 import { formatQueryError } from '~/api/utils/error'; ··· 47 47 48 48 const mutation = createMutation(() => ({ 49 49 async mutationFn({ url }: { url: URL }) { 50 - const rpc = new XRPC({ handler: simpleFetchHandler({ service: url }) }); 51 - await rpc.get('x.basa.describeServer', {}); 50 + const client = new Client({ handler: simpleFetchHandler({ service: url }) }); 51 + await ok(client.get('x.basa.describeServer')); 52 52 }, 53 53 onSuccess(_data, { url }) { 54 54 const href = url.toString();
+12 -12
src/components/threads/post-translation.tsx
··· 1 1 import { Match, Switch, createMemo, createSignal } from 'solid-js'; 2 2 3 - import { XRPC, simpleFetchHandler } from '@atcute/client'; 3 + import { Client, ok, simpleFetchHandler } from '@atcute/client'; 4 4 import { sampleOne } from '@mary/array-fns'; 5 5 import { createQuery } from '@mary/solid-query'; 6 6 ··· 66 66 targetLang = found; 67 67 } 68 68 69 - console.log(targetLang); 70 - 71 - const rpc = new XRPC({ handler: simpleFetchHandler({ service: $instanceUrl! }) }); 72 - const { data } = await rpc.get('x.basa.translate', { 73 - params: { 74 - engine: 'google', 75 - text: $text, 76 - from: $source, 77 - to: targetLang, 78 - }, 79 - }); 69 + const client = new Client({ handler: simpleFetchHandler({ service: $instanceUrl! }) }); 70 + const data = await ok( 71 + client.get('x.basa.translate', { 72 + params: { 73 + engine: 'google', 74 + text: $text, 75 + from: $source, 76 + to: targetLang, 77 + }, 78 + }), 79 + ); 80 80 81 81 return data; 82 82 },
+39 -38
src/components/timeline/delete-post-prompt.tsx
··· 1 - import { XRPCError } from '@atcute/client'; 1 + import { ClientResponseError, ok } from '@atcute/client'; 2 2 import type { AppBskyFeedDefs, AppBskyFeedPost } from '@atcute/client/lexicons'; 3 3 import { useQueryClient } from '@mary/solid-query'; 4 4 ··· 19 19 20 20 const DeletePostPrompt = ({ post, onPostDelete }: DeletePostPromptProps) => { 21 21 const { currentAccount } = useSession(); 22 - const { rpc } = useAgent(); 22 + const { client } = useAgent(); 23 23 24 24 const queryClient = useQueryClient(); 25 25 26 - const onDelete = () => { 26 + const onDelete = async () => { 27 27 const uri = parseCanonicalResourceUri(post.uri); 28 28 29 - const promise = rpc.call('com.atproto.repo.applyWrites', { 30 - data: { 29 + const write = await client.post('com.atproto.repo.applyWrites', { 30 + input: { 31 31 repo: currentAccount!.did, 32 32 writes: [ 33 33 { ··· 39 39 }, 40 40 }); 41 41 42 - promise.then( 43 - () => { 44 - updatePostShadow(queryClient, post.uri, { deleted: true }); 45 - onPostDelete?.(); 46 - }, 47 - async (err) => { 48 - if (err instanceof XRPCError && err.kind === 'InternalServerError') { 49 - await rpc.call('com.atproto.repo.putRecord', { 50 - data: { 51 - repo: currentAccount!.did, 52 - collection: 'app.bsky.feed.post', 53 - rkey: uri.rkey, 54 - validate: false, 55 - record: { 56 - $type: 'app.bsky.feed.post', 57 - text: '', 58 - createdAt: '1970-01-01T00:00:00.000Z', 59 - } satisfies AppBskyFeedPost.Record, 60 - }, 61 - }); 42 + if (write.ok) { 43 + updatePostShadow(queryClient, post.uri, { deleted: true }); 44 + onPostDelete?.(); 45 + return; 46 + } 62 47 63 - await rpc.call('com.atproto.repo.deleteRecord', { 64 - data: { 65 - repo: currentAccount!.did, 66 - collection: 'app.bsky.feed.post', 67 - rkey: uri.rkey, 68 - }, 69 - }); 48 + if (write.data.error !== 'InternalServerError') { 49 + throw new ClientResponseError(write); 50 + } 70 51 71 - updatePostShadow(queryClient, post.uri, { deleted: true }); 72 - onPostDelete?.(); 73 - return; 74 - } 52 + await ok( 53 + client.post('com.atproto.repo.putRecord', { 54 + input: { 55 + repo: currentAccount!.did, 56 + collection: 'app.bsky.feed.post', 57 + rkey: uri.rkey, 58 + validate: false, 59 + record: { 60 + $type: 'app.bsky.feed.post', 61 + text: '', 62 + createdAt: '1970-01-01T00:00:00.000Z', 63 + } satisfies AppBskyFeedPost.Record, 64 + }, 65 + }), 66 + ); 75 67 76 - throw err; 77 - }, 68 + await ok( 69 + client.post('com.atproto.repo.deleteRecord', { 70 + input: { 71 + repo: currentAccount!.did, 72 + collection: 'app.bsky.feed.post', 73 + rkey: uri.rkey, 74 + }, 75 + }), 78 76 ); 77 + 78 + updatePostShadow(queryClient, post.uri, { deleted: true }); 79 + onPostDelete?.(); 79 80 }; 80 81 81 82 return (
+5 -5
src/components/timeline/pin-post-prompt.tsx
··· 1 1 import { Match, Show, Switch, batch } from 'solid-js'; 2 2 3 - import { XRPCError } from '@atcute/client'; 3 + import { ClientResponseError } from '@atcute/client'; 4 4 import type { AppBskyFeedDefs } from '@atcute/client/lexicons'; 5 5 import { createMutation } from '@mary/solid-query'; 6 6 ··· 23 23 24 24 const PinPostPrompt = ({ post }: PinPostPromptProps) => { 25 25 const { currentAccount } = useSession(); 26 - const { rpc } = useAgent(); 26 + const { client } = useAgent(); 27 27 28 28 const { close } = useModalContext(); 29 29 ··· 40 40 updatePostShadow(queryClient, post.uri, { pinned: next }); 41 41 42 42 while (true) { 43 - const existing = await getRecord(rpc, { 43 + const existing = await getRecord(client, { 44 44 repo, 45 45 collection: 'app.bsky.actor.profile', 46 46 rkey: 'self', ··· 63 63 record.pinnedPost = next ? { uri: post.uri, cid: post.cid } : undefined; 64 64 65 65 try { 66 - await putRecord(rpc, { 66 + await putRecord(client, { 67 67 repo, 68 68 collection: 'app.bsky.actor.profile', 69 69 rkey: 'self', ··· 71 71 swapRecord: existing?.cid ?? null, 72 72 }); 73 73 } catch (err) { 74 - if (err instanceof XRPCError && err.kind === 'InvalidSwapError') { 74 + if (err instanceof ClientResponseError && err.error === 'InvalidSwapError') { 75 75 if (retriesRemaining--) { 76 76 continue; 77 77 }
+4 -4
src/lib/states/agent.tsx
··· 1 1 import { type JSX, type ParentProps, createContext, createMemo, useContext } from 'solid-js'; 2 2 3 - import { XRPC, simpleFetchHandler } from '@atcute/client'; 3 + import { Client, simpleFetchHandler } from '@atcute/client'; 4 4 import type { At } from '@atcute/client/lexicons'; 5 5 import type { OAuthUserAgent } from '@atcute/oauth-browser-client'; 6 6 import { QueryClient, QueryClientProvider } from '@mary/solid-query'; ··· 15 15 16 16 export interface AgentContext { 17 17 did: At.Did | null; 18 - rpc: XRPC; 18 + client: Client; 19 19 handler: OAuthUserAgent | null; 20 20 persister: ReturnType<typeof createQueryPersister>; 21 21 } ··· 32 32 return { 33 33 did: currentAccount.did, 34 34 handler: currentAccount.agent ?? null, 35 - rpc: currentAccount.rpc, 35 + client: currentAccount.client, 36 36 persister: createQueryPersister({ name: `queryCache-${currentAccount.did}` }), 37 37 }; 38 38 } ··· 40 40 return { 41 41 did: null, 42 42 handler: null, 43 - rpc: new XRPC({ handler: simpleFetchHandler({ service: DEFAULT_APPVIEW_URL }) }), 43 + client: new Client({ handler: simpleFetchHandler({ service: DEFAULT_APPVIEW_URL }) }), 44 44 persister: createQueryPersister({ name: `queryCache-public` }), 45 45 }; 46 46 });
+7 -7
src/lib/states/session.tsx
··· 10 10 useContext, 11 11 } from 'solid-js'; 12 12 13 - import { type FetchHandler, type FetchHandlerObject, XRPC, XRPCError } from '@atcute/client'; 13 + import { Client, ClientResponseError, type FetchHandler, type FetchHandlerObject } from '@atcute/client'; 14 14 import type { At } from '@atcute/client/lexicons'; 15 15 import { OAuthUserAgent, deleteStoredSession, getSession } from '@atcute/oauth-browser-client'; 16 16 import { mapDefined } from '@mary/array-fns'; ··· 31 31 readonly data: AccountData; 32 32 readonly preferences: PerAccountPreferenceSchema; 33 33 34 - readonly rpc: XRPC; 34 + readonly client: Client; 35 35 readonly agent: OAuthUserAgent | undefined; 36 36 readonly _cleanup: () => void; 37 37 } ··· 62 62 const createAccountState = ( 63 63 did: At.Did, 64 64 session: OAuthUserAgent | undefined, 65 - rpc: XRPC, 65 + client: Client, 66 66 ): CurrentAccountState => { 67 67 return createRoot((cleanup): CurrentAccountState => { 68 68 const preferences = createAccountPreferences(did); ··· 80 80 }); 81 81 82 82 // A bit of a hack, but works right now. 83 - rpc.handle = attachLabelerHeaders(rpc.handle, labelers); 83 + client.handler = attachLabelerHeaders(client.handler, labelers); 84 84 85 85 createEffect(() => { 86 86 const signal = abortable(); ··· 129 129 return $data; 130 130 }, 131 131 132 - rpc: rpc, 132 + client: client, 133 133 agent: session, 134 134 _cleanup: cleanup, 135 135 }; ··· 181 181 // Dirty hack to account for the fact that we aren't dumping the user 182 182 // directly to the login modal. 183 183 handler = () => { 184 - throw new XRPCError(400, { kind: 'invalid_token' }); 184 + throw new ClientResponseError({ status: 400, data: { error: 'invalid_token' } }); 185 185 }; 186 186 } 187 187 188 - const rpc = new XRPC({ handler }); 188 + const rpc = new Client({ handler }); 189 189 190 190 signal.throwIfAborted(); 191 191
+13 -13
src/lib/states/singletons/moderation.ts
··· 1 1 import { createMemo } from 'solid-js'; 2 2 import { unwrap } from 'solid-js/store'; 3 3 4 - import type { AppBskyLabelerDefs, At } from '@atcute/client/lexicons'; 4 + import { ok } from '@atcute/client'; 5 + import type { At } from '@atcute/client/lexicons'; 5 6 import { mapDefined } from '@mary/array-fns'; 6 7 import { createBatchedFetch } from '@mary/batch-fetch'; 7 8 import { type QueryFunctionContext as QC, createQueries } from '@mary/solid-query'; ··· 14 15 import { useSession } from '../session'; 15 16 import { define } from '../singleton'; 16 17 17 - type Labeler = AppBskyLabelerDefs.LabelerViewDetailed; 18 - 19 18 const ModerationService = define('moderation', () => { 20 - const { rpc, persister } = useAgent(); 19 + const { client, persister } = useAgent(); 21 20 const { currentAccount } = useSession(); 22 21 23 22 const modPreferences = createMemo((): ModerationPreferences => { ··· 44 43 timeout: 1, 45 44 idFromResource: (labeler) => labeler.did, 46 45 async fetch(dids, signal) { 47 - const { data } = await rpc.get('app.bsky.labeler.getServices', { 48 - signal, 49 - params: { 50 - dids: dids, 51 - detailed: true, 52 - }, 53 - }); 54 - 55 - const views = data.views as Labeler[]; 46 + const data = await ok( 47 + client.get('app.bsky.labeler.getServices', { 48 + signal, 49 + params: { 50 + dids: dids, 51 + detailed: true, 52 + }, 53 + }), 54 + ); 56 55 56 + const views = data.views.filter((view) => view.$type === 'app.bsky.labeler.defs#labelerViewDetailed'); 57 57 return views.map((view) => interpretLabelerDefinition(view)); 58 58 }, 59 59 });
+2 -2
src/views/bluemoji-emotes.tsx
··· 45 45 openModal(() => <AddEmotePrompt blob={blob} onAdd={() => {}} />); 46 46 }; 47 47 48 - const { rpc } = useAgent(); 48 + const { client } = useAgent(); 49 49 const { currentAccount } = useSession(); 50 50 51 51 const query = createInfiniteQuery(() => ({ 52 52 queryKey: ['bluemoji', 'emotes'], 53 53 async queryFn(ctx) { 54 - return listRecords(rpc, { 54 + return listRecords(client, { 55 55 repo: currentAccount!.did, 56 56 collection: 'blue.moji.collection.item', 57 57 limit: 100,
+9 -7
src/views/oauth-callback.tsx
··· 1 1 import { Match, Switch, createResource } from 'solid-js'; 2 2 3 - import { XRPC } from '@atcute/client'; 3 + import { Client, ok } from '@atcute/client'; 4 4 import { 5 5 AuthorizationError, 6 6 OAuthResponseError, ··· 27 27 const did = session.info.sub; 28 28 29 29 const agent = new OAuthUserAgent(session); 30 - const rpc = new XRPC({ handler: agent }); 30 + const client = new Client({ handler: agent }); 31 31 32 - const { data: profile } = await rpc.get('app.bsky.actor.getProfile', { 33 - params: { 34 - actor: did, 35 - }, 36 - }); 32 + const profile = await ok( 33 + client.get('app.bsky.actor.getProfile', { 34 + params: { 35 + actor: did, 36 + }, 37 + }), 38 + ); 37 39 38 40 { 39 41 // Update UI preferences
+5 -5
src/views/post-thread.tsx
··· 1 1 import { For, Match, Switch, createEffect, createMemo, createSignal } from 'solid-js'; 2 2 3 - import { XRPCError } from '@atcute/client'; 3 + import { ClientResponseError } from '@atcute/client'; 4 4 import type { AppBskyFeedDefs, AppBskyFeedPost, At, Brand } from '@atcute/client/lexicons'; 5 5 import { useQueryClient } from '@mary/solid-query'; 6 6 ··· 78 78 79 79 <Switch> 80 80 <Match when={query.error} keyed> 81 - {(error) => { 82 - if (error instanceof XRPCError) { 83 - if (error.kind === 'NotFound') { 81 + {(err) => { 82 + if (err instanceof ClientResponseError) { 83 + if (err.error === 'NotFound') { 84 84 return ( 85 85 <div class="px-4 py-3"> 86 86 <div class="rounded-md border border-outline p-3"> ··· 91 91 } 92 92 } 93 93 94 - return <ErrorView error={error} onRetry={() => query.refetch()} />; 94 + return <ErrorView error={err} onRetry={() => query.refetch()} />; 95 95 }} 96 96 </Match> 97 97
+7 -7
src/views/profile.tsx
··· 1 1 import { Match, Show, Switch, createMemo } from 'solid-js'; 2 2 3 - import { XRPCError } from '@atcute/client'; 3 + import { ClientResponseError } from '@atcute/client'; 4 4 import type { AppBskyActorDefs, At } from '@atcute/client/lexicons'; 5 5 import { useQueryClient } from '@mary/solid-query'; 6 6 ··· 117 117 <Match when={profile.error} keyed> 118 118 {(err) => { 119 119 if ( 120 - err instanceof XRPCError && 121 - (err.kind === 'InvalidRequest' || 122 - err.kind === 'AccountTakedown' || 123 - err.kind === 'AccountDeactivated') 120 + err instanceof ClientResponseError && 121 + (err.error === 'InvalidRequest' || 122 + err.error === 'AccountTakedown' || 123 + err.error === 'AccountDeactivated') 124 124 ) { 125 125 const text = 126 - err.kind === 'AccountTakedown' 126 + err.error === 'AccountTakedown' 127 127 ? `This account is taken down` 128 - : err.kind === 'AccountDeactivated' 128 + : err.error === 'AccountDeactivated' 129 129 ? `This account has deactivated` 130 130 : `This account doesn't exist`; 131 131
+7 -6
src/views/settings-account.tsx
··· 1 + import { ok } from '@atcute/client'; 1 2 import { createQuery } from '@mary/solid-query'; 2 3 3 4 import { useTitle } from '~/lib/navigation/router'; ··· 7 8 import * as Page from '~/components/page'; 8 9 9 10 const AccountSettingsPage = () => { 10 - const { did, rpc, persister } = useAgent(); 11 + const { did, client, persister } = useAgent(); 11 12 12 13 const repo = createQuery(() => ({ 13 14 queryKey: ['describe-repo'], 14 15 persister: persister as any, 15 16 async queryFn() { 16 - const [repoResponse, serverResponse] = await Promise.all([ 17 - rpc.get('com.atproto.repo.describeRepo', { params: { repo: did! } }), 18 - rpc.handle('/xrpc/com.atproto.server.describeServer', {}), 17 + const [repo, server] = await Promise.all([ 18 + ok(client.get('com.atproto.repo.describeRepo', { params: { repo: did! } })), 19 + ok(client.get('com.atproto.server.describeServer')), 19 20 ]); 20 21 21 22 return { 22 - handle: repoResponse.data.handle, 23 - pds: new URL(serverResponse.url).host, 23 + handle: repo.handle, 24 + pds: server.did.replace(/^did:web:/, ''), 24 25 }; 25 26 }, 26 27 }));
+10 -6
src/views/settings-app-passwords.tsx
··· 1 1 import { For, Match, Show, Switch } from 'solid-js'; 2 2 3 + import { ok } from '@atcute/client'; 3 4 import type { ComAtprotoServerListAppPasswords } from '@atcute/client/lexicons'; 4 5 import { createMutation, createQuery } from '@mary/solid-query'; 5 6 ··· 22 23 import AddAppPasswordPrompt from '~/components/settings/app-passwords/add-app-password-prompt'; 23 24 24 25 const AppPasswordsSettingsPage = () => { 25 - const { rpc } = useAgent(); 26 + const { client } = useAgent(); 26 27 27 28 const passwords = createQuery(() => { 28 29 return { 29 30 queryKey: ['app-passwords'], 30 31 async queryFn() { 31 - const { data } = await rpc.get('com.atproto.server.listAppPasswords', {}); 32 + const data = await ok(client.get('com.atproto.server.listAppPasswords')); 32 33 33 34 return data.passwords; 34 35 }, ··· 105 106 } 106 107 107 108 const PasswordEntry = ({ item }: PasswordEntryProps) => { 108 - const { rpc } = useAgent(); 109 + const { client } = useAgent(); 109 110 110 111 const isPrivileged = item.privileged; 111 112 112 113 const mutation = createMutation((queryClient) => { 113 114 return { 114 115 async mutationFn() { 115 - await rpc.call('com.atproto.server.revokeAppPassword', { 116 - data: { name: item.name }, 117 - }); 116 + await ok( 117 + client.post('com.atproto.server.revokeAppPassword', { 118 + as: null, 119 + input: { name: item.name }, 120 + }), 121 + ); 118 122 }, 119 123 async onSuccess() { 120 124 await queryClient.invalidateQueries({ queryKey: ['app-passwords'] });