migrate the post pipeline write and reply lookup to the clients
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
+75
-84
@@ -1,22 +1,11 @@
|
|||||||
import {
|
import {type AtpAgent, ChatBskyGroupDefs} from '@atproto/api'
|
||||||
type $Typed,
|
|
||||||
type AppBskyEmbedExternal,
|
|
||||||
type AppBskyEmbedGallery,
|
|
||||||
type AppBskyEmbedImages,
|
|
||||||
type AppBskyEmbedRecord,
|
|
||||||
type AppBskyEmbedRecordWithMedia,
|
|
||||||
type AppBskyEmbedVideo,
|
|
||||||
AppBskyFeedPost,
|
|
||||||
type AtpAgent,
|
|
||||||
type BlobRef,
|
|
||||||
ChatBskyGroupDefs,
|
|
||||||
type ComAtprotoLabelDefs,
|
|
||||||
type ComAtprotoRepoApplyWrites,
|
|
||||||
type ComAtprotoRepoStrongRef,
|
|
||||||
} from '@atproto/api'
|
|
||||||
import {TID} from '@atproto/common-web'
|
import {TID} from '@atproto/common-web'
|
||||||
import {type Client} from '@atproto/lex'
|
import {type $Typed, type Client} from '@atproto/lex'
|
||||||
import {toDatetimeString} from '@atproto/syntax'
|
import {
|
||||||
|
type AtUriString,
|
||||||
|
toDatetimeString,
|
||||||
|
type UriString,
|
||||||
|
} from '@atproto/syntax'
|
||||||
import {RichText} from '@bsky.app/sdk/richtext'
|
import {RichText} from '@bsky.app/sdk/richtext'
|
||||||
import {t} from '@lingui/core/macro'
|
import {t} from '@lingui/core/macro'
|
||||||
import {type QueryClient} from '@tanstack/react-query'
|
import {type QueryClient} from '@tanstack/react-query'
|
||||||
@@ -39,11 +28,11 @@ import {
|
|||||||
type PostDraft,
|
type PostDraft,
|
||||||
type ThreadDraft,
|
type ThreadDraft,
|
||||||
} from '#/view/com/composer/state/composer'
|
} from '#/view/com/composer/state/composer'
|
||||||
import {type app} from '#/lexicons'
|
import {app, com} from '#/lexicons'
|
||||||
import * as bsky from '#/types/bsky'
|
import * as bsky from '#/types/bsky'
|
||||||
import {createGIFDescription} from '../gif-alt-text'
|
import {createGIFDescription} from '../gif-alt-text'
|
||||||
import {computeCid} from './computeCid'
|
import {computeCid} from './computeCid'
|
||||||
import {toLegacyBlobRef} from './legacy-blob'
|
import {fromLegacyBlobRef} from './legacy-blob'
|
||||||
import {uploadBlob} from './upload-blob'
|
import {uploadBlob} from './upload-blob'
|
||||||
|
|
||||||
export {uploadBlob}
|
export {uploadBlob}
|
||||||
@@ -54,17 +43,23 @@ interface PostOpts {
|
|||||||
onStateChange?: (state: string) => void
|
onStateChange?: (state: string) => void
|
||||||
langs?: string[]
|
langs?: string[]
|
||||||
/*
|
/*
|
||||||
* Facet/mention resolution is an appview job - it resolves handles through
|
* Facet/mention resolution and reply-root lookup are appview jobs - they
|
||||||
* the appview, and the public fallback keeps it working when logged out.
|
* resolve handles and read posts through the appview, and the public
|
||||||
|
* fallback keeps facet detection working when logged out.
|
||||||
*/
|
*/
|
||||||
appviewClient: Client
|
appviewClient: Client
|
||||||
/*
|
/*
|
||||||
* Record blobs (images, gallery items, link thumbnails, video captions)
|
* The repo write itself (applyWrites) plus every record blob (images,
|
||||||
* upload to the account's own PDS, never the appview.
|
* gallery items, link thumbnails, video captions) goes to the account's own
|
||||||
|
* PDS, never the appview.
|
||||||
*/
|
*/
|
||||||
pdsClient: Client
|
pdsClient: Client
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The `agent` is only still here for the link/gif resolvers in `resolve.ts`,
|
||||||
|
* which read through it; drop the parameter when those move to the clients.
|
||||||
|
*/
|
||||||
export async function post(
|
export async function post(
|
||||||
agent: AtpAgent,
|
agent: AtpAgent,
|
||||||
queryClient: QueryClient,
|
queryClient: QueryClient,
|
||||||
@@ -74,12 +69,12 @@ export async function post(
|
|||||||
opts.onStateChange?.(t`Processing...`)
|
opts.onStateChange?.(t`Processing...`)
|
||||||
|
|
||||||
let replyPromise:
|
let replyPromise:
|
||||||
| Promise<AppBskyFeedPost.Record['reply']>
|
| Promise<app.bsky.feed.post.Main['reply']>
|
||||||
| AppBskyFeedPost.Record['reply']
|
| app.bsky.feed.post.Main['reply']
|
||||||
| undefined
|
| undefined
|
||||||
if (opts.replyTo) {
|
if (opts.replyTo) {
|
||||||
// Not awaited to avoid waterfalls.
|
// Not awaited to avoid waterfalls.
|
||||||
replyPromise = resolveReply(agent, opts.replyTo)
|
replyPromise = resolveReply(opts.appviewClient, opts.replyTo)
|
||||||
}
|
}
|
||||||
|
|
||||||
// add top 3 languages from user preferences if langs is provided
|
// add top 3 languages from user preferences if langs is provided
|
||||||
@@ -88,8 +83,8 @@ export async function post(
|
|||||||
langs = opts.langs.slice(0, 3)
|
langs = opts.langs.slice(0, 3)
|
||||||
}
|
}
|
||||||
|
|
||||||
const did = agent.assertDid
|
const did = opts.pdsClient.assertDid
|
||||||
const writes: $Typed<ComAtprotoRepoApplyWrites.Create>[] = []
|
const writes: com.atproto.repo.applyWrites.$InputBody['writes'] = []
|
||||||
const uris: string[] = []
|
const uris: string[] = []
|
||||||
|
|
||||||
let now = new Date()
|
let now = new Date()
|
||||||
@@ -107,7 +102,7 @@ export async function post(
|
|||||||
draft,
|
draft,
|
||||||
opts.onStateChange,
|
opts.onStateChange,
|
||||||
)
|
)
|
||||||
let labels: $Typed<ComAtprotoLabelDefs.SelfLabels> | undefined
|
let labels: $Typed<com.atproto.label.defs.SelfLabels> | undefined
|
||||||
if (draft.labels.length) {
|
if (draft.labels.length) {
|
||||||
labels = {
|
labels = {
|
||||||
$type: 'com.atproto.label.defs#selfLabels',
|
$type: 'com.atproto.label.defs#selfLabels',
|
||||||
@@ -120,17 +115,17 @@ export async function post(
|
|||||||
now.setMilliseconds(now.getMilliseconds() + 1)
|
now.setMilliseconds(now.getMilliseconds() + 1)
|
||||||
tid = TID.next(tid)
|
tid = TID.next(tid)
|
||||||
const rkey = tid.toString()
|
const rkey = tid.toString()
|
||||||
const uri = `at://${did}/app.bsky.feed.post/${rkey}`
|
const uri = `at://${did}/app.bsky.feed.post/${rkey}` as AtUriString
|
||||||
uris.push(uri)
|
uris.push(uri)
|
||||||
|
|
||||||
const rt = await rtPromise
|
const rt = await rtPromise
|
||||||
const embed = await embedPromise
|
const embed = await embedPromise
|
||||||
const reply = await replyPromise
|
const reply = await replyPromise
|
||||||
const record: AppBskyFeedPost.Record = {
|
const record: app.bsky.feed.post.Main = {
|
||||||
// IMPORTANT: $type has to exist, CID is calculated with the `$type` field
|
// IMPORTANT: $type has to exist, CID is calculated with the `$type` field
|
||||||
// present and will produce the wrong CID if you omit it.
|
// present and will produce the wrong CID if you omit it.
|
||||||
$type: 'app.bsky.feed.post',
|
$type: 'app.bsky.feed.post',
|
||||||
createdAt: now.toISOString(),
|
createdAt: toDatetimeString(now),
|
||||||
text: rt.text,
|
text: rt.text,
|
||||||
facets: rt.facets,
|
facets: rt.facets,
|
||||||
reply,
|
reply,
|
||||||
@@ -169,7 +164,7 @@ export async function post(
|
|||||||
value: {
|
value: {
|
||||||
...thread.postgate,
|
...thread.postgate,
|
||||||
$type: 'app.bsky.feed.postgate',
|
$type: 'app.bsky.feed.postgate',
|
||||||
createdAt: now.toISOString(),
|
createdAt: toDatetimeString(now),
|
||||||
post: uri,
|
post: uri,
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
@@ -177,13 +172,7 @@ export async function post(
|
|||||||
|
|
||||||
// Prepare a ref to the current post for the next post in the thread.
|
// Prepare a ref to the current post for the next post in the thread.
|
||||||
const ref = {
|
const ref = {
|
||||||
/*
|
cid: await computeCid(record),
|
||||||
* `computeCid` is typed against the lex record. The pipeline still builds
|
|
||||||
* the legacy `AppBskyFeedPost.Record`; the two shapes are structurally
|
|
||||||
* equivalent for hashing, so bridge them until the pipeline itself moves
|
|
||||||
* to the lex record type.
|
|
||||||
*/
|
|
||||||
cid: await computeCid(record as unknown as app.bsky.feed.post.Main),
|
|
||||||
uri,
|
uri,
|
||||||
}
|
}
|
||||||
replyPromise = {
|
replyPromise = {
|
||||||
@@ -193,8 +182,8 @@ export async function post(
|
|||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
await agent.com.atproto.repo.applyWrites({
|
await opts.pdsClient.call(com.atproto.repo.applyWrites, {
|
||||||
repo: agent.assertDid,
|
repo: did,
|
||||||
writes: writes,
|
writes: writes,
|
||||||
validate: true,
|
validate: true,
|
||||||
})
|
})
|
||||||
@@ -235,9 +224,9 @@ export class ReplyDeletedError extends Error {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async function resolveReply(agent: AtpAgent, replyTo: string) {
|
async function resolveReply(appviewClient: Client, replyTo: string) {
|
||||||
const {data} = await agent.app.bsky.feed.getPosts({
|
const data = await appviewClient.call(app.bsky.feed.getPosts, {
|
||||||
uris: [replyTo],
|
uris: [replyTo as AtUriString],
|
||||||
})
|
})
|
||||||
const parentPost = data.posts[0]
|
const parentPost = data.posts[0]
|
||||||
if (!parentPost) {
|
if (!parentPost) {
|
||||||
@@ -248,14 +237,9 @@ async function resolveReply(agent: AtpAgent, replyTo: string) {
|
|||||||
uri: parentPost.uri,
|
uri: parentPost.uri,
|
||||||
cid: parentPost.cid,
|
cid: parentPost.cid,
|
||||||
}
|
}
|
||||||
let rootRef = parentRef
|
let rootRef: com.atproto.repo.strongRef.Main = parentRef
|
||||||
|
|
||||||
if (
|
if (bsky.isType(app.bsky.feed.post, parentPost.record)) {
|
||||||
bsky.dangerousIsType<AppBskyFeedPost.Record>(
|
|
||||||
parentPost.record,
|
|
||||||
AppBskyFeedPost.isRecord,
|
|
||||||
)
|
|
||||||
) {
|
|
||||||
if (parentPost.record.reply) {
|
if (parentPost.record.reply) {
|
||||||
rootRef = parentPost.record.reply.root
|
rootRef = parentPost.record.reply.root
|
||||||
}
|
}
|
||||||
@@ -273,15 +257,7 @@ async function resolveEmbed(
|
|||||||
queryClient: QueryClient,
|
queryClient: QueryClient,
|
||||||
draft: PostDraft,
|
draft: PostDraft,
|
||||||
onStateChange: ((state: string) => void) | undefined,
|
onStateChange: ((state: string) => void) | undefined,
|
||||||
): Promise<
|
): Promise<app.bsky.feed.post.Main['embed']> {
|
||||||
| $Typed<AppBskyEmbedImages.Main>
|
|
||||||
| $Typed<AppBskyEmbedGallery.Main>
|
|
||||||
| $Typed<AppBskyEmbedVideo.Main>
|
|
||||||
| $Typed<AppBskyEmbedExternal.Main>
|
|
||||||
| $Typed<AppBskyEmbedRecord.Main>
|
|
||||||
| $Typed<AppBskyEmbedRecordWithMedia.Main>
|
|
||||||
| undefined
|
|
||||||
> {
|
|
||||||
if (draft.embed.quote) {
|
if (draft.embed.quote) {
|
||||||
const [resolvedMedia, resolvedQuote] = await Promise.all([
|
const [resolvedMedia, resolvedQuote] = await Promise.all([
|
||||||
resolveMedia(agent, pdsClient, queryClient, draft.embed, onStateChange),
|
resolveMedia(agent, pdsClient, queryClient, draft.embed, onStateChange),
|
||||||
@@ -321,7 +297,12 @@ async function resolveEmbed(
|
|||||||
if (resolvedLink.type === 'record') {
|
if (resolvedLink.type === 'record') {
|
||||||
return {
|
return {
|
||||||
$type: 'app.bsky.embed.record',
|
$type: 'app.bsky.embed.record',
|
||||||
record: resolvedLink.record,
|
/*
|
||||||
|
* `resolve.ts` is still legacy-typed - its strong refs and URIs carry
|
||||||
|
* unbranded strings. Assert at the boundary until it moves to the
|
||||||
|
* clients.
|
||||||
|
*/
|
||||||
|
record: resolvedLink.record as com.atproto.repo.strongRef.Main,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -335,10 +316,10 @@ async function resolveMedia(
|
|||||||
embedDraft: EmbedDraft,
|
embedDraft: EmbedDraft,
|
||||||
onStateChange: ((state: string) => void) | undefined,
|
onStateChange: ((state: string) => void) | undefined,
|
||||||
): Promise<
|
): Promise<
|
||||||
| $Typed<AppBskyEmbedExternal.Main>
|
| $Typed<app.bsky.embed.external.Main>
|
||||||
| $Typed<AppBskyEmbedImages.Main>
|
| $Typed<app.bsky.embed.images.Main>
|
||||||
| $Typed<AppBskyEmbedGallery.Main>
|
| $Typed<app.bsky.embed.gallery.Main>
|
||||||
| $Typed<AppBskyEmbedVideo.Main>
|
| $Typed<app.bsky.embed.video.Main>
|
||||||
| undefined
|
| undefined
|
||||||
> {
|
> {
|
||||||
if (embedDraft.media?.type === 'images') {
|
if (embedDraft.media?.type === 'images') {
|
||||||
@@ -347,7 +328,7 @@ async function resolveMedia(
|
|||||||
count: imagesDraft.length,
|
count: imagesDraft.length,
|
||||||
})
|
})
|
||||||
onStateChange?.(t`Uploading images...`)
|
onStateChange?.(t`Uploading images...`)
|
||||||
const images: AppBskyEmbedImages.Image[] = await Promise.all(
|
const images: app.bsky.embed.images.Image[] = await Promise.all(
|
||||||
imagesDraft.map(async (image, i) => {
|
imagesDraft.map(async (image, i) => {
|
||||||
logger.debug(`Compressing image #${i}`)
|
logger.debug(`Compressing image #${i}`)
|
||||||
const {path, width, height, mime} = await compressImage(
|
const {path, width, height, mime} = await compressImage(
|
||||||
@@ -357,7 +338,7 @@ async function resolveMedia(
|
|||||||
logger.debug(`Uploading image #${i}`)
|
logger.debug(`Uploading image #${i}`)
|
||||||
const res = await uploadBlob(pdsClient, path, mime)
|
const res = await uploadBlob(pdsClient, path, mime)
|
||||||
return {
|
return {
|
||||||
image: toLegacyBlobRef(res.blob),
|
image: res.blob,
|
||||||
alt: image.alt,
|
alt: image.alt,
|
||||||
aspectRatio: {width, height},
|
aspectRatio: {width, height},
|
||||||
}
|
}
|
||||||
@@ -374,7 +355,7 @@ async function resolveMedia(
|
|||||||
count: imagesDraft.length,
|
count: imagesDraft.length,
|
||||||
})
|
})
|
||||||
onStateChange?.(t`Uploading images...`)
|
onStateChange?.(t`Uploading images...`)
|
||||||
const items: $Typed<AppBskyEmbedGallery.Image>[] = await Promise.all(
|
const items: $Typed<app.bsky.embed.gallery.Image>[] = await Promise.all(
|
||||||
imagesDraft.map(async (image, i) => {
|
imagesDraft.map(async (image, i) => {
|
||||||
logger.debug(`Compressing image #${i}`)
|
logger.debug(`Compressing image #${i}`)
|
||||||
const {path, width, height, mime} = await compressImage(
|
const {path, width, height, mime} = await compressImage(
|
||||||
@@ -385,7 +366,7 @@ async function resolveMedia(
|
|||||||
const res = await uploadBlob(pdsClient, path, mime)
|
const res = await uploadBlob(pdsClient, path, mime)
|
||||||
return {
|
return {
|
||||||
$type: 'app.bsky.embed.gallery#image' as const,
|
$type: 'app.bsky.embed.gallery#image' as const,
|
||||||
image: toLegacyBlobRef(res.blob),
|
image: res.blob,
|
||||||
alt: image.alt,
|
alt: image.alt,
|
||||||
aspectRatio: {width, height},
|
aspectRatio: {width, height},
|
||||||
}
|
}
|
||||||
@@ -405,10 +386,10 @@ async function resolveMedia(
|
|||||||
videoDraft.captions
|
videoDraft.captions
|
||||||
.filter(caption => caption.lang !== '')
|
.filter(caption => caption.lang !== '')
|
||||||
.map(async caption => {
|
.map(async caption => {
|
||||||
const {data} = await agent.uploadBlob(caption.file, {
|
const res = await pdsClient.uploadBlob(caption.file, {
|
||||||
encoding: 'text/vtt',
|
encoding: 'text/vtt',
|
||||||
})
|
})
|
||||||
return {lang: caption.lang, file: data.blob}
|
return {lang: caption.lang, file: res.body.blob}
|
||||||
}),
|
}),
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -428,7 +409,11 @@ async function resolveMedia(
|
|||||||
|
|
||||||
return {
|
return {
|
||||||
$type: 'app.bsky.embed.video',
|
$type: 'app.bsky.embed.video',
|
||||||
video: videoDraft.pendingPublish.blobRef,
|
/*
|
||||||
|
* The video pipeline still reads its blob off the legacy agent, so
|
||||||
|
* normalize it to the lex shape before it reaches the lex write.
|
||||||
|
*/
|
||||||
|
video: fromLegacyBlobRef(videoDraft.pendingPublish.blobRef),
|
||||||
alt: videoDraft.altText || undefined,
|
alt: videoDraft.altText || undefined,
|
||||||
captions: captions.length === 0 ? undefined : captions,
|
captions: captions.length === 0 ? undefined : captions,
|
||||||
aspectRatio,
|
aspectRatio,
|
||||||
@@ -443,17 +428,17 @@ async function resolveMedia(
|
|||||||
agent,
|
agent,
|
||||||
gifDraft.gif,
|
gifDraft.gif,
|
||||||
)
|
)
|
||||||
let blob: BlobRef | undefined
|
let blob: app.bsky.embed.external.External['thumb']
|
||||||
if (resolvedGif.thumb) {
|
if (resolvedGif.thumb) {
|
||||||
onStateChange?.(t`Uploading link thumbnail...`)
|
onStateChange?.(t`Uploading link thumbnail...`)
|
||||||
const {path, mime} = resolvedGif.thumb.source
|
const {path, mime} = resolvedGif.thumb.source
|
||||||
const response = await uploadBlob(pdsClient, path, mime)
|
const response = await uploadBlob(pdsClient, path, mime)
|
||||||
blob = toLegacyBlobRef(response.blob)
|
blob = response.blob
|
||||||
}
|
}
|
||||||
return {
|
return {
|
||||||
$type: 'app.bsky.embed.external',
|
$type: 'app.bsky.embed.external',
|
||||||
external: {
|
external: {
|
||||||
uri: resolvedGif.uri,
|
uri: resolvedGif.uri as UriString,
|
||||||
title: resolvedGif.title,
|
title: resolvedGif.title,
|
||||||
description: createGIFDescription(resolvedGif.title, gifDraft.alt),
|
description: createGIFDescription(resolvedGif.title, gifDraft.alt),
|
||||||
thumb: blob,
|
thumb: blob,
|
||||||
@@ -467,21 +452,23 @@ async function resolveMedia(
|
|||||||
embedDraft.link.uri,
|
embedDraft.link.uri,
|
||||||
)
|
)
|
||||||
if (resolvedLink.type === 'external') {
|
if (resolvedLink.type === 'external') {
|
||||||
let blob: BlobRef | undefined
|
let blob: app.bsky.embed.external.External['thumb']
|
||||||
if (resolvedLink.thumb) {
|
if (resolvedLink.thumb) {
|
||||||
onStateChange?.(t`Uploading link thumbnail...`)
|
onStateChange?.(t`Uploading link thumbnail...`)
|
||||||
const {path, mime} = resolvedLink.thumb.source
|
const {path, mime} = resolvedLink.thumb.source
|
||||||
const response = await uploadBlob(pdsClient, path, mime)
|
const response = await uploadBlob(pdsClient, path, mime)
|
||||||
blob = toLegacyBlobRef(response.blob)
|
blob = response.blob
|
||||||
}
|
}
|
||||||
return {
|
return {
|
||||||
$type: 'app.bsky.embed.external',
|
$type: 'app.bsky.embed.external',
|
||||||
external: {
|
external: {
|
||||||
uri: resolvedLink.uri,
|
uri: resolvedLink.uri as UriString,
|
||||||
title: resolvedLink.title,
|
title: resolvedLink.title,
|
||||||
description: resolvedLink.description,
|
description: resolvedLink.description,
|
||||||
thumb: blob,
|
thumb: blob,
|
||||||
associatedRefs: resolvedLink.associatedRefs,
|
associatedRefs: resolvedLink.associatedRefs as
|
||||||
|
| com.atproto.repo.strongRef.Main[]
|
||||||
|
| undefined,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -492,7 +479,7 @@ async function resolveMedia(
|
|||||||
return {
|
return {
|
||||||
$type: 'app.bsky.embed.external',
|
$type: 'app.bsky.embed.external',
|
||||||
external: {
|
external: {
|
||||||
uri: resolvedLink.uri,
|
uri: resolvedLink.uri as UriString,
|
||||||
title: resolvedLink.view.name,
|
title: resolvedLink.view.name,
|
||||||
description: `${resolvedLink.view.memberCount}/${resolvedLink.view.memberLimit}`,
|
description: `${resolvedLink.view.memberCount}/${resolvedLink.view.memberLimit}`,
|
||||||
},
|
},
|
||||||
@@ -502,14 +489,18 @@ async function resolveMedia(
|
|||||||
return undefined
|
return undefined
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* `resolve.ts` still resolves through the agent and returns legacy-typed refs;
|
||||||
|
* assert at the boundary until it moves to the clients.
|
||||||
|
*/
|
||||||
async function resolveRecord(
|
async function resolveRecord(
|
||||||
agent: AtpAgent,
|
agent: AtpAgent,
|
||||||
queryClient: QueryClient,
|
queryClient: QueryClient,
|
||||||
uri: string,
|
uri: string,
|
||||||
): Promise<ComAtprotoRepoStrongRef.Main> {
|
): Promise<com.atproto.repo.strongRef.Main> {
|
||||||
const resolvedLink = await fetchResolveLinkQuery(queryClient, agent, uri)
|
const resolvedLink = await fetchResolveLinkQuery(queryClient, agent, uri)
|
||||||
if (resolvedLink.type !== 'record') {
|
if (resolvedLink.type !== 'record') {
|
||||||
throw Error(t`Expected uri to resolve to a record`)
|
throw Error(t`Expected uri to resolve to a record`)
|
||||||
}
|
}
|
||||||
return resolvedLink.record
|
return resolvedLink.record as com.atproto.repo.strongRef.Main
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -14,3 +14,18 @@ import {type BlobRef as LexBlobRef} from '@atproto/lex'
|
|||||||
export function toLegacyBlobRef(blob: LexBlobRef): BlobRef {
|
export function toLegacyBlobRef(blob: LexBlobRef): BlobRef {
|
||||||
return BlobRef.fromJsonRef(blob as Parameters<typeof BlobRef.fromJsonRef>[0])
|
return BlobRef.fromJsonRef(blob as Parameters<typeof BlobRef.fromJsonRef>[0])
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Normalize a legacy `BlobRef` class instance to the plain-JSON lex blob shape.
|
||||||
|
*
|
||||||
|
* Required for any blob that reaches a lex write: the lex serializer walks
|
||||||
|
* plain objects, so a class instance goes on the wire with its internal
|
||||||
|
* `original` field and no `$type`. `ipld()` yields exactly the lex shape, and
|
||||||
|
* hashes identically (see `computeCid.test.ts` case 2b).
|
||||||
|
*
|
||||||
|
* Only the video pipeline still needs this - it reads its blob off the legacy
|
||||||
|
* agent (`app.bsky.video.getJobStatus`). Drop it when the video client moves.
|
||||||
|
*/
|
||||||
|
export function fromLegacyBlobRef(blob: BlobRef): LexBlobRef {
|
||||||
|
return blob.ipld()
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user