Fix optimistic insert

This commit is contained in:
Eric Bailey
2025-05-25 13:41:21 -05:00
parent 8f59675fc4
commit 4c4274ada3
4 changed files with 34 additions and 38 deletions
@@ -33,7 +33,7 @@ export function createCacheMutator({
}) { }) {
return { return {
insertReplies( insertReplies(
parent: AppBskyUnspeccedGetPostThreadV2.ThreadItem, parentUri: string,
replies: AppBskyUnspeccedGetPostThreadV2.ThreadItem[], replies: AppBskyUnspeccedGetPostThreadV2.ThreadItem[],
) { ) {
queryClient.setQueryData<AppBskyUnspeccedGetPostThreadV2.OutputSchema>( queryClient.setQueryData<AppBskyUnspeccedGetPostThreadV2.OutputSchema>(
@@ -51,16 +51,14 @@ export function createCacheMutator({
) )
) )
continue continue
if (!AppBskyUnspeccedGetPostThreadV2.isThreadItemPost(parent.value)) if (existingParent.uri !== parentUri) continue
continue
if (existingParent.uri !== parent.uri) continue
/* /*
* Update parent data * Update parent data
*/ */
existingParent.value.post = { existingParent.value.post = {
...existingParent.value.post, ...existingParent.value.post,
replyCount: parent.value.post.replyCount, replyCount: (existingParent.value.post.replyCount || 0) + 1,
} }
const opDid = getRootPostAtUri(existingParent.value.post)?.host const opDid = getRootPostAtUri(existingParent.value.post)?.host
+1 -6
View File
@@ -27,14 +27,9 @@ export interface ComposerOptsPostRef {
export type OnPostSuccessData = export type OnPostSuccessData =
| { | {
type: 'post' replyToUri?: string
posts: AppBskyUnspeccedGetPostThreadV2.ThreadItem[] posts: AppBskyUnspeccedGetPostThreadV2.ThreadItem[]
} }
| {
type: 'reply'
parent: AppBskyUnspeccedGetPostThreadV2.ThreadItem
replies: AppBskyUnspeccedGetPostThreadV2.ThreadItem[]
}
| undefined | undefined
export interface ComposerOpts { export interface ComposerOpts {
+25 -22
View File
@@ -45,6 +45,7 @@ import {type ImagePickerAsset} from 'expo-image-picker'
import { import {
AppBskyFeedDefs, AppBskyFeedDefs,
type AppBskyFeedGetPostThread, type AppBskyFeedGetPostThread,
AppBskyUnspeccedGetPostThreadV2,
type BskyAgent, type BskyAgent,
type RichText, type RichText,
} from '@atproto/api' } from '@atproto/api'
@@ -393,6 +394,7 @@ export const ComposePost = ({
let postUri: string | undefined let postUri: string | undefined
let postSuccessData: OnPostSuccessData let postSuccessData: OnPostSuccessData
try { try {
logger.info(`composer: posting...`)
postUri = ( postUri = (
await apilib.post(agent, queryClient, { await apilib.post(agent, queryClient, {
thread, thread,
@@ -401,47 +403,48 @@ export const ComposePost = ({
langs: toPostLanguages(langPrefs.postLanguage), langs: toPostLanguages(langPrefs.postLanguage),
}) })
).uris[0] ).uris[0]
/*
* Wait for app view to have received the post(s). If this fails, it's
* ok, because the post _was_ actually published above.
*/
try { try {
if (postUri) { if (postUri) {
const [maybeParent, maybeReply, ...posts] = await retry( logger.info(`composer: waiting for app view`)
const posts = await retry(
5, 5,
_e => true, _e => true,
async () => { async () => {
const res = await agent.app.bsky.unspecced.getPostThreadV2({ const res = await agent.app.bsky.unspecced.getPostThreadV2({
uri: postUri!, anchor: postUri!,
above: 1, above: false,
below: thread.posts.length - 1, below: thread.posts.length - 1,
nestedBranchingFactor: 1, branchingFactor: 1,
}) })
const parent = res.data.thread.at(0) if (res.data.thread.length !== thread.posts.length) {
if (!parent) { throw new Error(`composer: app view is not ready`)
throw new Error(`Not ready`)
} }
if (res.data.thread.length !== thread.posts.length + 1) { if (
throw new Error(`Not ready`) !res.data.thread.every(p =>
AppBskyUnspeccedGetPostThreadV2.isThreadItemPost(p.value),
)
) {
throw new Error(`composer: app view returned non-post items`)
} }
return res.data.thread return res.data.thread
}, },
1e3, 1e3,
) )
if (maybeReply && maybeReply.uri === postUri) {
postSuccessData = { postSuccessData = {
type: 'reply', replyToUri: replyTo?.uri,
parent: maybeParent, posts,
replies: [maybeReply, ...posts],
}
} else {
postSuccessData = {
type: 'post',
posts: [maybeParent, maybeReply, ...posts].filter(Boolean),
}
} }
} }
} catch (waitErr: any) { } catch (waitErr: any) {
logger.error(waitErr, { logger.info(`composer: waiting for app view failed`, {
message: `Waiting for app view failed`, safeMessage: waitErr,
}) })
// Keep going because the post *was* published.
} }
} catch (e: any) { } catch (e: any) {
logger.error(e, { logger.error(e, {
+4 -4
View File
@@ -151,10 +151,10 @@ export function Inner({uri}: {uri: string | undefined}) {
}) })
const optimisticOnPostReply = (data: OnPostSuccessData) => { const optimisticOnPostReply = (data: OnPostSuccessData) => {
if (data && data.type === 'reply') { if (data) {
const {parent, replies} = data const {replyToUri, posts} = data
if (parent && replies.length) { if (replyToUri && posts.length) {
insertReplies(parent, replies) insertReplies(replyToUri, posts)
} }
} }
} }