···1414import {Container} from '../components/container'
1515import {Link} from '../components/link'
1616import {Post} from '../components/post'
1717-import {niceDate} from '../utils'
1717+import * as bsky from '../types/bsky'
1818+import {niceDate} from '../util/nice-date'
18191919-const DEFAULT_POST = 'https://bsky.app/profile/emilyliu.me/post/3jzn6g7ixgq2y'
2020+const DEFAULT_POST =
2121+ 'https://bsky.app/profile/did:plc:vjug55kidv6sye7ykr5faxxn/post/3jzn6g7ixgq2y'
2022const DEFAULT_URI =
2123 'at://did:plc:vjug55kidv6sye7ykr5faxxn/app.bsky.feed.post/3jzn6g7ixgq2y'
2224···222224 const snippet = useMemo(() => {
223225 const record = thread.post.record
224226225225- if (!AppBskyFeedPost.isRecord(record)) {
227227+ if (
228228+ !bsky.dangerousIsType<AppBskyFeedPost.Record>(
229229+ record,
230230+ AppBskyFeedPost.isRecord,
231231+ )
232232+ ) {
226233 return ''
227234 }
228235
+1-1
bskyembed/src/screens/post.tsx
···88import {Container} from '../components/container'
99import {Link} from '../components/link'
1010import {Post} from '../components/post'
1111-import {getRkey} from '../utils'
1111+import {getRkey} from '../util/rkey'
12121313const root = document.getElementById('app')
1414if (!root) throw new Error('No root element')
+49
bskyembed/src/types/bsky/index.ts
···11+import {ValidationResult} from '@atproto/lexicon'
22+33+export * as profile from './profile'
44+55+/**
66+ * Fast type checking without full schema validation, for use with data we
77+ * trust, or for non-critical path use cases. Why? Our SDK's `is*` identity
88+ * utils do not assert the type of the entire object, only the `$type` string.
99+ *
1010+ * For full validation of the object schema, use the `validate` export from
1111+ * this file.
1212+ *
1313+ * Usage:
1414+ * ```ts
1515+ * import * as bsky from '#/types/bsky'
1616+ *
1717+ * if (bsky.dangerousIsType<AppBskyFeedPost.Record>(item, AppBskyFeedPost.isRecord)) {
1818+ * // `item` has type `$Typed<AppBskyFeedPost.Record>` here
1919+ * }
2020+ * ```
2121+ */
2222+export function dangerousIsType<R extends {$type?: string}>(
2323+ record: unknown,
2424+ identity: <V>(v: V) => v is V & {$type: NonNullable<R['$type']>},
2525+): record is R {
2626+ return identity(record)
2727+}
2828+2929+/**
3030+ * Fully validates the object schema, which has a performance cost.
3131+ *
3232+ * For faster checks with data we trust, like that from our app view, use the
3333+ * `dangerousIsType` export from this same file.
3434+ *
3535+ * Usage:
3636+ * ```ts
3737+ * import * as bsky from '#/types/bsky'
3838+ *
3939+ * if (bsky.validate(item, AppBskyFeedPost.validateRecord)) {
4040+ * // `item` has type `$Typed<AppBskyFeedPost.Record>` here
4141+ * }
4242+ * ```
4343+ */
4444+export function validate<R extends {$type?: string}>(
4545+ record: unknown,
4646+ validator: (v: unknown) => ValidationResult<R>,
4747+): record is R {
4848+ return validator(record).success
4949+}
+10
bskyembed/src/types/bsky/profile.ts
···11+import {type AppBskyActorDefs, type ChatBskyActorDefs} from '@atproto/api'
22+33+/**
44+ * Matches any profile view exported by our SDK
55+ */
66+export type AnyProfileView =
77+ | AppBskyActorDefs.ProfileViewBasic
88+ | AppBskyActorDefs.ProfileView
99+ | AppBskyActorDefs.ProfileViewDetailed
1010+ | ChatBskyActorDefs.ProfileViewBasic
+11
bskyembed/src/util/nice-date.ts
···11+export function niceDate(date: number | string | Date) {
22+ const d = new Date(date)
33+ return `${d.toLocaleDateString('en-us', {
44+ year: 'numeric',
55+ month: 'short',
66+ day: 'numeric',
77+ })} at ${d.toLocaleTimeString(undefined, {
88+ hour: 'numeric',
99+ minute: '2-digit',
1010+ })}`
1111+}