-
Notifications
You must be signed in to change notification settings - Fork 2k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
@atproto/api@next
integration
#7344
base: main
Are you sure you want to change the base?
Conversation
|
441cebc
to
d1906e3
Compare
0b5c7a7
to
73009ac
Compare
1841b06
to
1de44ec
Compare
1de44ec
to
0ff385c
Compare
0ff385c
to
d7a550f
Compare
Co-authored-by: Matthieu Sieben <[email protected]>
Co-authored-by: Matthieu Sieben <[email protected]>
Co-authored-by: Matthieu Sieben <[email protected]>
9e7ad48
to
be7c6cb
Compare
!AppBskyFeedPost.isRecord(post.record) || | ||
!AppBskyFeedPost.validateRecord(post.record).success | ||
) { | ||
if (!AppBskyFeedPost.isValidRecord(post.record)) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't think we need these validations here, but they were already in place and I wanted to double check if they're necessary. Left them for now, will revisit before this merges.
const root = threads | ||
.reverse() | ||
.find(uri => uri.includes(currentAccount.did)) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm not sure why TS got mad about this suddenly, but decided to do this instead of rabbit holing any further on this PR.
sender: ChatBskyActorDefs.ProfileViewBasic | undefined | ||
recipients: ChatBskyActorDefs.ProfileViewBasic[] |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Good example of how we were actually using the wrong types here, but it was never an issue because they were loosely equal until now (and they're essentially the same object).
sender: { | ||
$type: 'chat.bsky.convo.defs#messageViewSender', | ||
did: this.sender!.did, | ||
}, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Here's an example of passing the full ProfileView*
where only did
was needed that was previously possible with the old loose types. This was only used to construct "pending" messages and never written to the user's repo, but it's good to be precise.
return convo.filter( | ||
c => | ||
c.id !== | ||
(log as ChatBskyConvoDefs.LogCreateMessage).convoId, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is checked above, but within this nested closure TS loses it. This all runs synchronously, so I took a shortcut instead of doing the same checks again.
if ( | ||
(ChatBskyConvoDefs.isDeletedMessageView(log.message) || | ||
ChatBskyConvoDefs.isMessageView(log.message)) && | ||
(ChatBskyConvoDefs.isDeletedMessageView(convo.lastMessage) || | ||
ChatBskyConvoDefs.isMessageView(convo.lastMessage)) | ||
) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Just being overly cautious here
AppBskyFeedPost.isRecord(post.record) && | ||
AppBskyFeedPost.validateRecord(post.record).success | ||
) { | ||
if (AppBskyFeedPost.isRecord(post.record)) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Removed validation from critical path
@@ -21,7 +20,7 @@ export interface ComposerOptsPostRef { | |||
cid: string | |||
text: string | |||
author: AppBskyActorDefs.ProfileViewBasic | |||
embed?: AppBskyEmbedRecord.ViewRecord['embed'] | |||
embed?: AppBskyFeedDefs.PostView['embed'] |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This type was just entirely wrong. embed
does not exist on AppBskyEmbedRecord.ViewRecord
. All the usages, however, were correct 😅
@@ -0,0 +1,26 @@ | |||
export * as profile from '#/types/atproto/profile' |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
NIT: While dangerousIsType
is indeed "Atprotoy" (has to do with the way atproto allow "open" data), the profile
& starterPack
are not really part of "atproto".
Wouldn't it make more sense to name this something like bsky
or api
(contextualized by the app) ?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Oh yeah that's a solid idea 👍
This PR integrates bluesky-social/atproto#2999 into our frontend codebase. From that PR:
Those three updates are all covered here.
3
Swapping
isType
checks withisValidType
checks fixes the vast majority of edits in this PR. However, the latter runs full lexical validation of the object. While this is more correct, anything in the critical path obviously should not run validations for performance reasons.To that end, this PR introduces a
dangerousIsType
util, which is used like this:This util uses the same identity method
isProfileView
that we've been using, and additionally performs the type cast previously handled byisProfileView
itself. Hence why this method requires us to pass in the type we expect.Though we decided to use the term "dangerous", this isn't terribly dangerous. We should be able to trust our API responses to return only validated objects, see Slack convo.
Note
In cases where the validation check is not in the critical perf path, I did chose to use the more simple
isValid*
utils. There are a few such cases in this PR.1 & 2
This is the meatier change, and these two overlap a lot in practice.
As mentioned in the
atproto
repo PR, our old types didn't have the$type
property defined (see howProfileViewBasic
is defined here). This made understanding these types somewhat confusing, and prevented directly checking the$type
prop to disciminate these open unions e.g.if (view.$type === 'app.bsky.feed.post') { ...handle post type... }
. With this change, we can now do this.Worth noting, these open unions still require us to provide a trailing unknown type to accommodate future iteration. This means that we need to strictly discriminate this union to get proper types e.g. we need to check each case of the union, otherwise TypeScript will assert that the object is of type
$Typed<{ $type: string }>
.This mostly affects the frontend codebase when we're dealing with profile views (and to a lesser extend starter pack views) of which we have a few with varying levels of detail. Prior to this,
ProfileViewBasic
ProfileView
andProfileViewDetailed
(as well as the additionalChatBskyActorDefs.ProfileViewBasic
) were all loosely equal, and we unfortunately used them interchangeably in a number of places throughout this codebase.In many cases, this is by design.
UserAvatar
is used in many places, and only relies on props available on all profile view types.FollowButton
is another example. Many of our queries behave like this too.One option here would be to refactor all these cases so that these components only accept those props they need i.e. pass in
profile.did
only instead ofprofile
in its entirety. That would require much more work, so I didn't take that approach here. It may be a good idea to consider doing this in the future.Another would be to use omit the
$type
param entirely likeOmit<AppBskyFeedDefs.PostView, '$type'>
, but then we aren't even sure what types are coming through and we lose the ability to discriminate types.The option I decided to move forward with was creating an abstraction of these types called
AnyProfileView
, that is simply a union of all 4 profile types we use in the app. This single source of truth saves us having to create these unions in dozens of places.This util is intended to be used in these component API contracts. Within that, we can discriminate the union to determine what can actually be rendered. See an example of how I did this in the old
ProfileCard
.Note
When referencing a property typed as
AnyProfileView
, accessing common props likedid
shows no feedback. But accessingdescription
will emit a type error, since it's not available onProfileViewBasic
. So we're still forced to use the identity utils when checking for potentially missing values.Lastly, when constructing records manually on the frontend, like when creating reports, we now need to use the included
$Typed
util, which asserts that$type
is in fact defined. This made a few things a little more verbose, but safer in the long run. Example here.New concepts
This PR introduces the following concepts.
#/types/atproto
This is intended to be commonly used, and imported like below. I'll just list out the full APIs here:
Note: I aliased the type utils for convenience, but that's totally optional.
atp.post.parseEmbed
Complexity around embed rendering has plagued us for a while. With this new update, we can now do exhaustive union checking, which is great, but verbose. Instead importing
AppBsky*Defs
and identity functions and doing the ~13 type checks required (some nested) in the handful of places in the app where we render post embeds, this util aims to make this handling more ergonomic by reconstructing the lexicon's open union into a new discriminated union shape.You can see a full example here. Usage looks like this:
Unstable profile cache
We've been using a kinda hacky "profile cache" via
precacheProfile
for a while now that pre-fills profile data when clicking on profile links. I say hacky because we're using React Query's client cache, without ever actually using the query key in auseQuery
call, or ever referencing this data from the profile shadow.Example of how we've used this:
PreviewableUserAvatar
is used in multiple components, right. And those components may pass in any view type and thereby pass that view type intoPreviewableUserAvatar
ProfileCard
is one example of this.ProfileCard
can be used with multiple view types, though it’s mostlyProfileViewBasic
NotificationFeedItem
is another example. Here, the author view is actuallyProfileView
PreviewableUserAvatar
callsprecacheProfile
on press to provide data toplaceholderData
on the profile query so that we can render the profile page more quicklySo you see:
precacheProfile
has — for a long time — been handling varied profile view types. In fact, inresolve-uri.ts
we were casting it asProfileViewBasic
(source) and inprofile.ts
we were casting it asProfileViewDetailed
(source) even though the data returned is probably never aDetailed
view (haven’t verified this, just a hunch).So this PR introduces a new interface for "unstable profile cache" located in
#/state/queries/unstable-profile-cache
. See the file for full details, should be pretty clear.Misc
ProfileView
toProfileViewBasic
was sufficient to quiet errors, and was correct based on the data that was being passed around.<prop> in object
to sufficiently discriminate unions (example)