From 006f7339e281fcf7b8bf0e22c8223cadd630a2cb Mon Sep 17 00:00:00 2001 From: Matthieu Sieben Date: Mon, 13 Jan 2025 15:56:42 +0100 Subject: [PATCH 01/20] Make codegen types stricter --- .changeset/brave-dots-wink.md | 5 + .changeset/brown-flies-stare.md | 9 + .changeset/eleven-knives-complain.md | 5 + .changeset/fast-points-walk.md | 5 + .changeset/fast-waves-jog.md | 5 + .changeset/green-cherries-shave.md | 5 + .changeset/green-forks-lie.md | 5 + .changeset/lazy-moles-switch.md | 5 + .changeset/popular-shirts-rescue.md | 5 + .changeset/shiny-suns-walk.md | 5 + .changeset/silly-starfishes-pay.md | 5 + .changeset/spotty-bottles-march.md | 5 + .changeset/tiny-carpets-impress.md | 5 + packages/api/src/agent.ts | 609 +++---- packages/api/src/index.ts | 2 + packages/api/src/mocker.ts | 14 +- packages/api/src/moderation/decision.ts | 4 +- packages/api/src/moderation/mutewords.ts | 2 +- packages/api/src/moderation/subjects/post.ts | 120 +- .../api/src/moderation/subjects/user-list.ts | 6 +- packages/api/src/moderation/types.ts | 2 + packages/api/src/moderation/util.ts | 6 +- packages/api/src/predicate.ts | 43 + packages/api/src/rich-text/rich-text.ts | 18 +- packages/api/tests/atp-agent.test.ts | 38 +- .../tests/moderation-custom-labels.test.ts | 1 + .../api/tests/moderation-mutewords.test.ts | 1 + packages/api/tests/moderation-prefs.test.ts | 3 +- .../api/tests/moderation-quoteposts.test.ts | 1 + packages/api/tests/moderation.test.ts | 10 + .../api/tests/rich-text-detection.test.ts | 14 +- .../bsky/src/api/app/bsky/feed/getFeed.ts | 14 +- .../src/api/app/bsky/labeler/getServices.ts | 4 +- .../server/indexing/plugins/post.ts | 10 +- packages/bsky/src/hydration/graph.ts | 5 +- packages/bsky/src/hydration/hydrator.ts | 10 +- packages/bsky/src/hydration/util.ts | 6 +- packages/bsky/src/views/index.ts | 234 ++- packages/bsky/src/views/types.ts | 6 +- packages/bsky/tests/_util.ts | 91 +- packages/bsky/tests/admin/admin-auth.test.ts | 10 +- packages/bsky/tests/admin/moderation.test.ts | 20 +- .../__snapshots__/author-feed.test.ts.snap | 1476 +++++++++++++---- packages/bsky/tests/views/author-feed.test.ts | 76 +- packages/bsky/tests/views/blocks.test.ts | 21 +- .../bsky/tests/views/feed-view-post.test.ts | 40 +- .../bsky/tests/views/labeler-service.test.ts | 5 +- .../bsky/tests/views/labels-takedown.test.ts | 6 +- packages/bsky/tests/views/list-feed.test.ts | 13 +- packages/bsky/tests/views/posts.test.ts | 4 +- .../bsky/tests/views/starter-packs.test.ts | 8 +- packages/bsky/tests/views/thread.test.ts | 19 +- packages/dev-env/src/moderator-client.ts | 11 +- packages/dev-env/src/seed/client.ts | 3 +- packages/lex-cli/src/codegen/client.ts | 76 +- packages/lex-cli/src/codegen/common.ts | 209 ++- packages/lex-cli/src/codegen/lex-gen.ts | 275 ++- packages/lex-cli/src/codegen/server.ts | 55 +- packages/lexicon/src/lexicons.ts | 30 +- packages/lexicon/src/types.ts | 24 +- packages/lexicon/src/util.ts | 15 - packages/lexicon/src/validators/complex.ts | 101 +- packages/lexicon/src/validators/formats.ts | 19 +- packages/lexicon/src/validators/primitives.ts | 12 +- .../ozone/src/api/moderation/emitEvent.ts | 9 +- packages/ozone/src/api/moderation/getRepos.ts | 2 +- packages/ozone/src/api/util.ts | 27 +- packages/ozone/src/mod-service/index.ts | 16 +- packages/ozone/src/mod-service/subject.ts | 52 +- packages/ozone/src/mod-service/types.ts | 17 +- packages/ozone/src/mod-service/util.ts | 4 +- packages/ozone/src/mod-service/views.ts | 256 +-- .../ozone/src/tag-service/embed-tagger.ts | 10 +- .../ozone/src/tag-service/language-tagger.ts | 21 +- .../moderation-events.test.ts.snap | 2 +- packages/ozone/tests/_util.ts | 58 +- packages/ozone/tests/blob-divert.test.ts | 8 +- .../ozone/tests/moderation-appeals.test.ts | 1 + .../ozone/tests/moderation-events.test.ts | 29 +- .../tests/moderation-status-tags.test.ts | 13 +- .../ozone/tests/moderation-statuses.test.ts | 34 +- packages/ozone/tests/moderation.test.ts | 25 +- packages/ozone/tests/protected-tags.test.ts | 1 + .../tests/record-and-account-events.test.ts | 53 +- packages/ozone/tests/takedown.test.ts | 14 +- packages/pds/src/actor-store/record/reader.ts | 4 +- .../src/api/app/bsky/feed/getActorLikes.ts | 2 +- .../src/api/app/bsky/feed/getPostThread.ts | 11 +- .../com/atproto/identity/signPlcOperation.ts | 12 +- .../api/com/atproto/identity/updateHandle.ts | 1 + .../pds/src/api/com/atproto/repo/putRecord.ts | 2 +- packages/pds/src/pipethrough.ts | 4 +- packages/pds/src/read-after-write/viewer.ts | 37 +- packages/pds/src/repo/prepare.ts | 46 +- packages/pds/tests/_util.ts | 11 +- packages/pds/tests/app-passwords.test.ts | 8 +- packages/pds/tests/create-post.test.ts | 5 +- packages/pds/tests/crud.test.ts | 9 +- packages/pds/tests/file-uploads.test.ts | 2 + packages/pds/tests/moderation.test.ts | 21 +- packages/pds/tests/moderator-auth.test.ts | 7 +- packages/pds/tests/preferences.test.ts | 2 + .../proxied/__snapshots__/admin.test.ts.snap | 2 +- packages/pds/tests/proxied/admin.test.ts | 6 + .../tests/proxied/read-after-write.test.ts | 45 +- packages/pds/tests/seeds/basic.ts | 2 +- packages/syntax/src/tid.ts | 20 +- tsconfig/browser.json | 2 +- tsconfig/isomorphic.json | 2 +- tsconfig/node.json | 2 +- tsconfig/nodenext.json | 4 +- tsconfig/tests.json | 2 +- 112 files changed, 3120 insertions(+), 1674 deletions(-) create mode 100644 .changeset/brave-dots-wink.md create mode 100644 .changeset/brown-flies-stare.md create mode 100644 .changeset/eleven-knives-complain.md create mode 100644 .changeset/fast-points-walk.md create mode 100644 .changeset/fast-waves-jog.md create mode 100644 .changeset/green-cherries-shave.md create mode 100644 .changeset/green-forks-lie.md create mode 100644 .changeset/lazy-moles-switch.md create mode 100644 .changeset/popular-shirts-rescue.md create mode 100644 .changeset/shiny-suns-walk.md create mode 100644 .changeset/silly-starfishes-pay.md create mode 100644 .changeset/spotty-bottles-march.md create mode 100644 .changeset/tiny-carpets-impress.md create mode 100644 packages/api/src/predicate.ts diff --git a/.changeset/brave-dots-wink.md b/.changeset/brave-dots-wink.md new file mode 100644 index 00000000000..f8937aed2f7 --- /dev/null +++ b/.changeset/brave-dots-wink.md @@ -0,0 +1,5 @@ +--- +"@atproto/lexicon": patch +--- + +Small bundle size improvement diff --git a/.changeset/brown-flies-stare.md b/.changeset/brown-flies-stare.md new file mode 100644 index 00000000000..97aa88fdec2 --- /dev/null +++ b/.changeset/brown-flies-stare.md @@ -0,0 +1,9 @@ +--- +"@atproto/ozone": patch +"@atproto/bsky": patch +"@atproto/api": patch +"@atproto/pds": patch +--- + +Update generated code to better reflect actual entity structure + diff --git a/.changeset/eleven-knives-complain.md b/.changeset/eleven-knives-complain.md new file mode 100644 index 00000000000..a62cbd72fbd --- /dev/null +++ b/.changeset/eleven-knives-complain.md @@ -0,0 +1,5 @@ +--- +"@atproto/lex-cli": patch +--- + +Improve typing of isX and validateX return values diff --git a/.changeset/fast-points-walk.md b/.changeset/fast-points-walk.md new file mode 100644 index 00000000000..697405d5178 --- /dev/null +++ b/.changeset/fast-points-walk.md @@ -0,0 +1,5 @@ +--- +"@atproto/syntax": patch +--- + +Improve performance of isValidTid diff --git a/.changeset/fast-waves-jog.md b/.changeset/fast-waves-jog.md new file mode 100644 index 00000000000..401816823af --- /dev/null +++ b/.changeset/fast-waves-jog.md @@ -0,0 +1,5 @@ +--- +"@atproto/lex-cli": patch +--- + +Strongly type result of generated validation helpers diff --git a/.changeset/green-cherries-shave.md b/.changeset/green-cherries-shave.md new file mode 100644 index 00000000000..13326d6d967 --- /dev/null +++ b/.changeset/green-cherries-shave.md @@ -0,0 +1,5 @@ +--- +"@atproto/pds": patch +--- + +Minor typing fixes diff --git a/.changeset/green-forks-lie.md b/.changeset/green-forks-lie.md new file mode 100644 index 00000000000..394c8fbac4d --- /dev/null +++ b/.changeset/green-forks-lie.md @@ -0,0 +1,5 @@ +--- +"@atproto/api": minor +--- + +Helper functions (e.g. `NS.isRecord`) no longer casts the output value. Use the new `NS.isValidRecord` function to ensure an unknown input is a valid `NS.Record`. The `isX` helper function's purpose is to discriminate between `$type`d values from unions. diff --git a/.changeset/lazy-moles-switch.md b/.changeset/lazy-moles-switch.md new file mode 100644 index 00000000000..06688cc6284 --- /dev/null +++ b/.changeset/lazy-moles-switch.md @@ -0,0 +1,5 @@ +--- +"@atproto/api": minor +--- + +Add a `$type` property to record and custom user interfaces. diff --git a/.changeset/popular-shirts-rescue.md b/.changeset/popular-shirts-rescue.md new file mode 100644 index 00000000000..3e7b8ed68bb --- /dev/null +++ b/.changeset/popular-shirts-rescue.md @@ -0,0 +1,5 @@ +--- +"@atproto/lexicon": patch +--- + +Various performance improvements diff --git a/.changeset/shiny-suns-walk.md b/.changeset/shiny-suns-walk.md new file mode 100644 index 00000000000..65068e336df --- /dev/null +++ b/.changeset/shiny-suns-walk.md @@ -0,0 +1,5 @@ +--- +"@atproto/lex-cli": patch +--- + +Type the generated `ids` object (that contains all the lexicon namespace ids) as `const`. diff --git a/.changeset/silly-starfishes-pay.md b/.changeset/silly-starfishes-pay.md new file mode 100644 index 00000000000..d88f950aeed --- /dev/null +++ b/.changeset/silly-starfishes-pay.md @@ -0,0 +1,5 @@ +--- +"@atproto/lexicon": patch +--- + +Fully type `ValidationResult`'s `value` property, allowing `NS.validateRecord` helper functions to return a typed value in case of success. diff --git a/.changeset/spotty-bottles-march.md b/.changeset/spotty-bottles-march.md new file mode 100644 index 00000000000..93f8f8758d8 --- /dev/null +++ b/.changeset/spotty-bottles-march.md @@ -0,0 +1,5 @@ +--- +"@atproto/lex-cli": minor +--- + +Remove `[string]: unknown` index signature from records, custom user objects, input and output schemas. diff --git a/.changeset/tiny-carpets-impress.md b/.changeset/tiny-carpets-impress.md new file mode 100644 index 00000000000..6afcc7fa381 --- /dev/null +++ b/.changeset/tiny-carpets-impress.md @@ -0,0 +1,5 @@ +--- +"@atproto/lex-cli": patch +--- + +Properly type empty "schemas" array as `LexiconDoc[]`. diff --git a/packages/api/src/agent.ts b/packages/api/src/agent.ts index ec4cda02fb4..ba58d710163 100644 --- a/packages/api/src/agent.ts +++ b/packages/api/src/agent.ts @@ -1,5 +1,5 @@ import AwaitLock from 'await-lock' -import { TID } from '@atproto/common-web' +import { TID, retry } from '@atproto/common-web' import { AtUri, ensureValidDid } from '@atproto/syntax' import { FetchHandler, XrpcClient, buildFetchHandler } from '@atproto/xrpc' import { @@ -15,6 +15,7 @@ import { } from './client/index' import { schemas } from './client/lexicons' import { MutedWord, Nux } from './client/types/app/bsky/actor/defs' +import { $Typed, Un$Typed } from './client/util' import { BSKY_LABELER_DID } from './const' import { interpretLabelValueDefinitions } from './moderation' import { DEFAULT_LABEL_SETTINGS } from './moderation/const/labels' @@ -23,6 +24,7 @@ import { LabelPreference, ModerationPrefs, } from './moderation/types' +import * as predicate from './predicate' import { SessionManager } from './session-manager' import { AtpAgentGlobalOpts, @@ -56,15 +58,6 @@ const THREAD_VIEW_PREF_DEFAULTS = { prioritizeFollowedUsers: true, } -declare global { - interface Array { - findLast( - predicate: (value: T, index: number, obj: T[]) => unknown, - thisArg?: any, - ): T - } -} - export type { FetchHandler } /** @@ -447,59 +440,56 @@ export class Agent extends XrpcClient { }) } + /** + * @note: Using this method will reset the whole profile record if it + * previously contained invalid values (wrt to the profile lexicon). + */ async upsertProfile( updateFn: ( existing: AppBskyActorProfile.Record | undefined, - ) => AppBskyActorProfile.Record | Promise, - ) { - const repo = this.accountDid + ) => + | Un$Typed + | Promise>, + ): Promise { + const upsert = async () => { + const repo = this.assertDid + const collection = 'app.bsky.actor.profile' - let retriesRemaining = 5 - while (retriesRemaining >= 0) { - // fetch existing const existing = await this.com.atproto.repo - .getRecord({ - repo, - collection: 'app.bsky.actor.profile', - rkey: 'self', - }) + .getRecord({ repo, collection, rkey: 'self' }) .catch((_) => undefined) + const existingRecord: AppBskyActorProfile.Record | undefined = + existing && predicate.isValidProfile(existing.data.value) + ? existing.data.value + : undefined + // run the update - const updated = await updateFn(existing?.data.value) - if (updated) { - updated.$type = 'app.bsky.actor.profile' - } + const updated = await updateFn(existingRecord) + + // validate the value returned by the update function + const validation = AppBskyActorProfile.validateRecord({ + $type: collection, + ...updated, + }) - // validate the record - const validation = AppBskyActorProfile.validateRecord(updated) if (!validation.success) { throw validation.error } - try { - // attempt the put - await this.com.atproto.repo.putRecord({ - repo, - collection: 'app.bsky.actor.profile', - rkey: 'self', - record: updated, - swapRecord: existing?.data.cid || null, - }) - } catch (e: unknown) { - if ( - retriesRemaining > 0 && - e instanceof ComAtprotoRepoPutRecord.InvalidSwapError - ) { - // try again - retriesRemaining-- - continue - } else { - throw e - } - } - break + await this.com.atproto.repo.putRecord({ + repo, + collection, + rkey: 'self', + record: validation.value, + swapRecord: existing?.data.cid || null, + }) } + + return retry(upsert, { + maxRetries: 5, + retryable: (e) => e instanceof ComAtprotoRepoPutRecord.InvalidSwapError, + }) } async mute(actor: string) { @@ -591,23 +581,14 @@ export class Agent extends XrpcClient { const res = await this.app.bsky.actor.getPreferences({}) const labelPrefs: AppBskyActorDefs.ContentLabelPref[] = [] for (const pref of res.data.preferences) { - if ( - AppBskyActorDefs.isAdultContentPref(pref) && - AppBskyActorDefs.validateAdultContentPref(pref).success - ) { + if (predicate.isValidAdultContentPref(pref)) { // adult content preferences prefs.moderationPrefs.adultContentEnabled = pref.enabled - } else if ( - AppBskyActorDefs.isContentLabelPref(pref) && - AppBskyActorDefs.validateContentLabelPref(pref).success - ) { + } else if (predicate.isValidContentLabelPref(pref)) { // content label preference const adjustedPref = adjustLegacyContentLabelPref(pref) labelPrefs.push(adjustedPref) - } else if ( - AppBskyActorDefs.isLabelersPref(pref) && - AppBskyActorDefs.validateLabelersPref(pref).success - ) { + } else if (predicate.isValidLabelersPref(pref)) { // labelers preferences prefs.moderationPrefs.labelers = this.appLabelers .map((did: string) => ({ did, labels: {} })) @@ -617,56 +598,30 @@ export class Agent extends XrpcClient { labels: {}, })), ) - } else if ( - AppBskyActorDefs.isSavedFeedsPrefV2(pref) && - AppBskyActorDefs.validateSavedFeedsPrefV2(pref).success - ) { + } else if (predicate.isValidSavedFeedsPrefV2(pref)) { prefs.savedFeeds = pref.items - } else if ( - AppBskyActorDefs.isSavedFeedsPref(pref) && - AppBskyActorDefs.validateSavedFeedsPref(pref).success - ) { + } else if (predicate.isValidSavedFeedsPref(pref)) { // saved and pinned feeds prefs.feeds.saved = pref.saved prefs.feeds.pinned = pref.pinned - } else if ( - AppBskyActorDefs.isPersonalDetailsPref(pref) && - AppBskyActorDefs.validatePersonalDetailsPref(pref).success - ) { + } else if (predicate.isValidPersonalDetailsPref(pref)) { // birth date (irl) if (pref.birthDate) { prefs.birthDate = new Date(pref.birthDate) } - } else if ( - AppBskyActorDefs.isFeedViewPref(pref) && - AppBskyActorDefs.validateFeedViewPref(pref).success - ) { + } else if (predicate.isValidFeedViewPref(pref)) { // feed view preferences - // eslint-disable-next-line @typescript-eslint/no-unused-vars - const { $type, feed, ...v } = pref - prefs.feedViewPrefs[pref.feed] = { ...FEED_VIEW_PREF_DEFAULTS, ...v } - } else if ( - AppBskyActorDefs.isThreadViewPref(pref) && - AppBskyActorDefs.validateThreadViewPref(pref).success - ) { + const { $type: _, feed, ...v } = pref + prefs.feedViewPrefs[feed] = { ...FEED_VIEW_PREF_DEFAULTS, ...v } + } else if (predicate.isValidThreadViewPref(pref)) { // thread view preferences - // eslint-disable-next-line @typescript-eslint/no-unused-vars - const { $type, ...v } = pref + const { $type: _, ...v } = pref prefs.threadViewPrefs = { ...prefs.threadViewPrefs, ...v } - } else if ( - AppBskyActorDefs.isInterestsPref(pref) && - AppBskyActorDefs.validateInterestsPref(pref).success - ) { - // eslint-disable-next-line @typescript-eslint/no-unused-vars - const { $type, ...v } = pref + } else if (predicate.isValidInterestsPref(pref)) { + const { $type: _, ...v } = pref prefs.interests = { ...prefs.interests, ...v } - } else if ( - AppBskyActorDefs.isMutedWordsPref(pref) && - AppBskyActorDefs.validateMutedWordsPref(pref).success - ) { - // eslint-disable-next-line @typescript-eslint/no-unused-vars - const { $type, ...v } = pref - prefs.moderationPrefs.mutedWords = v.items + } else if (predicate.isValidMutedWordsPref(pref)) { + prefs.moderationPrefs.mutedWords = pref.items if (prefs.moderationPrefs.mutedWords.length) { prefs.moderationPrefs.mutedWords = @@ -675,26 +630,13 @@ export class Agent extends XrpcClient { return word }) } - } else if ( - AppBskyActorDefs.isHiddenPostsPref(pref) && - AppBskyActorDefs.validateHiddenPostsPref(pref).success - ) { - // eslint-disable-next-line @typescript-eslint/no-unused-vars - const { $type, ...v } = pref - prefs.moderationPrefs.hiddenPosts = v.items - } else if ( - AppBskyActorDefs.isBskyAppStatePref(pref) && - AppBskyActorDefs.validateBskyAppStatePref(pref).success - ) { - // eslint-disable-next-line @typescript-eslint/no-unused-vars - const { $type, ...v } = pref - prefs.bskyAppState.queuedNudges = v.queuedNudges || [] - prefs.bskyAppState.activeProgressGuide = v.activeProgressGuide - prefs.bskyAppState.nuxs = v.nuxs || [] - } else if ( - AppBskyActorDefs.isPostInteractionSettingsPref(pref) && - AppBskyActorDefs.validatePostInteractionSettingsPref(pref).success - ) { + } else if (predicate.isValidHiddenPostsPref(pref)) { + prefs.moderationPrefs.hiddenPosts = pref.items + } else if (predicate.isValidBskyAppStatePref(pref)) { + prefs.bskyAppState.queuedNudges = pref.queuedNudges || [] + prefs.bskyAppState.activeProgressGuide = pref.activeProgressGuide + prefs.bskyAppState.nuxs = pref.nuxs || [] + } else if (predicate.isValidPostInteractionSettingsPref(pref)) { prefs.postInteractionSettings.threadgateAllowRules = pref.threadgateAllowRules prefs.postInteractionSettings.postgateEmbeddingRules = @@ -901,22 +843,18 @@ export class Agent extends XrpcClient { async setAdultContentEnabled(v: boolean) { await this.updatePreferences((prefs: AppBskyActorDefs.Preferences) => { - let adultContentPref = prefs.findLast( - (pref) => - AppBskyActorDefs.isAdultContentPref(pref) && - AppBskyActorDefs.validateAdultContentPref(pref).success, - ) - if (adultContentPref) { - adultContentPref.enabled = v - } else { - adultContentPref = { - $type: 'app.bsky.actor.defs#adultContentPref', - enabled: v, - } + const adultContentPref = prefs.findLast( + predicate.isValidAdultContentPref, + ) || { + $type: 'app.bsky.actor.defs#adultContentPref', + enabled: v, } + + adultContentPref.enabled = v + return prefs .filter((pref) => !AppBskyActorDefs.isAdultContentPref(pref)) - .concat([adultContentPref]) + .concat(adultContentPref) }) } @@ -929,26 +867,20 @@ export class Agent extends XrpcClient { ensureValidDid(labelerDid) } await this.updatePreferences((prefs: AppBskyActorDefs.Preferences) => { - let labelPref = prefs.findLast( - (pref) => - AppBskyActorDefs.isContentLabelPref(pref) && - AppBskyActorDefs.validateContentLabelPref(pref).success && - pref.label === key && - pref.labelerDid === labelerDid, - ) - let legacyLabelPref: AppBskyActorDefs.ContentLabelPref | undefined - - if (labelPref) { - labelPref.visibility = value - } else { - labelPref = { - $type: 'app.bsky.actor.defs#contentLabelPref', - label: key, - labelerDid, - visibility: value, - } + const labelPref = prefs + .filter(predicate.isValidContentLabelPref) + .findLast( + (pref) => pref.label === key && pref.labelerDid === labelerDid, + ) || { + $type: 'app.bsky.actor.defs#contentLabelPref', + label: key, + labelerDid, + visibility: value, } + labelPref.visibility = value + + let legacyLabelPref: $Typed | undefined if (AppBskyActorDefs.isContentLabelPref(labelPref)) { // is global if (!labelPref.labelerDid) { @@ -956,28 +888,26 @@ export class Agent extends XrpcClient { 'graphic-media': 'gore', porn: 'nsfw', sexual: 'suggestive', + // Protect against using toString, hasOwnProperty, etc. as a label: + __proto__: null, }[labelPref.label] // if it's a legacy label, double-write the legacy label if (legacyLabelValue) { - legacyLabelPref = prefs.findLast( - (pref) => - AppBskyActorDefs.isContentLabelPref(pref) && - AppBskyActorDefs.validateContentLabelPref(pref).success && - pref.label === legacyLabelValue && - pref.labelerDid === undefined, - ) as AppBskyActorDefs.ContentLabelPref | undefined - - if (legacyLabelPref) { - legacyLabelPref.visibility = value - } else { - legacyLabelPref = { - $type: 'app.bsky.actor.defs#contentLabelPref', - label: legacyLabelValue, - labelerDid: undefined, - visibility: value, - } + legacyLabelPref = prefs + .filter(predicate.isValidContentLabelPref) + .findLast( + (pref) => + pref.label === legacyLabelValue && + pref.labelerDid === undefined, + ) || { + $type: 'app.bsky.actor.defs#contentLabelPref', + label: legacyLabelValue, + labelerDid: undefined, + visibility: value, } + + legacyLabelPref!.visibility = value } } } @@ -988,7 +918,7 @@ export class Agent extends XrpcClient { !AppBskyActorDefs.isContentLabelPref(pref) || !(pref.label === key && pref.labelerDid === labelerDid), ) - .concat([labelPref]) + .concat(labelPref) .filter((pref) => { if (!legacyLabelPref) return true return ( @@ -1006,31 +936,18 @@ export class Agent extends XrpcClient { async addLabeler(did: string) { const prefs = await this.updatePreferences( (prefs: AppBskyActorDefs.Preferences) => { - let labelersPref = prefs.findLast( - (pref) => - AppBskyActorDefs.isLabelersPref(pref) && - AppBskyActorDefs.validateLabelersPref(pref).success, - ) - if (!labelersPref) { - labelersPref = { - $type: 'app.bsky.actor.defs#labelersPref', - labelers: [], - } + const labelersPref = prefs.findLast(predicate.isValidLabelersPref) || { + $type: 'app.bsky.actor.defs#labelersPref', + labelers: [], } - if (AppBskyActorDefs.isLabelersPref(labelersPref)) { - let labelerPrefItem = labelersPref.labelers.find( - (labeler) => labeler.did === did, - ) - if (!labelerPrefItem) { - labelerPrefItem = { - did, - } - labelersPref.labelers.push(labelerPrefItem) - } + + if (!labelersPref.labelers.some((labeler) => labeler.did === did)) { + labelersPref.labelers.push({ did }) } + return prefs .filter((pref) => !AppBskyActorDefs.isLabelersPref(pref)) - .concat([labelersPref]) + .concat(labelersPref) }, ) // automatically configure the client @@ -1040,25 +957,18 @@ export class Agent extends XrpcClient { async removeLabeler(did: string) { const prefs = await this.updatePreferences( (prefs: AppBskyActorDefs.Preferences) => { - let labelersPref = prefs.findLast( - (pref) => - AppBskyActorDefs.isLabelersPref(pref) && - AppBskyActorDefs.validateLabelersPref(pref).success, - ) - if (!labelersPref) { - labelersPref = { - $type: 'app.bsky.actor.defs#labelersPref', - labelers: [], - } - } - if (AppBskyActorDefs.isLabelersPref(labelersPref)) { - labelersPref.labelers = labelersPref.labelers.filter( - (labeler) => labeler.did !== did, - ) + const labelersPref = prefs.findLast(predicate.isValidLabelersPref) || { + $type: 'app.bsky.actor.defs#labelersPref', + labelers: [], } + + labelersPref.labelers = labelersPref.labelers.filter( + (labeler) => labeler.did !== did, + ) + return prefs .filter((pref) => !AppBskyActorDefs.isLabelersPref(pref)) - .concat([labelersPref]) + .concat(labelersPref) }, ) // automatically configure the client @@ -1072,73 +982,63 @@ export class Agent extends XrpcClient { }) { birthDate = birthDate instanceof Date ? birthDate.toISOString() : birthDate await this.updatePreferences((prefs: AppBskyActorDefs.Preferences) => { - let personalDetailsPref = prefs.findLast( - (pref) => - AppBskyActorDefs.isPersonalDetailsPref(pref) && - AppBskyActorDefs.validatePersonalDetailsPref(pref).success, - ) - if (personalDetailsPref) { - personalDetailsPref.birthDate = birthDate - } else { - personalDetailsPref = { - $type: 'app.bsky.actor.defs#personalDetailsPref', - birthDate, - } + const personalDetailsPref = prefs.findLast( + predicate.isValidPersonalDetailsPref, + ) || { + $type: 'app.bsky.actor.defs#personalDetailsPref', + birthDate, } + + personalDetailsPref.birthDate = birthDate + return prefs .filter((pref) => !AppBskyActorDefs.isPersonalDetailsPref(pref)) - .concat([personalDetailsPref]) + .concat(personalDetailsPref) }) } async setFeedViewPrefs(feed: string, pref: Partial) { await this.updatePreferences((prefs: AppBskyActorDefs.Preferences) => { - const existing = prefs.findLast( - (pref) => - AppBskyActorDefs.isFeedViewPref(pref) && - AppBskyActorDefs.validateFeedViewPref(pref).success && - pref.feed === feed, - ) - if (existing) { - pref = { ...existing, ...pref } - } + const existing = prefs + .filter(predicate.isValidFeedViewPref) + .findLast((pref) => pref.feed === feed) + return prefs - .filter( - (p) => !AppBskyActorDefs.isFeedViewPref(pref) || p.feed !== feed, - ) - .concat([{ ...pref, $type: 'app.bsky.actor.defs#feedViewPref', feed }]) + .filter((p) => !AppBskyActorDefs.isFeedViewPref(p) || p.feed !== feed) + .concat({ + ...existing, + ...pref, + $type: 'app.bsky.actor.defs#feedViewPref', + feed, + }) }) } async setThreadViewPrefs(pref: Partial) { await this.updatePreferences((prefs: AppBskyActorDefs.Preferences) => { - const existing = prefs.findLast( - (pref) => - AppBskyActorDefs.isThreadViewPref(pref) && - AppBskyActorDefs.validateThreadViewPref(pref).success, - ) - if (existing) { - pref = { ...existing, ...pref } - } + const existing = prefs.findLast(predicate.isValidThreadViewPref) + return prefs .filter((p) => !AppBskyActorDefs.isThreadViewPref(p)) - .concat([{ ...pref, $type: 'app.bsky.actor.defs#threadViewPref' }]) + .concat({ + ...existing, + ...pref, + $type: 'app.bsky.actor.defs#threadViewPref', + }) }) } async setInterestsPref(pref: Partial) { await this.updatePreferences((prefs: AppBskyActorDefs.Preferences) => { - const existing = prefs.findLast( - (pref) => - AppBskyActorDefs.isInterestsPref(pref) && - AppBskyActorDefs.validateInterestsPref(pref).success, - ) - if (existing) { - pref = { ...existing, ...pref } - } + const existing = prefs.findLast(predicate.isValidInterestsPref) + return prefs .filter((p) => !AppBskyActorDefs.isInterestsPref(p)) - .concat([{ ...pref, $type: 'app.bsky.actor.defs#interestsPref' }]) + .concat({ + ...existing, + ...pref, + $type: 'app.bsky.actor.defs#interestsPref', + }) }) } @@ -1156,11 +1056,7 @@ export class Agent extends XrpcClient { if (!sanitizedValue) return await this.updatePreferences((prefs: AppBskyActorDefs.Preferences) => { - let mutedWordsPref = prefs.findLast( - (pref) => - AppBskyActorDefs.isMutedWordsPref(pref) && - AppBskyActorDefs.validateMutedWordsPref(pref).success, - ) + let mutedWordsPref = prefs.findLast(predicate.isValidMutedWordsPref) const newMutedWord: AppBskyActorDefs.MutedWord = { id: TID.nextStr(), @@ -1182,15 +1078,14 @@ export class Agent extends XrpcClient { } else { // if the pref doesn't exist, create it mutedWordsPref = { + $type: 'app.bsky.actor.defs#mutedWordsPref', items: [newMutedWord], } } return prefs .filter((p) => !AppBskyActorDefs.isMutedWordsPref(p)) - .concat([ - { ...mutedWordsPref, $type: 'app.bsky.actor.defs#mutedWordsPref' }, - ]) + .concat(mutedWordsPref) }) } @@ -1218,11 +1113,7 @@ export class Agent extends XrpcClient { */ async updateMutedWord(mutedWord: AppBskyActorDefs.MutedWord) { await this.updatePreferences((prefs: AppBskyActorDefs.Preferences) => { - const mutedWordsPref = prefs.findLast( - (pref) => - AppBskyActorDefs.isMutedWordsPref(pref) && - AppBskyActorDefs.validateMutedWordsPref(pref).success, - ) + const mutedWordsPref = prefs.findLast(predicate.isValidMutedWordsPref) if (mutedWordsPref && AppBskyActorDefs.isMutedWordsPref(mutedWordsPref)) { mutedWordsPref.items = mutedWordsPref.items.map((existingItem) => { @@ -1255,9 +1146,10 @@ export class Agent extends XrpcClient { return prefs .filter((p) => !AppBskyActorDefs.isMutedWordsPref(p)) - .concat([ - { ...mutedWordsPref, $type: 'app.bsky.actor.defs#mutedWordsPref' }, - ]) + .concat({ + ...mutedWordsPref, + $type: 'app.bsky.actor.defs#mutedWordsPref', + }) } return prefs @@ -1269,11 +1161,7 @@ export class Agent extends XrpcClient { */ async removeMutedWord(mutedWord: AppBskyActorDefs.MutedWord) { await this.updatePreferences((prefs: AppBskyActorDefs.Preferences) => { - const mutedWordsPref = prefs.findLast( - (pref) => - AppBskyActorDefs.isMutedWordsPref(pref) && - AppBskyActorDefs.validateMutedWordsPref(pref).success, - ) + const mutedWordsPref = prefs.findLast(predicate.isValidMutedWordsPref) if (mutedWordsPref && AppBskyActorDefs.isMutedWordsPref(mutedWordsPref)) { for (let i = 0; i < mutedWordsPref.items.length; i++) { @@ -1294,9 +1182,10 @@ export class Agent extends XrpcClient { return prefs .filter((p) => !AppBskyActorDefs.isMutedWordsPref(p)) - .concat([ - { ...mutedWordsPref, $type: 'app.bsky.actor.defs#mutedWordsPref' }, - ]) + .concat({ + ...mutedWordsPref, + $type: 'app.bsky.actor.defs#mutedWordsPref', + }) } return prefs @@ -1320,38 +1209,30 @@ export class Agent extends XrpcClient { async bskyAppQueueNudges(nudges: string | string[]) { await this.updatePreferences((prefs: AppBskyActorDefs.Preferences) => { - let bskyAppStatePref: AppBskyActorDefs.BskyAppStatePref = prefs.findLast( - (pref) => - AppBskyActorDefs.isBskyAppStatePref(pref) && - AppBskyActorDefs.validateBskyAppStatePref(pref).success, - ) + const bskyAppStatePref = prefs.findLast( + predicate.isValidBskyAppStatePref, + ) || { + $type: 'app.bsky.actor.defs#bskyAppStatePref', + } - bskyAppStatePref = bskyAppStatePref || {} - nudges = Array.isArray(nudges) ? nudges : [nudges] bskyAppStatePref.queuedNudges = ( bskyAppStatePref.queuedNudges || [] ).concat(nudges) return prefs .filter((p) => !AppBskyActorDefs.isBskyAppStatePref(p)) - .concat([ - { - ...bskyAppStatePref, - $type: 'app.bsky.actor.defs#bskyAppStatePref', - }, - ]) + .concat(bskyAppStatePref) }) } async bskyAppDismissNudges(nudges: string | string[]) { await this.updatePreferences((prefs: AppBskyActorDefs.Preferences) => { - let bskyAppStatePref: AppBskyActorDefs.BskyAppStatePref = prefs.findLast( - (pref) => - AppBskyActorDefs.isBskyAppStatePref(pref) && - AppBskyActorDefs.validateBskyAppStatePref(pref).success, - ) + const bskyAppStatePref = prefs.findLast( + predicate.isValidBskyAppStatePref, + ) || { + $type: 'app.bsky.actor.defs#bskyAppStatePref', + } - bskyAppStatePref = bskyAppStatePref || {} nudges = Array.isArray(nudges) ? nudges : [nudges] bskyAppStatePref.queuedNudges = ( bskyAppStatePref.queuedNudges || [] @@ -1359,43 +1240,30 @@ export class Agent extends XrpcClient { return prefs .filter((p) => !AppBskyActorDefs.isBskyAppStatePref(p)) - .concat([ - { - ...bskyAppStatePref, - $type: 'app.bsky.actor.defs#bskyAppStatePref', - }, - ]) + .concat(bskyAppStatePref) }) } async bskyAppSetActiveProgressGuide( guide: AppBskyActorDefs.BskyAppProgressGuide | undefined, ) { - if ( - guide && - !AppBskyActorDefs.validateBskyAppProgressGuide(guide).success - ) { - throw new Error('Invalid progress guide') + if (guide) { + const result = AppBskyActorDefs.validateBskyAppProgressGuide(guide) + if (!result.success) throw result.error } await this.updatePreferences((prefs: AppBskyActorDefs.Preferences) => { - let bskyAppStatePref: AppBskyActorDefs.BskyAppStatePref = prefs.findLast( - (pref) => - AppBskyActorDefs.isBskyAppStatePref(pref) && - AppBskyActorDefs.validateBskyAppStatePref(pref).success, - ) + const bskyAppStatePref = prefs.findLast( + predicate.isValidBskyAppStatePref, + ) || { + $type: 'app.bsky.actor.defs#bskyAppStatePref', + } - bskyAppStatePref = bskyAppStatePref || {} bskyAppStatePref.activeProgressGuide = guide return prefs .filter((p) => !AppBskyActorDefs.isBskyAppStatePref(p)) - .concat([ - { - ...bskyAppStatePref, - $type: 'app.bsky.actor.defs#bskyAppStatePref', - }, - ]) + .concat(bskyAppStatePref) }) } @@ -1406,13 +1274,12 @@ export class Agent extends XrpcClient { validateNux(nux) await this.updatePreferences((prefs: AppBskyActorDefs.Preferences) => { - let bskyAppStatePref: AppBskyActorDefs.BskyAppStatePref = prefs.findLast( - (pref) => - AppBskyActorDefs.isBskyAppStatePref(pref) && - AppBskyActorDefs.validateBskyAppStatePref(pref).success, - ) + const bskyAppStatePref = prefs.findLast( + predicate.isValidBskyAppStatePref, + ) || { + $type: 'app.bsky.actor.defs#bskyAppStatePref', + } - bskyAppStatePref = bskyAppStatePref || {} bskyAppStatePref.nuxs = bskyAppStatePref.nuxs || [] const existing = bskyAppStatePref.nuxs?.find((n) => { @@ -1439,12 +1306,7 @@ export class Agent extends XrpcClient { return prefs .filter((p) => !AppBskyActorDefs.isBskyAppStatePref(p)) - .concat([ - { - ...bskyAppStatePref, - $type: 'app.bsky.actor.defs#bskyAppStatePref', - }, - ]) + .concat(bskyAppStatePref) }) } @@ -1453,25 +1315,19 @@ export class Agent extends XrpcClient { */ async bskyAppRemoveNuxs(ids: string[]) { await this.updatePreferences((prefs: AppBskyActorDefs.Preferences) => { - let bskyAppStatePref: AppBskyActorDefs.BskyAppStatePref = prefs.findLast( - (pref) => - AppBskyActorDefs.isBskyAppStatePref(pref) && - AppBskyActorDefs.validateBskyAppStatePref(pref).success, - ) + const bskyAppStatePref = prefs.findLast( + predicate.isValidBskyAppStatePref, + ) || { + $type: 'app.bsky.actor.defs#bskyAppStatePref', + } - bskyAppStatePref = bskyAppStatePref || {} bskyAppStatePref.nuxs = (bskyAppStatePref.nuxs || []).filter((nux) => { return !ids.includes(nux.id) }) return prefs .filter((p) => !AppBskyActorDefs.isBskyAppStatePref(p)) - .concat([ - { - ...bskyAppStatePref, - $type: 'app.bsky.actor.defs#bskyAppStatePref', - }, - ]) + .concat(bskyAppStatePref) }) } @@ -1553,27 +1409,21 @@ export class Agent extends XrpcClient { private async updateHiddenPost(postUri: string, action: 'hide' | 'unhide') { await this.updatePreferences((prefs: AppBskyActorDefs.Preferences) => { - let pref = prefs.findLast( - (pref) => - AppBskyActorDefs.isHiddenPostsPref(pref) && - AppBskyActorDefs.validateHiddenPostsPref(pref).success, - ) - if (pref && AppBskyActorDefs.isHiddenPostsPref(pref)) { - pref.items = - action === 'hide' - ? Array.from(new Set([...pref.items, postUri])) - : pref.items.filter((uri) => uri !== postUri) - } else { - if (action === 'hide') { - pref = { - $type: 'app.bsky.actor.defs#hiddenPostsPref', - items: [postUri], - } - } + const pref = prefs.findLast(predicate.isValidHiddenPostsPref) || { + $type: 'app.bsky.actor.defs#hiddenPostsPref', + items: [], } + + const hiddenItems = new Set(pref.items) + + if (action === 'hide') hiddenItems.add(postUri) + else hiddenItems.delete(postUri) + + pref.items = [...hiddenItems] + return prefs .filter((p) => !AppBskyActorDefs.isInterestsPref(p)) - .concat([{ ...pref, $type: 'app.bsky.actor.defs#hiddenPostsPref' }]) + .concat(pref) }) } @@ -1588,26 +1438,19 @@ export class Agent extends XrpcClient { ): Promise<{ saved: string[]; pinned: string[] }> { let res await this.updatePreferences((prefs: AppBskyActorDefs.Preferences) => { - let feedsPref = prefs.findLast( - (pref) => - AppBskyActorDefs.isSavedFeedsPref(pref) && - AppBskyActorDefs.validateSavedFeedsPref(pref).success, - ) as AppBskyActorDefs.SavedFeedsPref | undefined - if (feedsPref) { - res = cb(feedsPref.saved, feedsPref.pinned) - feedsPref.saved = res.saved - feedsPref.pinned = res.pinned - } else { - res = cb([], []) - feedsPref = { - $type: 'app.bsky.actor.defs#savedFeedsPref', - saved: res.saved, - pinned: res.pinned, - } + const feedsPref = prefs.findLast(predicate.isValidSavedFeedsPref) || { + $type: 'app.bsky.actor.defs#savedFeedsPref', + saved: [], + pinned: [], } + + res = cb(feedsPref.saved, feedsPref.pinned) + feedsPref.saved = res.saved + feedsPref.pinned = res.pinned + return prefs .filter((pref) => !AppBskyActorDefs.isSavedFeedsPref(pref)) - .concat([feedsPref]) + .concat(feedsPref) }) return res } @@ -1620,16 +1463,8 @@ export class Agent extends XrpcClient { let maybeMutatedSavedFeeds: AppBskyActorDefs.SavedFeed[] = [] await this.updatePreferences((prefs: AppBskyActorDefs.Preferences) => { - let existingV2Pref = prefs.findLast( - (pref) => - AppBskyActorDefs.isSavedFeedsPrefV2(pref) && - AppBskyActorDefs.validateSavedFeedsPrefV2(pref).success, - ) as AppBskyActorDefs.SavedFeedsPrefV2 | undefined - let existingV1Pref = prefs.findLast( - (pref) => - AppBskyActorDefs.isSavedFeedsPref(pref) && - AppBskyActorDefs.validateSavedFeedsPref(pref).success, - ) as AppBskyActorDefs.SavedFeedsPref | undefined + let existingV2Pref = prefs.findLast(predicate.isValidSavedFeedsPrefV2) + let existingV1Pref = prefs.findLast(predicate.isValidSavedFeedsPref) if (existingV2Pref) { maybeMutatedSavedFeeds = cb(existingV2Pref.items) @@ -1730,11 +1565,7 @@ function remapLegacyLabels( function prefsArrayToLabelerDids( prefs: AppBskyActorDefs.Preferences, ): string[] { - const labelersPref = prefs.findLast( - (pref) => - AppBskyActorDefs.isLabelersPref(pref) && - AppBskyActorDefs.validateLabelersPref(pref).success, - ) + const labelersPref = prefs.findLast(predicate.isValidLabelersPref) let dids: string[] = [] if (labelersPref) { dids = (labelersPref as AppBskyActorDefs.LabelersPref).labelers.map( diff --git a/packages/api/src/index.ts b/packages/api/src/index.ts index 8ced8afe4ea..6a6bb8f3f92 100644 --- a/packages/api/src/index.ts +++ b/packages/api/src/index.ts @@ -15,6 +15,8 @@ export * from './const' export * from './util' export * from './client' export { schemas } from './client/lexicons' +export type { $Type, $Typed, Un$Typed } from './client/util' +export { asPredicate } from './client/util' export * from './rich-text/rich-text' export * from './rich-text/sanitization' export * from './rich-text/unicode' diff --git a/packages/api/src/mocker.ts b/packages/api/src/mocker.ts index 905268de8d4..1d42142b06b 100644 --- a/packages/api/src/mocker.ts +++ b/packages/api/src/mocker.ts @@ -7,6 +7,7 @@ import { AppBskyNotificationListNotifications, ComAtprotoLabelDefs, } from './client' +import { $Typed, Un$Typed } from './client/util' const FAKE_CID = 'bafyreiclp443lavogvhj3d2ob2cxbfuscni2k5jk7bebjzg7khl3esabwq' @@ -21,7 +22,7 @@ export const mock = { facets?: AppBskyFeedPost.Record['facets'] reply?: AppBskyFeedPost.ReplyRef embed?: AppBskyFeedPost.Record['embed'] - }): AppBskyFeedPost.Record { + }): $Typed { return { $type: 'app.bsky.feed.post', text, @@ -51,7 +52,7 @@ export const mock = { likeCount?: number viewer?: AppBskyFeedDefs.ViewerState labels?: ComAtprotoLabelDefs.Label[] - }): AppBskyFeedDefs.PostView { + }): $Typed { return { $type: 'app.bsky.feed.defs#postView', uri: `at://${author.did}/app.bsky.feed.post/fake`, @@ -76,7 +77,7 @@ export const mock = { record: AppBskyFeedPost.Record author: AppBskyActorDefs.ProfileViewBasic labels?: ComAtprotoLabelDefs.Label[] - }): AppBskyEmbedRecord.View { + }): $Typed { return { $type: 'app.bsky.embed.record#view', record: { @@ -108,7 +109,8 @@ export const mock = { did: `did:web:${handle}`, handle, displayName, - description, // technically not in ProfileViewBasic but useful in some cases + // @ts-expect-error technically not in ProfileViewBasic but useful in some cases + description, viewer, labels, } @@ -158,7 +160,7 @@ export const mock = { labels, }: { record: AppBskyFeedPost.Record - author: AppBskyActorDefs.ProfileViewBasic + author: Un$Typed labels?: ComAtprotoLabelDefs.Label[] }): AppBskyNotificationListNotifications.Notification { return { @@ -179,7 +181,7 @@ export const mock = { subjectDid, labels, }: { - author: AppBskyActorDefs.ProfileViewBasic + author: Un$Typed subjectDid: string labels?: ComAtprotoLabelDefs.Label[] }): AppBskyNotificationListNotifications.Notification { diff --git a/packages/api/src/moderation/decision.ts b/packages/api/src/moderation/decision.ts index bc33e100ca6..6f35f331479 100644 --- a/packages/api/src/moderation/decision.ts +++ b/packages/api/src/moderation/decision.ts @@ -32,9 +32,7 @@ export class ModerationDecision { static merge( ...decisions: (ModerationDecision | undefined)[] ): ModerationDecision { - const decisionsFiltered: ModerationDecision[] = decisions.filter( - (v) => !!v, - ) as ModerationDecision[] + const decisionsFiltered = decisions.filter((v) => v != null) const decision = new ModerationDecision() if (decisionsFiltered[0]) { decision.did = decisionsFiltered[0].did diff --git a/packages/api/src/moderation/mutewords.ts b/packages/api/src/moderation/mutewords.ts index 143e9c0bb52..f88a1af1cca 100644 --- a/packages/api/src/moderation/mutewords.ts +++ b/packages/api/src/moderation/mutewords.ts @@ -34,7 +34,7 @@ export function hasMutedWord({ facets?: AppBskyRichtextFacet.Main[] outlineTags?: string[] languages?: string[] - actor?: AppBskyActorDefs.ProfileView + actor?: AppBskyActorDefs.ProfileView | AppBskyActorDefs.ProfileViewBasic }) { const exception = LANGUAGE_EXCEPTIONS.includes(languages?.[0] || '') const tags = ([] as string[]) diff --git a/packages/api/src/moderation/subjects/post.ts b/packages/api/src/moderation/subjects/post.ts index 8d0b7c6db5b..2075a55f696 100644 --- a/packages/api/src/moderation/subjects/post.ts +++ b/packages/api/src/moderation/subjects/post.ts @@ -11,10 +11,23 @@ import { hasMutedWord } from '../mutewords' import { ModerationOpts, ModerationSubjectPost } from '../types' import { decideAccount } from './account' import { decideProfile } from './profile' +import { $Typed } from '../../client/util' export function decidePost( subject: ModerationSubjectPost, opts: ModerationOpts, +): ModerationDecision { + return ModerationDecision.merge( + decideSubject(subject, opts), + decideEmbed(subject.embed, opts)?.downgrade(), + decideAccount(subject.author, opts), + decideProfile(subject.author, opts), + ) +} + +function decideSubject( + subject: ModerationSubjectPost, + opts: ModerationOpts, ): ModerationDecision { const acc = new ModerationDecision() @@ -30,35 +43,48 @@ export function decidePost( acc.addMutedWord(checkMutedWords(subject, opts.prefs.mutedWords)) } - let embedAcc - if (subject.embed) { - if (AppBskyEmbedRecord.isViewRecord(subject.embed.record)) { + return acc +} + +function decideEmbed( + embed: + | undefined + | $Typed + | $Typed + | { $type: string }, + opts: ModerationOpts, +) { + if (embed) { + if ( + (AppBskyEmbedRecord.isView(embed) || + AppBskyEmbedRecordWithMedia.isView(embed)) && + AppBskyEmbedRecord.isViewRecord(embed.record) + ) { // quote post - embedAcc = decideQuotedPost(subject.embed.record, opts) + return decideQuotedPost(embed.record, opts) } else if ( - AppBskyEmbedRecordWithMedia.isView(subject.embed) && - AppBskyEmbedRecord.isViewRecord(subject.embed.record.record) + AppBskyEmbedRecordWithMedia.isView(embed) && + AppBskyEmbedRecord.isViewRecord(embed.record.record) ) { // quoted post with media - embedAcc = decideQuotedPost(subject.embed.record.record, opts) - } else if (AppBskyEmbedRecord.isViewBlocked(subject.embed.record)) { + return decideQuotedPost(embed.record.record, opts) + } else if ( + (AppBskyEmbedRecord.isView(embed) || + AppBskyEmbedRecordWithMedia.isView(embed)) && + AppBskyEmbedRecord.isViewBlocked(embed.record) + ) { // blocked quote post - embedAcc = decideBlockedQuotedPost(subject.embed.record, opts) + return decideBlockedQuotedPost(embed.record, opts) } else if ( - AppBskyEmbedRecordWithMedia.isView(subject.embed) && - AppBskyEmbedRecord.isViewBlocked(subject.embed.record.record) + AppBskyEmbedRecordWithMedia.isView(embed) && + AppBskyEmbedRecord.isViewBlocked(embed.record.record) ) { // blocked quoted post with media - embedAcc = decideBlockedQuotedPost(subject.embed.record.record, opts) + return decideBlockedQuotedPost(embed.record.record, opts) } } - return ModerationDecision.merge( - acc, - embedAcc?.downgrade(), - decideAccount(subject.author, opts), - decideProfile(subject.author, opts), - ) + return undefined } function decideQuotedPost( @@ -117,6 +143,7 @@ function checkHiddenPost( } if (subject.embed) { if ( + AppBskyEmbedRecord.isView(subject.embed) && AppBskyEmbedRecord.isViewRecord(subject.embed.record) && hiddenPosts.includes(subject.embed.record.uri) ) { @@ -144,31 +171,29 @@ function checkMutedWords( const postAuthor = subject.author if (AppBskyFeedPost.isRecord(subject.record)) { + const post = subject.record as AppBskyFeedPost.Record // post text if ( hasMutedWord({ mutedWords, - text: subject.record.text, - facets: subject.record.facets, - outlineTags: subject.record.tags, - languages: subject.record.langs, + text: post.text, + facets: post.facets, + outlineTags: post.tags, + languages: post.langs, actor: postAuthor, }) ) { return true } - if ( - subject.record.embed && - AppBskyEmbedImages.isMain(subject.record.embed) - ) { + if (post.embed && AppBskyEmbedImages.isMain(post.embed)) { // post images - for (const image of subject.record.embed.images) { + for (const image of post.embed.images) { if ( hasMutedWord({ mutedWords, text: image.alt, - languages: subject.record.langs, + languages: post.langs, actor: postAuthor, }) ) { @@ -178,12 +203,17 @@ function checkMutedWords( } } - if (subject.embed) { + const { embed } = subject + if (embed) { // quote post - if (AppBskyEmbedRecord.isViewRecord(subject.embed.record)) { - if (AppBskyFeedPost.isRecord(subject.embed.record.value)) { - const embeddedPost = subject.embed.record.value - const embedAuthor = subject.embed.record.author + if ( + (AppBskyEmbedRecord.isView(embed) || + AppBskyEmbedRecordWithMedia.isView(embed)) && + AppBskyEmbedRecord.isViewRecord(embed.record) + ) { + if (AppBskyFeedPost.isRecord(embed.record.value)) { + const embeddedPost = embed.record.value as AppBskyFeedPost.Record + const embedAuthor = embed.record.author // quoted post text if ( @@ -267,8 +297,8 @@ function checkMutedWords( } } // link card - else if (AppBskyEmbedExternal.isView(subject.embed)) { - const { external } = subject.embed + else if (AppBskyEmbedExternal.isView(embed)) { + const { external } = embed if ( hasMutedWord({ mutedWords, @@ -282,14 +312,14 @@ function checkMutedWords( } // quote post with media else if ( - AppBskyEmbedRecordWithMedia.isView(subject.embed) && - AppBskyEmbedRecord.isViewRecord(subject.embed.record.record) + AppBskyEmbedRecordWithMedia.isView(embed) && + AppBskyEmbedRecord.isViewRecord(embed.record.record) ) { - const embedAuthor = subject.embed.record.record.author + const embedAuthor = embed.record.record.author // quoted post text - if (AppBskyFeedPost.isRecord(subject.embed.record.record.value)) { - const post = subject.embed.record.record.value + if (AppBskyFeedPost.isRecord(embed.record.record.value)) { + const post = embed.record.record.value as AppBskyFeedPost.Record if ( hasMutedWord({ mutedWords, @@ -305,14 +335,14 @@ function checkMutedWords( } // quoted post images - if (AppBskyEmbedImages.isView(subject.embed.media)) { - for (const image of subject.embed.media.images) { + if (AppBskyEmbedImages.isView(embed.media)) { + for (const image of embed.media.images) { if ( hasMutedWord({ mutedWords, text: image.alt, languages: AppBskyFeedPost.isRecord(subject.record) - ? subject.record.langs + ? (subject.record as AppBskyFeedPost.Record).langs : [], actor: embedAuthor, }) @@ -322,8 +352,8 @@ function checkMutedWords( } } - if (AppBskyEmbedExternal.isView(subject.embed.media)) { - const { external } = subject.embed.media + if (AppBskyEmbedExternal.isView(embed.media)) { + const { external } = embed.media if ( hasMutedWord({ mutedWords, diff --git a/packages/api/src/moderation/subjects/user-list.ts b/packages/api/src/moderation/subjects/user-list.ts index f2d5f408702..461b469b9fe 100644 --- a/packages/api/src/moderation/subjects/user-list.ts +++ b/packages/api/src/moderation/subjects/user-list.ts @@ -11,7 +11,11 @@ export function decideUserList( ): ModerationDecision { const acc = new ModerationDecision() - const creator = isProfile(subject.creator) ? subject.creator : undefined + const creator = + // Note: ListViewBasic should not contain a creator field, but let's support it anyway + 'creator' in subject && isProfile(subject.creator) + ? subject.creator + : undefined if (creator) { acc.setDid(creator.did) diff --git a/packages/api/src/moderation/types.ts b/packages/api/src/moderation/types.ts index 6d2371b113c..0b3df23ee8b 100644 --- a/packages/api/src/moderation/types.ts +++ b/packages/api/src/moderation/types.ts @@ -4,6 +4,7 @@ import { AppBskyGraphDefs, AppBskyNotificationListNotifications, ComAtprotoLabelDefs, + ChatBskyActorDefs, } from '../client/index' import { KnownLabelValue } from './const/labels' @@ -87,6 +88,7 @@ export type ModerationSubjectProfile = | AppBskyActorDefs.ProfileViewBasic | AppBskyActorDefs.ProfileView | AppBskyActorDefs.ProfileViewDetailed + | ChatBskyActorDefs.ProfileViewBasic export type ModerationSubjectPost = AppBskyFeedDefs.PostView diff --git a/packages/api/src/moderation/util.ts b/packages/api/src/moderation/util.ts index 4c4c4eb210e..a76b37f8b9f 100644 --- a/packages/api/src/moderation/util.ts +++ b/packages/api/src/moderation/util.ts @@ -4,6 +4,7 @@ import { AppBskyLabelerDefs, ComAtprotoLabelDefs, } from '../client' +import { asPredicate } from '../client/util' import { InterpretedLabelValueDefinition, LabelPreference, @@ -103,10 +104,7 @@ export function interpretLabelValueDefinitions( labelerView: AppBskyLabelerDefs.LabelerViewDetailed, ): InterpretedLabelValueDefinition[] { return (labelerView.policies?.labelValueDefinitions || []) - .filter( - (labelValDef) => - ComAtprotoLabelDefs.validateLabelValueDefinition(labelValDef).success, - ) + .filter(asPredicate(ComAtprotoLabelDefs.validateLabelValueDefinition)) .map((labelValDef) => interpretLabelValueDefinition(labelValDef, labelerView.creator.did), ) diff --git a/packages/api/src/predicate.ts b/packages/api/src/predicate.ts new file mode 100644 index 00000000000..3b24542a8b9 --- /dev/null +++ b/packages/api/src/predicate.ts @@ -0,0 +1,43 @@ +import { AppBskyActorDefs, AppBskyActorProfile } from './client/index' +import { asPredicate } from './client/util' + +export const isValidProfile = asPredicate(AppBskyActorProfile.validateRecord) +export const isValidAdultContentPref = asPredicate( + AppBskyActorDefs.validateAdultContentPref, +) +export const isValidBskyAppStatePref = asPredicate( + AppBskyActorDefs.validateBskyAppStatePref, +) +export const isValidContentLabelPref = asPredicate( + AppBskyActorDefs.validateContentLabelPref, +) +export const isValidFeedViewPref = asPredicate( + AppBskyActorDefs.validateFeedViewPref, +) +export const isValidHiddenPostsPref = asPredicate( + AppBskyActorDefs.validateHiddenPostsPref, +) +export const isValidInterestsPref = asPredicate( + AppBskyActorDefs.validateInterestsPref, +) +export const isValidLabelersPref = asPredicate( + AppBskyActorDefs.validateLabelersPref, +) +export const isValidMutedWordsPref = asPredicate( + AppBskyActorDefs.validateMutedWordsPref, +) +export const isValidPersonalDetailsPref = asPredicate( + AppBskyActorDefs.validatePersonalDetailsPref, +) +export const isValidPostInteractionSettingsPref = asPredicate( + AppBskyActorDefs.validatePostInteractionSettingsPref, +) +export const isValidSavedFeedsPref = asPredicate( + AppBskyActorDefs.validateSavedFeedsPref, +) +export const isValidSavedFeedsPrefV2 = asPredicate( + AppBskyActorDefs.validateSavedFeedsPrefV2, +) +export const isValidThreadViewPref = asPredicate( + AppBskyActorDefs.validateThreadViewPref, +) diff --git a/packages/api/src/rich-text/rich-text.ts b/packages/api/src/rich-text/rich-text.ts index 44f48b5f4b8..ba46e570356 100644 --- a/packages/api/src/rich-text/rich-text.ts +++ b/packages/api/src/rich-text/rich-text.ts @@ -122,11 +122,7 @@ export class RichTextSegment { ) {} get link(): FacetLink | undefined { - const link = this.facet?.features.find(AppBskyRichtextFacet.isLink) - if (AppBskyRichtextFacet.isLink(link)) { - return link - } - return undefined + return this.facet?.features.find(AppBskyRichtextFacet.isLink) } isLink() { @@ -134,11 +130,7 @@ export class RichTextSegment { } get mention(): FacetMention | undefined { - const mention = this.facet?.features.find(AppBskyRichtextFacet.isMention) - if (AppBskyRichtextFacet.isMention(mention)) { - return mention - } - return undefined + return this.facet?.features.find(AppBskyRichtextFacet.isMention) } isMention() { @@ -146,11 +138,7 @@ export class RichTextSegment { } get tag(): FacetTag | undefined { - const tag = this.facet?.features.find(AppBskyRichtextFacet.isTag) - if (AppBskyRichtextFacet.isTag(tag)) { - return tag - } - return undefined + return this.facet?.features.find(AppBskyRichtextFacet.isTag) } isTag() { diff --git a/packages/api/tests/atp-agent.test.ts b/packages/api/tests/atp-agent.test.ts index 86fbd8ac18e..9d266cc61c1 100644 --- a/packages/api/tests/atp-agent.test.ts +++ b/packages/api/tests/atp-agent.test.ts @@ -12,6 +12,7 @@ import { savedFeedsToUriArrays, validateSavedFeed, } from '../src/util' +import { asPredicate } from '../src/client/util' describe('agent', () => { let network: TestNetworkNoAppView @@ -2320,38 +2321,22 @@ describe('agent', () => { async function addLegacyMutedWord(mutedWord: AppBskyActorDefs.MutedWord) { await updatePreferences(agent, (prefs) => { - let mutedWordsPref = prefs.findLast( - (pref) => - AppBskyActorDefs.isMutedWordsPref(pref) && - AppBskyActorDefs.validateMutedWordsPref(pref).success, - ) + const mutedWordsPref = prefs.findLast( + asPredicate(AppBskyActorDefs.validateMutedWordsPref), + ) || { + $type: 'app.bsky.actor.defs#mutedWordsPref', + items: [], + } - const newMutedWord: AppBskyActorDefs.MutedWord = { + mutedWordsPref.items.push({ value: mutedWord.value, targets: mutedWord.targets, actorTarget: 'all', - } - - if ( - mutedWordsPref && - AppBskyActorDefs.isMutedWordsPref(mutedWordsPref) - ) { - mutedWordsPref.items.push(newMutedWord) - } else { - // if the pref doesn't exist, create it - mutedWordsPref = { - items: [newMutedWord], - } - } + }) return prefs .filter((p) => !AppBskyActorDefs.isMutedWordsPref(p)) - .concat([ - { - ...mutedWordsPref, - $type: 'app.bsky.actor.defs#mutedWordsPref', - }, - ]) + .concat([mutedWordsPref]) }) } @@ -3350,6 +3335,7 @@ describe('agent', () => { await agent.bskyAppSetActiveProgressGuide({ guide: 'test-guide', + // @ts-expect-error unspecced field numThings: 0, }) await expect(agent.getPreferences()).resolves.toHaveProperty( @@ -3358,6 +3344,7 @@ describe('agent', () => { ) await agent.bskyAppSetActiveProgressGuide({ guide: 'test-guide', + // @ts-expect-error unspecced field numThings: 1, }) await expect(agent.getPreferences()).resolves.toHaveProperty( @@ -3436,6 +3423,7 @@ describe('agent', () => { // @ts-expect-error expect(() => agent.bskyAppUpsertNux({ name: 'a' })).rejects.toThrow() expect(() => + // @ts-expect-error agent.bskyAppUpsertNux({ id: 'a', completed: false, foo: 'bar' }), ).rejects.toThrow() }) diff --git a/packages/api/tests/moderation-custom-labels.test.ts b/packages/api/tests/moderation-custom-labels.test.ts index 0af184aeb06..366e7a486ce 100644 --- a/packages/api/tests/moderation-custom-labels.test.ts +++ b/packages/api/tests/moderation-custom-labels.test.ts @@ -223,6 +223,7 @@ describe('Moderation: custom labels', () => { res = moderatePost( mock.postView({ record: { + $type: 'app.bsky.feed.post', text: 'Hello', createdAt: new Date().toISOString(), }, diff --git a/packages/api/tests/moderation-mutewords.test.ts b/packages/api/tests/moderation-mutewords.test.ts index b8eb014ee56..3aa15d2da6d 100644 --- a/packages/api/tests/moderation-mutewords.test.ts +++ b/packages/api/tests/moderation-mutewords.test.ts @@ -831,6 +831,7 @@ describe(`hasMutedWord`, () => { features: [ { $type: 'com.example.richtext.facet#other', + // @ts-expect-error foo: 'bar', }, { diff --git a/packages/api/tests/moderation-prefs.test.ts b/packages/api/tests/moderation-prefs.test.ts index a9ce47314c5..a859035117b 100644 --- a/packages/api/tests/moderation-prefs.test.ts +++ b/packages/api/tests/moderation-prefs.test.ts @@ -1,5 +1,6 @@ import { TestNetworkNoAppView } from '@atproto/dev-env' import { DEFAULT_LABEL_SETTINGS } from '../src' +import { isContentLabelPref } from '../src/client/types/app/bsky/actor/defs' import './util/moderation-behavior' describe('agent', () => { @@ -351,7 +352,7 @@ describe('agent', () => { const a = await agent.app.bsky.actor.getPreferences({}) const nsfwSettings = a.data.preferences.filter( - (pref) => pref.label === 'nsfw', + (pref) => isContentLabelPref(pref) && pref.label === 'nsfw', ) expect(nsfwSettings.length).toEqual(1) }) diff --git a/packages/api/tests/moderation-quoteposts.test.ts b/packages/api/tests/moderation-quoteposts.test.ts index 2041f190396..4ff94abae39 100644 --- a/packages/api/tests/moderation-quoteposts.test.ts +++ b/packages/api/tests/moderation-quoteposts.test.ts @@ -193,6 +193,7 @@ describe('Moderation: custom labels', () => { const post = mock.postView({ record: { + $type: 'app.bsky.feed.post', text: 'Hello', createdAt: new Date().toISOString(), }, diff --git a/packages/api/tests/moderation.test.ts b/packages/api/tests/moderation.test.ts index f85e4a5325a..6c0a7f9139a 100644 --- a/packages/api/tests/moderation.test.ts +++ b/packages/api/tests/moderation.test.ts @@ -177,6 +177,7 @@ describe('Moderation', () => { const res1 = moderatePost( mock.postView({ record: { + $type: 'app.bsky.feed.post', text: 'Hello', createdAt: new Date().toISOString(), }, @@ -214,6 +215,7 @@ describe('Moderation', () => { const res1 = moderatePost( mock.postView({ record: { + $type: 'app.bsky.feed.post', text: 'Hello', createdAt: new Date().toISOString(), }, @@ -293,6 +295,7 @@ describe('Moderation', () => { const res = moderatePost( mock.postView({ record: { + $type: 'app.bsky.feed.post', text: 'Hello', createdAt: new Date().toISOString(), }, @@ -354,6 +357,7 @@ describe('Moderation', () => { const res = moderatePost( mock.postView({ record: { + $type: 'app.bsky.feed.post', text: 'Hello', createdAt: new Date().toISOString(), }, @@ -430,6 +434,7 @@ describe('Moderation', () => { const res = moderatePost( mock.postView({ record: { + $type: 'app.bsky.feed.post', text: 'Hello', createdAt: new Date().toISOString(), }, @@ -518,6 +523,7 @@ describe('Moderation', () => { const res1 = moderatePost( mock.postView({ record: { + $type: 'app.bsky.feed.post', text: 'Hello', createdAt: new Date().toISOString(), }, @@ -549,6 +555,7 @@ describe('Moderation', () => { const res2 = moderatePost( mock.postView({ record: { + $type: 'app.bsky.feed.post', text: 'Hello', createdAt: new Date().toISOString(), }, @@ -580,6 +587,7 @@ describe('Moderation', () => { const res3 = moderatePost( mock.postView({ record: { + $type: 'app.bsky.feed.post', text: 'Hello', createdAt: new Date().toISOString(), }, @@ -645,6 +653,7 @@ describe('Moderation', () => { const res = moderatePost( mock.postView({ record: { + $type: 'app.bsky.feed.post', text: 'Hello', createdAt: new Date().toISOString(), }, @@ -698,6 +707,7 @@ describe('Moderation', () => { const res = moderatePost( mock.postView({ record: { + $type: 'app.bsky.feed.post', text: 'Hello', createdAt: new Date().toISOString(), }, diff --git a/packages/api/tests/rich-text-detection.test.ts b/packages/api/tests/rich-text-detection.test.ts index c87b5eab0a6..bb55f8222b1 100644 --- a/packages/api/tests/rich-text-detection.test.ts +++ b/packages/api/tests/rich-text-detection.test.ts @@ -1,5 +1,9 @@ import { AtpAgent, RichText, RichTextSegment } from '../src' -import { isTag } from '../src/client/types/app/bsky/richtext/facet' +import { + isLink, + isMention, + isTag, +} from '../src/client/types/app/bsky/richtext/facet' describe('detectFacets', () => { const agent = new AtpAgent({ service: 'http://localhost' }) @@ -374,12 +378,8 @@ function segmentToOutput(segment: RichTextSegment): string[] { return [ segment.text, segment.facet?.features.map((f) => { - if (f.did) { - return String(f.did) - } - if (f.uri) { - return String(f.uri) - } + if (isMention(f)) return f.did + if (isLink(f)) return f.uri return undefined })?.[0] || '', ] diff --git a/packages/bsky/src/api/app/bsky/feed/getFeed.ts b/packages/bsky/src/api/app/bsky/feed/getFeed.ts index e61ac0e9474..b3f3a528782 100644 --- a/packages/bsky/src/api/app/bsky/feed/getFeed.ts +++ b/packages/bsky/src/api/app/bsky/feed/getFeed.ts @@ -18,6 +18,7 @@ import { FeedItem } from '../../../../hydration/feed' import { HydrateCtx } from '../../../../hydration/hydrator' import { Server } from '../../../../lexicon' import { ids } from '../../../../lexicon/lexicons' +import { isSkeletonReasonRepost } from '../../../../lexicon/types/app/bsky/feed/defs' import { QueryParams as GetFeedParams } from '../../../../lexicon/types/app/bsky/feed/getFeed' import { OutputSchema as SkeletonOutput } from '../../../../lexicon/types/app/bsky/feed/getFeedSkeleton' import { @@ -41,10 +42,8 @@ export default function (server: Server, ctx: AppContext) { auth: ctx.authVerifier.standardOptionalParameterized({ lxmCheck: (method) => { return ( - method !== undefined && - [ids.AppBskyFeedGetFeedSkeleton, ids.AppBskyFeedGetFeed].includes( - method, - ) + method === ids.AppBskyFeedGetFeedSkeleton || + method === ids.AppBskyFeedGetFeed ) }, skipAudCheck: true, @@ -254,10 +253,9 @@ const skeletonFromFeedGen = async ( const { feed: feedSkele, ...skele } = skeleton const feedItems = feedSkele.slice(0, params.limit).map((item) => ({ post: { uri: item.post }, - repost: - typeof item.reason?.repost === 'string' - ? { uri: item.reason.repost } - : undefined, + repost: isSkeletonReasonRepost(item.reason) + ? { uri: item.reason.repost } + : undefined, feedContext: item.feedContext, })) diff --git a/packages/bsky/src/api/app/bsky/labeler/getServices.ts b/packages/bsky/src/api/app/bsky/labeler/getServices.ts index c40b6a4b13f..df98a862234 100644 --- a/packages/bsky/src/api/app/bsky/labeler/getServices.ts +++ b/packages/bsky/src/api/app/bsky/labeler/getServices.ts @@ -21,15 +21,15 @@ export default function (server: Server, ctx: AppContext) { const view = ctx.views.labelerDetailed(did, hydration) if (!view) return return { - $type: 'app.bsky.labeler.defs#labelerViewDetailed', ...view, + $type: 'app.bsky.labeler.defs#labelerViewDetailed', } } else { const view = ctx.views.labeler(did, hydration) if (!view) return return { - $type: 'app.bsky.labeler.defs#labelerView', ...view, + $type: 'app.bsky.labeler.defs#labelerView', } } }) diff --git a/packages/bsky/src/data-plane/server/indexing/plugins/post.ts b/packages/bsky/src/data-plane/server/indexing/plugins/post.ts index 80ce451354e..d9bb864713f 100644 --- a/packages/bsky/src/data-plane/server/indexing/plugins/post.ts +++ b/packages/bsky/src/data-plane/server/indexing/plugins/post.ts @@ -18,11 +18,13 @@ import { isLink, isMention, } from '../../../../lexicon/types/app/bsky/richtext/facet' +import { $Typed } from '../../../../lexicon/util' import { postUriToPostgateUri, postUriToThreadgateUri, uriToDid, } from '../../../../util/uris' +import { RecordWithMedia } from '../../../../views/types' import { parsePostgate } from '../../../../views/util' import { BackgroundQueue } from '../../background' import { Database } from '../../db' @@ -521,7 +523,13 @@ export const makePlugin = ( export default makePlugin -function separateEmbeds(embed: PostRecord['embed']) { +function separateEmbeds( + embed: PostRecord['embed'], +): Array< + | RecordWithMedia['media'] + | $Typed + | NonNullable +> { if (!embed) { return [] } diff --git a/packages/bsky/src/hydration/graph.ts b/packages/bsky/src/hydration/graph.ts index 148a7e0da3b..fdf586a5bec 100644 --- a/packages/bsky/src/hydration/graph.ts +++ b/packages/bsky/src/hydration/graph.ts @@ -176,7 +176,10 @@ export class GraphHydrator { }, new HydrationMap()) } - async getBlocks(uris: string[], includeTakedowns = false): Promise { + async getBlocks( + uris: string[], + includeTakedowns = false, + ): Promise> { if (!uris.length) return new HydrationMap() const res = await this.dataplane.getBlockRecords({ uris }) return uris.reduce((acc, uri, i) => { diff --git a/packages/bsky/src/hydration/hydrator.ts b/packages/bsky/src/hydration/hydrator.ts index 788b9c52806..b91874fb796 100644 --- a/packages/bsky/src/hydration/hydrator.ts +++ b/packages/bsky/src/hydration/hydrator.ts @@ -3,6 +3,7 @@ import { mapDefined } from '@atproto/common' import { AtUri } from '@atproto/syntax' import { DataPlaneClient } from '../data-plane/client' import { ids } from '../lexicon/lexicons' +import { Record as ProfileRecord } from '../lexicon/types/app/bsky/actor/profile' import { isMain as isEmbedRecord } from '../lexicon/types/app/bsky/embed/record' import { isMain as isEmbedRecordWithMedia } from '../lexicon/types/app/bsky/embed/recordWithMedia' import { isListRule as isThreadgateListRule } from '../lexicon/types/app/bsky/feed/threadgate' @@ -998,10 +999,7 @@ export class Hydrator { // ad-hoc record hydration // in com.atproto.repo.getRecord - async getRecord( - uri: string, - includeTakedowns = false, - ): Promise> | undefined> { + async getRecord(uri: string, includeTakedowns = false) { const parsed = new AtUri(uri) const collection = parsed.collection if (collection === ids.AppBskyFeedPost) { @@ -1081,13 +1079,15 @@ export class Hydrator { did, ) if (!actor?.profile || !actor?.profileCid) return undefined - return { + const recordInfo: RecordInfo = { record: actor.profile, cid: actor.profileCid, sortedAt: actor.sortedAt ?? new Date(0), // @NOTE will be present since profile record is present indexedAt: actor.indexedAt ?? new Date(0), // @NOTE will be present since profile record is present takedownRef: actor.profileTakedownRef, } + + return recordInfo } } diff --git a/packages/bsky/src/hydration/util.ts b/packages/bsky/src/hydration/util.ts index fae89596044..beb7b774c30 100644 --- a/packages/bsky/src/hydration/util.ts +++ b/packages/bsky/src/hydration/util.ts @@ -18,7 +18,9 @@ export interface Merges { merge(map: T): this } -export type RecordInfo = { +type UnknownRecord = { $type: string; [x: string]: unknown } + +export type RecordInfo = { record: T cid: string sortedAt: Date @@ -56,7 +58,7 @@ export const mergeManyMaps = (...maps: HydrationMap[]) => { export type ItemRef = { uri: string; cid?: string } -export const parseRecord = ( +export const parseRecord = ( entry: Record, includeTakedowns: boolean, ): RecordInfo | undefined => { diff --git a/packages/bsky/src/views/index.ts b/packages/bsky/src/views/index.ts index d28b1bc7cb2..ec40166e4dd 100644 --- a/packages/bsky/src/views/index.ts +++ b/packages/bsky/src/views/index.ts @@ -1,18 +1,24 @@ import { mapDefined } from '@atproto/common' import { AtUri, INVALID_HANDLE, normalizeDatetimeAlways } from '@atproto/syntax' import { ProfileViewerState } from '../hydration/actor' -import { FeedItem, Post, Repost } from '../hydration/feed' +import { FeedItem, Like, Post, Repost } from '../hydration/feed' +import { Follow } from '../hydration/graph' import { HydrationState } from '../hydration/hydrator' import { Label } from '../hydration/label' import { RecordInfo } from '../hydration/util' import { ImageUriBuilder } from '../image/uri' import { ids } from '../lexicon/lexicons' import { + KnownFollowers, ProfileView, ProfileViewBasic, ProfileViewDetailed, ViewerState as ProfileViewer, } from '../lexicon/types/app/bsky/actor/defs' +import { + Record as ProfileRecord, + isRecord as isProfileRecord, +} from '../lexicon/types/app/bsky/actor/profile' import { BlockedPost, FeedViewPost, @@ -26,7 +32,12 @@ import { ThreadgateView, isPostView, } from '../lexicon/types/app/bsky/feed/defs' -import { isRecord as isPostRecord } from '../lexicon/types/app/bsky/feed/post' +import { Record as LikeRecord } from '../lexicon/types/app/bsky/feed/like' +import { + Record as PostRecord, + isRecord as isPostRecord, +} from '../lexicon/types/app/bsky/feed/post' +import { Record as RepostRecord } from '../lexicon/types/app/bsky/feed/repost' import { isListRule } from '../lexicon/types/app/bsky/feed/threadgate' import { ListView, @@ -34,11 +45,17 @@ import { StarterPackView, StarterPackViewBasic, } from '../lexicon/types/app/bsky/graph/defs' +import { Record as FollowRecord } from '../lexicon/types/app/bsky/graph/follow' import { LabelerView, LabelerViewDetailed, } from '../lexicon/types/app/bsky/labeler/defs' +import { + Record as LabelerRecord, + isRecord as isLabelerRecord, +} from '../lexicon/types/app/bsky/labeler/service' import { isSelfLabels } from '../lexicon/types/com/atproto/label/defs' +import { $Typed, Un$Typed } from '../lexicon/util' import { Notification } from '../proto/bsky_pb' import { postUriToPostgateUri, @@ -181,7 +198,7 @@ export class Views { profileDetailed( did: string, state: HydrationState, - ): ProfileViewDetailed | undefined { + ): Un$Typed | undefined { const actor = state.actors?.get(did) if (!actor) return const baseView = this.profile(did, state) @@ -223,7 +240,10 @@ export class Views { } } - profile(did: string, state: HydrationState): ProfileView | undefined { + profile( + did: string, + state: HydrationState, + ): Un$Typed | undefined { const actor = state.actors?.get(did) if (!actor) return const basicView = this.profileBasic(did, state) @@ -244,7 +264,7 @@ export class Views { profileBasic( did: string, state: HydrationState, - ): ProfileViewBasic | undefined { + ): Un$Typed | undefined { const actor = state.actors?.get(did) if (!actor) return const profileUri = AtUri.make( @@ -332,7 +352,10 @@ export class Views { } } - knownFollowers(did: string, state: HydrationState) { + knownFollowers( + did: string, + state: HydrationState, + ): KnownFollowers | undefined { const knownFollowers = state.knownFollowers?.get(did) if (!knownFollowers) return const blocks = state.bidirectionalBlocks?.get(did) @@ -369,7 +392,7 @@ export class Views { // Graph // ------------ - list(uri: string, state: HydrationState): ListView | undefined { + list(uri: string, state: HydrationState): Un$Typed | undefined { const creatorDid = creatorFromUri(uri) const list = state.lists?.get(uri) if (!list) return @@ -387,7 +410,10 @@ export class Views { } } - listBasic(uri: string, state: HydrationState): ListViewBasic | undefined { + listBasic( + uri: string, + state: HydrationState, + ): Un$Typed | undefined { const list = state.lists?.get(uri) if (!list) { return undefined @@ -423,7 +449,7 @@ export class Views { starterPackBasic( uri: string, state: HydrationState, - ): StarterPackViewBasic | undefined { + ): Un$Typed | undefined { const sp = state.starterPacks?.get(uri) if (!sp) return const parsedUri = new AtUri(uri) @@ -443,7 +469,10 @@ export class Views { } } - starterPack(uri: string, state: HydrationState): StarterPackView | undefined { + starterPack( + uri: string, + state: HydrationState, + ): Un$Typed | undefined { const sp = state.starterPacks?.get(uri) const basicView = this.starterPackBasic(uri, state) if (!sp || !basicView) return @@ -470,14 +499,37 @@ export class Views { // Labels // ------------ - selfLabels(details: { + selfLabels({ + uri, + cid, + record, + }: { uri?: string cid?: string - record?: Record + record?: + | PostRecord + | LikeRecord + | RepostRecord + | FollowRecord + | ProfileRecord + | LabelerRecord }): Label[] { - const { uri, cid, record } = details if (!uri || !cid || !record) return [] - if (!isSelfLabels(record.labels)) return [] + + // Only these have a "labels" property: + if ( + !isPostRecord(record) && + !isProfileRecord(record) && + !isLabelerRecord(record) + ) { + return [] + } + + // Ignore if no labels defines + if (!isSelfLabels(record.labels) || !record.labels.values.length) { + return [] + } + const src = creatorFromUri(uri) // record creator const cts = typeof record.createdAt === 'string' @@ -488,7 +540,10 @@ export class Views { }) } - labeler(did: string, state: HydrationState): LabelerView | undefined { + labeler( + did: string, + state: HydrationState, + ): Un$Typed | undefined { const labeler = state.labelers?.get(did) if (!labeler) return const creator = this.profile(did, state) @@ -524,7 +579,7 @@ export class Views { labelerDetailed( did: string, state: HydrationState, - ): LabelerViewDetailed | undefined { + ): Un$Typed | undefined { const baseView = this.labeler(did, state) if (!baseView) return const record = state.labelers?.get(did) @@ -572,7 +627,10 @@ export class Views { } } - feedGenerator(uri: string, state: HydrationState): GeneratorView | undefined { + feedGenerator( + uri: string, + state: HydrationState, + ): Un$Typed | undefined { const feedgen = state.feedgens?.get(uri) if (!feedgen) return const creatorDid = creatorFromUri(uri) @@ -609,7 +667,10 @@ export class Views { } } - threadgate(uri: string, state: HydrationState): ThreadgateView | undefined { + threadgate( + uri: string, + state: HydrationState, + ): Un$Typed | undefined { const gate = state.threadgates?.get(uri) if (!gate) return return { @@ -623,7 +684,11 @@ export class Views { } } - post(uri: string, state: HydrationState, depth = 0): PostView | undefined { + post( + uri: string, + state: HydrationState, + depth = 0, + ): Un$Typed | undefined { const post = state.posts?.get(uri) if (!post) return const parsedUri = new AtUri(uri) @@ -675,9 +740,9 @@ export class Views { feedViewPost( item: FeedItem, state: HydrationState, - ): FeedViewPost | undefined { + ): Un$Typed | undefined { const postInfo = state.posts?.get(item.post.uri) - let reason: ReasonRepost | ReasonPin | undefined + let reason: $Typed | $Typed | undefined if (item.authorPinned) { reason = this.reasonPin() } else if (item.repost) { @@ -699,7 +764,7 @@ export class Views { } } - replyRef(uri: string, state: HydrationState): ReplyRef | undefined { + replyRef(uri: string, state: HydrationState): Un$Typed | undefined { const postRecord = state.posts?.get(uri.toString())?.record if (!postRecord?.reply) return let root = this.maybePost(postRecord.reply.root.uri, state) @@ -717,8 +782,13 @@ export class Views { } } let grandparentAuthor: ProfileViewBasic | undefined - if (isPostRecord(parent.record) && parent.record.reply) { + if ( + isPostView(parent) && + isPostRecord(parent.record) && + parent.record.reply + ) { grandparentAuthor = this.profileBasic( + // @ts-expect-error isValidPostRecord(parent.record) should be used but the "parent" is not IPDL decoded creatorFromUri(parent.record.reply.parent.uri), state, ) @@ -730,7 +800,7 @@ export class Views { } } - maybePost(uri: string, state: HydrationState): MaybePostView { + maybePost(uri: string, state: HydrationState): $Typed { const post = this.post(uri, state) if (!post) { return this.notFoundPost(uri) @@ -739,8 +809,8 @@ export class Views { return this.blockedPost(uri, post.author.did, state) } return { - $type: 'app.bsky.feed.defs#postView', ...post, + $type: 'app.bsky.feed.defs#postView', } } @@ -748,7 +818,7 @@ export class Views { uri: string, authorDid: string, state: HydrationState, - ): BlockedPost { + ): $Typed { return { $type: 'app.bsky.feed.defs#blockedPost', uri, @@ -760,7 +830,7 @@ export class Views { } } - notFoundPost(uri: string): NotFoundPost { + notFoundPost(uri: string): $Typed { return { $type: 'app.bsky.feed.defs#notFoundPost', uri, @@ -772,7 +842,7 @@ export class Views { creatorDid: string, repost: Repost, state: HydrationState, - ): ReasonRepost | undefined { + ): $Typed | undefined { const creator = this.profileBasic(creatorDid, state) if (!creator) return return { @@ -782,7 +852,7 @@ export class Views { } } - reasonPin() { + reasonPin(): $Typed { return { $type: 'app.bsky.feed.defs#reasonPin', } @@ -795,7 +865,7 @@ export class Views { skele: { anchor: string; uris: string[] }, state: HydrationState, opts: { height: number; depth: number }, - ): ThreadViewPost | NotFoundPost | BlockedPost { + ): $Typed | $Typed | $Typed { const { anchor, uris } = skele const post = this.post(anchor, state) const postInfo = state.posts?.get(anchor) @@ -843,7 +913,11 @@ export class Views { rootUri: string, state: HydrationState, height: number, - ): ThreadViewPost | NotFoundPost | BlockedPost | undefined { + ): + | $Typed + | $Typed + | $Typed + | undefined { if (height < 1) return undefined const parentUri = state.posts?.get(childUri)?.record.reply?.parent.uri if (!parentUri) return undefined @@ -876,7 +950,7 @@ export class Views { childrenByParentUri: Record, state: HydrationState, depth: number, - ): (ThreadViewPost | BlockedPost)[] | undefined { + ): ($Typed | $Typed)[] | undefined { if (depth < 1) return undefined const childrenUris = childrenByParentUri[parentUri] ?? [] return mapDefined(childrenUris, (uri) => { @@ -926,7 +1000,7 @@ export class Views { embed: Embed | { $type: string }, state: HydrationState, depth: number, - ): EmbedView | undefined { + ): $Typed | undefined { if (isImagesEmbed(embed)) { return this.imagesEmbed(creatorFromUri(postUri), embed) } else if (isVideoEmbed(embed)) { @@ -942,7 +1016,7 @@ export class Views { } } - imagesEmbed(did: string, embed: ImagesEmbed): ImagesEmbedView { + imagesEmbed(did: string, embed: ImagesEmbed): $Typed { const imgViews = embed.images.map((img) => ({ thumb: this.imgUriBuilder.getPresetUri( 'feed_thumbnail', @@ -963,7 +1037,7 @@ export class Views { } } - videoEmbed(did: string, embed: VideoEmbed): VideoEmbedView { + videoEmbed(did: string, embed: VideoEmbed): $Typed { const cid = cidFromBlobJson(embed.video) return { $type: 'app.bsky.embed.video#view', @@ -975,7 +1049,7 @@ export class Views { } } - externalEmbed(did: string, embed: ExternalEmbed): ExternalEmbedView { + externalEmbed(did: string, embed: ExternalEmbed): $Typed { const { uri, title, description, thumb } = embed.external return { $type: 'app.bsky.embed.external#view', @@ -994,7 +1068,10 @@ export class Views { } } - embedNotFound(uri: string): { $type: string; record: EmbedNotFound } { + embedNotFound(uri: string): { + $type: 'app.bsky.embed.record#view' + record: $Typed + } { return { $type: 'app.bsky.embed.record#view', record: { @@ -1005,7 +1082,10 @@ export class Views { } } - embedDetached(uri: string): { $type: string; record: EmbedDetached } { + embedDetached(uri: string): { + $type: 'app.bsky.embed.record#view' + record: $Typed + } { return { $type: 'app.bsky.embed.record#view', record: { @@ -1019,7 +1099,10 @@ export class Views { embedBlocked( uri: string, state: HydrationState, - ): { $type: string; record: EmbedBlocked } { + ): { + $type: 'app.bsky.embed.record#view' + record: $Typed + } { const creator = creatorFromUri(uri) return { $type: 'app.bsky.embed.record#view', @@ -1039,7 +1122,7 @@ export class Views { uri: string, state: HydrationState, depth: number, - ): PostEmbedView | undefined { + ): $Typed | undefined { const postView = this.post(uri, state, depth) if (!postView) return return { @@ -1058,6 +1141,20 @@ export class Views { } } + recordEmbed( + postUri: string, + embed: RecordEmbed, + state: HydrationState, + depth: number, + withTypeTag: false, + ): RecordEmbedView + recordEmbed( + postUri: string, + embed: RecordEmbed, + state: HydrationState, + depth: number, + withTypeTag?: true, + ): $Typed recordEmbed( postUri: string, embed: RecordEmbed, @@ -1091,35 +1188,43 @@ export class Views { } else if (parsedUri.collection === ids.AppBskyFeedGenerator) { const view = this.feedGenerator(uri, state) if (!view) return this.embedNotFound(uri) - view.$type = 'app.bsky.feed.defs#generatorView' - return this.recordEmbedWrapper(view, withTypeTag) + return this.recordEmbedWrapper( + { ...view, $type: 'app.bsky.feed.defs#generatorView' }, + withTypeTag, + ) } else if (parsedUri.collection === ids.AppBskyGraphList) { const view = this.list(uri, state) if (!view) return this.embedNotFound(uri) - view.$type = 'app.bsky.graph.defs#listView' - return this.recordEmbedWrapper(view, withTypeTag) + return this.recordEmbedWrapper( + { ...view, $type: 'app.bsky.graph.defs#listView' }, + withTypeTag, + ) } else if (parsedUri.collection === ids.AppBskyLabelerService) { const view = this.labeler(parsedUri.hostname, state) if (!view) return this.embedNotFound(uri) - view.$type = 'app.bsky.labeler.defs#labelerView' - return this.recordEmbedWrapper(view, withTypeTag) + return this.recordEmbedWrapper( + { ...view, $type: 'app.bsky.labeler.defs#labelerView' }, + withTypeTag, + ) } else if (parsedUri.collection === ids.AppBskyGraphStarterpack) { const view = this.starterPackBasic(uri, state) if (!view) return this.embedNotFound(uri) - view.$type = 'app.bsky.graph.defs#starterPackViewBasic' - return this.recordEmbedWrapper(view, withTypeTag) + return this.recordEmbedWrapper( + { ...view, $type: 'app.bsky.graph.defs#starterPackViewBasic' }, + withTypeTag, + ) } return this.embedNotFound(uri) } - private recordEmbedWrapper( - record: RecordEmbedViewInternal, + private recordEmbedWrapper>( + record: T, withTypeTag: boolean, - ): RecordEmbedView { + ) { return { - $type: withTypeTag ? 'app.bsky.embed.record#view' : undefined, + $type: withTypeTag ? ('app.bsky.embed.record#view' as const) : undefined, record, - } + } satisfies RecordEmbedView } recordWithMediaEmbed( @@ -1127,9 +1232,12 @@ export class Views { embed: RecordWithMedia, state: HydrationState, depth: number, - ): RecordWithMediaView | undefined { + ): $Typed | undefined { const creator = creatorFromUri(postUri) - let mediaEmbed: ImagesEmbedView | VideoEmbedView | ExternalEmbedView + let mediaEmbed: + | $Typed + | $Typed + | $Typed if (isImagesEmbed(embed.media)) { mediaEmbed = this.imagesEmbed(creator, embed.media) } else if (isVideoEmbed(embed.media)) { @@ -1222,13 +1330,22 @@ export class Views { notif: Notification, lastSeenAt: string | undefined, state: HydrationState, - ): NotificationView | undefined { + ): Un$Typed | undefined { if (!notif.timestamp || !notif.reason) return const uri = new AtUri(notif.uri) const authorDid = uri.hostname const author = this.profile(authorDid, state) if (!author) return - let recordInfo: RecordInfo> | null | undefined + + let recordInfo: + | Post + | Like + | Repost + | Follow + | RecordInfo + | undefined + | null + if (uri.collection === ids.AppBskyFeedPost) { recordInfo = state.posts?.get(notif.uri) } else if (uri.collection === ids.AppBskyFeedLike) { @@ -1248,9 +1365,10 @@ export class Views { indexedAt: actor.indexedAt ?? new Date(0), // @NOTE will be present since profile record is present takedownRef: actor.profileTakedownRef, } - : null + : undefined } if (!recordInfo) return + const labels = state.labels?.getBySubject(notif.uri) ?? [] const selfLabels = this.selfLabels({ uri: notif.uri, diff --git a/packages/bsky/src/views/types.ts b/packages/bsky/src/views/types.ts index a3e30c827f5..84549ab76b3 100644 --- a/packages/bsky/src/views/types.ts +++ b/packages/bsky/src/views/types.ts @@ -25,7 +25,10 @@ import { NotFoundPost, PostView, } from '../lexicon/types/app/bsky/feed/defs' -import { ListView } from '../lexicon/types/app/bsky/graph/defs' +import { + ListView, + StarterPackViewBasic, +} from '../lexicon/types/app/bsky/graph/defs' import { LabelerView } from '../lexicon/types/app/bsky/labeler/defs' export type { @@ -89,3 +92,4 @@ export type RecordEmbedViewInternal = | GeneratorView | ListView | LabelerView + | StarterPackViewBasic diff --git a/packages/bsky/tests/_util.ts b/packages/bsky/tests/_util.ts index 954c6c69035..78346277166 100644 --- a/packages/bsky/tests/_util.ts +++ b/packages/bsky/tests/_util.ts @@ -5,11 +5,16 @@ import { CID } from 'multiformats/cid' import { AppBskyFeedGetPostThread } from '@atproto/api' import { lexToJson } from '@atproto/lexicon' import { AtUri } from '@atproto/syntax' -import { isViewRecord } from '../src/lexicon/types/app/bsky/embed/record' +import { + isView as isEmbedRecordView, + isViewRecord, +} from '../src/lexicon/types/app/bsky/embed/record' +import { isView as isEmbedRecordWithMediaView } from '../src/lexicon/types/app/bsky/embed/recordWithMedia' import { FeedViewPost, PostView, isPostView, + isReasonRepost, isThreadViewPost, } from '../src/lexicon/types/app/bsky/feed/defs' import { @@ -17,6 +22,7 @@ import { isLabelerView, isLabelerViewDetailed, } from '../src/lexicon/types/app/bsky/labeler/defs' +import { $Typed } from '../src/lexicon/util' type ThreadViewPost = Extract< AppBskyFeedGetPostThread.OutputSchema['thread'], @@ -109,10 +115,10 @@ export const forSnapshot = (obj: unknown) => { // Feed testing utils export const getOriginator = (item: FeedViewPost) => { - if (!item.reason) { - return item.post.author.did + if (isReasonRepost(item.reason)) { + return item.reason.by.did } else { - return (item.reason.by as { [did: string]: string }).did + return item.post.author.did } } @@ -122,8 +128,11 @@ export const getOriginator = (item: FeedViewPost) => { // to this: // [{ uri: '0'}, { uri: '1' }, { uri: '0'}] const kTake = Symbol('take') -export function take(obj, value: string): string -export function take(obj, value: string | undefined): string | undefined +export function take(obj: Record, value: string): string +export function take( + obj: Record, + value?: string, +): string | undefined export function take( obj: { [s: string]: number; [kTake]?: string }, value: string | undefined, @@ -143,7 +152,10 @@ export function take( export const constantDate = new Date(0).toISOString() export const constantKeysetCursor = '0000000000000__bafycid' -const mapLeafValues = (obj: unknown, fn: (val: unknown) => unknown) => { +const mapLeafValues = ( + obj: unknown, + fn: (val: unknown) => unknown, +): unknown => { if (Array.isArray(obj)) { return obj.map((item) => mapLeafValues(item, fn)) } @@ -172,34 +184,43 @@ export const paginateAll = async ( } // @NOTE mutates -export const stripViewer = }>( +export const stripViewer = ( val: T, -): T => { +): Omit => { delete val.viewer return val } // @NOTE mutates -export const stripViewerFromPost = (postUnknown: unknown): PostView => { - if (postUnknown?.['$type'] && !isPostView(postUnknown)) { +export function stripViewerFromPost( + postUnknown: object, + withType?: false, +): PostView +export function stripViewerFromPost( + postUnknown: object, + withType: true, +): $Typed +export function stripViewerFromPost( + postUnknown: object, + withType = false, +): PostView { + if ('$type' in postUnknown && !isPostView(postUnknown)) { throw new Error('Expected post view') } const post = postUnknown as PostView + if (withType) { + post.$type = 'app.bsky.feed.defs#postView' + } else { + delete post.$type + } post.author = stripViewer(post.author) - const recordEmbed = - post.embed && isViewRecord(post.embed.record) - ? post.embed.record // Record from record embed - : post.embed?.['record'] && isViewRecord(post.embed['record']['record']) - ? post.embed['record']['record'] // Record from record-with-media embed - : undefined + + const recordEmbed = extractRecordEmbed(post.embed) + if (recordEmbed) { recordEmbed.author = stripViewer(recordEmbed.author) recordEmbed.embeds?.forEach((deepEmbed) => { - const deepRecordEmbed = isViewRecord(deepEmbed.record) - ? deepEmbed.record // Record from record embed - : deepEmbed['record'] && isViewRecord(deepEmbed['record']['record']) - ? deepEmbed['record']['record'] // Record from record-with-media embed - : undefined + const deepRecordEmbed = extractRecordEmbed(deepEmbed) if (deepRecordEmbed) { deepRecordEmbed.author = stripViewer(deepRecordEmbed.author) } @@ -208,10 +229,26 @@ export const stripViewerFromPost = (postUnknown: unknown): PostView => { return stripViewer(post) } +const extractRecordEmbed = (embed: PostView['embed']) => + isEmbedRecordView(embed) + ? isViewRecord(embed.record) + ? embed.record + : undefined + : isEmbedRecordWithMediaView(embed) + ? isViewRecord(embed.record.record) + ? embed.record.record + : undefined + : undefined + // @NOTE mutates -export const stripViewerFromThread = (thread: T): T => { - if (!isThreadViewPost(thread)) return thread +export const stripViewerFromThread = (threadUnknown: T): T => { + if (!isThreadViewPost(threadUnknown)) return threadUnknown + + const thread = threadUnknown as typeof threadUnknown & ThreadViewPost + + // @ts-expect-error "viewer" does not exist on type 'ThreadViewPost' delete thread.viewer + thread.post = stripViewerFromPost(thread.post) if (isThreadViewPost(thread.parent)) { thread.parent = stripViewerFromThread(thread.parent) @@ -223,11 +260,9 @@ export const stripViewerFromThread = (thread: T): T => { } // @NOTE mutates -export const stripViewerFromLabeler = ( - serviceUnknown: unknown, -): LabelerView => { +export const stripViewerFromLabeler = (serviceUnknown: object): LabelerView => { if ( - serviceUnknown?.['$type'] && + '$type' in serviceUnknown && !isLabelerView(serviceUnknown) && !isLabelerViewDetailed(serviceUnknown) ) { diff --git a/packages/bsky/tests/admin/admin-auth.test.ts b/packages/bsky/tests/admin/admin-auth.test.ts index 8379cb83361..2d73b233724 100644 --- a/packages/bsky/tests/admin/admin-auth.test.ts +++ b/packages/bsky/tests/admin/admin-auth.test.ts @@ -1,16 +1,21 @@ +import assert from 'node:assert' import { AtpAgent } from '@atproto/api' import { Secp256k1Keypair } from '@atproto/crypto' import { SeedClient, TestNetwork, usersSeed } from '@atproto/dev-env' import { createServiceAuthHeaders } from '@atproto/xrpc-server' import { ids } from '../../src/lexicon/lexicons' -import { RepoRef } from '../../src/lexicon/types/com/atproto/admin/defs' +import { + RepoRef, + isRepoRef, +} from '../../src/lexicon/types/com/atproto/admin/defs' +import { $Typed } from '../../src/lexicon/util' describe('admin auth', () => { let network: TestNetwork let agent: AtpAgent let sc: SeedClient - let repoSubject: RepoRef + let repoSubject: $Typed const modServiceDid = 'did:example:mod' const altModDid = 'did:example:alt' @@ -96,6 +101,7 @@ describe('admin auth', () => { { did: repoSubject.did }, getHeaders, ) + assert(isRepoRef(res.data.subject)) expect(res.data.subject.did).toBe(repoSubject.did) expect(res.data.takedown?.applied).toBe(true) }) diff --git a/packages/bsky/tests/admin/moderation.test.ts b/packages/bsky/tests/admin/moderation.test.ts index 8ce65843013..85bb6cae32f 100644 --- a/packages/bsky/tests/admin/moderation.test.ts +++ b/packages/bsky/tests/admin/moderation.test.ts @@ -1,19 +1,26 @@ import { AtpAgent } from '@atproto/api' import { ImageRef, SeedClient, TestNetwork, basicSeed } from '@atproto/dev-env' import { + isRepoBlobRef, + isRepoRef, RepoBlobRef, RepoRef, } from '../../src/lexicon/types/com/atproto/admin/defs' -import { Main as StrongRef } from '../../src/lexicon/types/com/atproto/repo/strongRef' +import { + Main as StrongRef, + isMain as isStrongRef, +} from '../../src/lexicon/types/com/atproto/repo/strongRef' +import { $Typed } from '../../src/lexicon/util' +import assert from 'node:assert' describe('moderation', () => { let network: TestNetwork let agent: AtpAgent let sc: SeedClient - let repoSubject: RepoRef - let recordSubject: StrongRef - let blobSubject: RepoBlobRef + let repoSubject: $Typed + let recordSubject: $Typed + let blobSubject: $Typed let blobRef: ImageRef beforeAll(async () => { @@ -64,6 +71,7 @@ describe('moderation', () => { }, { headers: network.bsky.adminAuthHeaders() }, ) + assert(isRepoRef(res.data.subject)) expect(res.data.subject.did).toEqual(sc.dids.bob) expect(res.data.takedown?.applied).toBe(true) // expect(res.data.takedown?.ref).toBe('test-repo') @TODO add these checks back in once takedown refs make it into dataplane @@ -86,6 +94,7 @@ describe('moderation', () => { }, { headers: network.bsky.adminAuthHeaders() }, ) + assert(isRepoRef(res.data.subject)) expect(res.data.subject.did).toEqual(sc.dids.bob) expect(res.data.takedown?.applied).toBe(false) expect(res.data.takedown?.ref).toBeUndefined() @@ -108,6 +117,7 @@ describe('moderation', () => { }, { headers: network.bsky.adminAuthHeaders() }, ) + assert(isStrongRef(res.data.subject)) expect(res.data.subject.uri).toEqual(recordSubject.uri) expect(res.data.takedown?.applied).toBe(true) // expect(res.data.takedown?.ref).toBe('test-record') @@ -130,6 +140,7 @@ describe('moderation', () => { }, { headers: network.bsky.adminAuthHeaders() }, ) + assert(isStrongRef(res.data.subject)) expect(res.data.subject.uri).toEqual(recordSubject.uri) expect(res.data.takedown?.applied).toBe(false) expect(res.data.takedown?.ref).toBeUndefined() @@ -168,6 +179,7 @@ describe('moderation', () => { }, { headers: network.bsky.adminAuthHeaders() }, ) + assert(isRepoBlobRef(res.data.subject)) expect(res.data.subject.did).toEqual(blobSubject.did) expect(res.data.subject.cid).toEqual(blobSubject.cid) expect(res.data.takedown?.applied).toBe(true) diff --git a/packages/bsky/tests/views/__snapshots__/author-feed.test.ts.snap b/packages/bsky/tests/views/__snapshots__/author-feed.test.ts.snap index 8ac1b1d68c5..a194292f8bd 100644 --- a/packages/bsky/tests/views/__snapshots__/author-feed.test.ts.snap +++ b/packages/bsky/tests/views/__snapshots__/author-feed.test.ts.snap @@ -1770,9 +1770,27 @@ Array [ Object { "post": Object { "author": Object { + "avatar": "https://bsky.public.url/img/avatar/plain/user(1)/cids(1)@jpeg", + "createdAt": "1970-01-01T00:00:00.000Z", "did": "user(0)", + "displayName": "ali", "handle": "alice.test", - "labels": Array [], + "labels": Array [ + Object { + "cid": "cids(2)", + "cts": "1970-01-01T00:00:00.000Z", + "src": "user(0)", + "uri": "record(1)", + "val": "self-label-a", + }, + Object { + "cid": "cids(2)", + "cts": "1970-01-01T00:00:00.000Z", + "src": "user(0)", + "uri": "record(1)", + "val": "self-label-b", + }, + ], "viewer": Object { "blockedBy": false, "muted": false, @@ -1801,15 +1819,33 @@ Array [ Object { "post": Object { "author": Object { + "avatar": "https://bsky.public.url/img/avatar/plain/user(1)/cids(1)@jpeg", + "createdAt": "1970-01-01T00:00:00.000Z", "did": "user(0)", + "displayName": "ali", "handle": "alice.test", - "labels": Array [], + "labels": Array [ + Object { + "cid": "cids(2)", + "cts": "1970-01-01T00:00:00.000Z", + "src": "user(0)", + "uri": "record(1)", + "val": "self-label-a", + }, + Object { + "cid": "cids(2)", + "cts": "1970-01-01T00:00:00.000Z", + "src": "user(0)", + "uri": "record(1)", + "val": "self-label-b", + }, + ], "viewer": Object { "blockedBy": false, "muted": false, }, }, - "cid": "cids(1)", + "cid": "cids(3)", "indexedAt": "1970-01-01T00:00:00.000Z", "labels": Array [], "likeCount": 0, @@ -1821,7 +1857,7 @@ Array [ }, "replyCount": 0, "repostCount": 0, - "uri": "record(1)", + "uri": "record(2)", "viewer": Object { "embeddingDisabled": false, "pinned": false, @@ -1832,15 +1868,33 @@ Array [ Object { "post": Object { "author": Object { + "avatar": "https://bsky.public.url/img/avatar/plain/user(1)/cids(1)@jpeg", + "createdAt": "1970-01-01T00:00:00.000Z", "did": "user(0)", + "displayName": "ali", "handle": "alice.test", - "labels": Array [], + "labels": Array [ + Object { + "cid": "cids(2)", + "cts": "1970-01-01T00:00:00.000Z", + "src": "user(0)", + "uri": "record(1)", + "val": "self-label-a", + }, + Object { + "cid": "cids(2)", + "cts": "1970-01-01T00:00:00.000Z", + "src": "user(0)", + "uri": "record(1)", + "val": "self-label-b", + }, + ], "viewer": Object { "blockedBy": false, "muted": false, }, }, - "cid": "cids(2)", + "cid": "cids(4)", "indexedAt": "1970-01-01T00:00:00.000Z", "labels": Array [], "likeCount": 0, @@ -1852,7 +1906,7 @@ Array [ }, "replyCount": 0, "repostCount": 0, - "uri": "record(2)", + "uri": "record(3)", "viewer": Object { "embeddingDisabled": false, "pinned": false, @@ -1863,15 +1917,33 @@ Array [ Object { "post": Object { "author": Object { + "avatar": "https://bsky.public.url/img/avatar/plain/user(1)/cids(1)@jpeg", + "createdAt": "1970-01-01T00:00:00.000Z", "did": "user(0)", + "displayName": "ali", "handle": "alice.test", - "labels": Array [], + "labels": Array [ + Object { + "cid": "cids(2)", + "cts": "1970-01-01T00:00:00.000Z", + "src": "user(0)", + "uri": "record(1)", + "val": "self-label-a", + }, + Object { + "cid": "cids(2)", + "cts": "1970-01-01T00:00:00.000Z", + "src": "user(0)", + "uri": "record(1)", + "val": "self-label-b", + }, + ], "viewer": Object { "blockedBy": false, "muted": false, }, }, - "cid": "cids(3)", + "cid": "cids(5)", "indexedAt": "1970-01-01T00:00:00.000Z", "labels": Array [], "likeCount": 0, @@ -1883,7 +1955,7 @@ Array [ }, "replyCount": 0, "repostCount": 0, - "uri": "record(3)", + "uri": "record(4)", "viewer": Object { "embeddingDisabled": false, "pinned": false, @@ -1894,15 +1966,33 @@ Array [ Object { "post": Object { "author": Object { + "avatar": "https://bsky.public.url/img/avatar/plain/user(1)/cids(1)@jpeg", + "createdAt": "1970-01-01T00:00:00.000Z", "did": "user(0)", + "displayName": "ali", "handle": "alice.test", - "labels": Array [], + "labels": Array [ + Object { + "cid": "cids(2)", + "cts": "1970-01-01T00:00:00.000Z", + "src": "user(0)", + "uri": "record(1)", + "val": "self-label-a", + }, + Object { + "cid": "cids(2)", + "cts": "1970-01-01T00:00:00.000Z", + "src": "user(0)", + "uri": "record(1)", + "val": "self-label-b", + }, + ], "viewer": Object { "blockedBy": false, "muted": false, }, }, - "cid": "cids(4)", + "cid": "cids(6)", "indexedAt": "1970-01-01T00:00:00.000Z", "labels": Array [], "likeCount": 0, @@ -1914,7 +2004,7 @@ Array [ }, "replyCount": 0, "repostCount": 0, - "uri": "record(4)", + "uri": "record(5)", "viewer": Object { "embeddingDisabled": false, "pinned": false, @@ -1925,15 +2015,33 @@ Array [ Object { "post": Object { "author": Object { + "avatar": "https://bsky.public.url/img/avatar/plain/user(1)/cids(1)@jpeg", + "createdAt": "1970-01-01T00:00:00.000Z", "did": "user(0)", + "displayName": "ali", "handle": "alice.test", - "labels": Array [], + "labels": Array [ + Object { + "cid": "cids(2)", + "cts": "1970-01-01T00:00:00.000Z", + "src": "user(0)", + "uri": "record(1)", + "val": "self-label-a", + }, + Object { + "cid": "cids(2)", + "cts": "1970-01-01T00:00:00.000Z", + "src": "user(0)", + "uri": "record(1)", + "val": "self-label-b", + }, + ], "viewer": Object { "blockedBy": false, "muted": false, }, }, - "cid": "cids(5)", + "cid": "cids(7)", "indexedAt": "1970-01-01T00:00:00.000Z", "labels": Array [], "likeCount": 0, @@ -1945,7 +2053,7 @@ Array [ }, "replyCount": 0, "repostCount": 0, - "uri": "record(5)", + "uri": "record(6)", "viewer": Object { "embeddingDisabled": false, "pinned": false, @@ -1956,15 +2064,33 @@ Array [ Object { "post": Object { "author": Object { + "avatar": "https://bsky.public.url/img/avatar/plain/user(1)/cids(1)@jpeg", + "createdAt": "1970-01-01T00:00:00.000Z", "did": "user(0)", + "displayName": "ali", "handle": "alice.test", - "labels": Array [], + "labels": Array [ + Object { + "cid": "cids(2)", + "cts": "1970-01-01T00:00:00.000Z", + "src": "user(0)", + "uri": "record(1)", + "val": "self-label-a", + }, + Object { + "cid": "cids(2)", + "cts": "1970-01-01T00:00:00.000Z", + "src": "user(0)", + "uri": "record(1)", + "val": "self-label-b", + }, + ], "viewer": Object { "blockedBy": false, "muted": false, }, }, - "cid": "cids(6)", + "cid": "cids(8)", "indexedAt": "1970-01-01T00:00:00.000Z", "labels": Array [], "likeCount": 0, @@ -1976,7 +2102,7 @@ Array [ }, "replyCount": 0, "repostCount": 0, - "uri": "record(6)", + "uri": "record(7)", "viewer": Object { "embeddingDisabled": false, "pinned": false, @@ -1987,15 +2113,33 @@ Array [ Object { "post": Object { "author": Object { + "avatar": "https://bsky.public.url/img/avatar/plain/user(1)/cids(1)@jpeg", + "createdAt": "1970-01-01T00:00:00.000Z", "did": "user(0)", + "displayName": "ali", "handle": "alice.test", - "labels": Array [], + "labels": Array [ + Object { + "cid": "cids(2)", + "cts": "1970-01-01T00:00:00.000Z", + "src": "user(0)", + "uri": "record(1)", + "val": "self-label-a", + }, + Object { + "cid": "cids(2)", + "cts": "1970-01-01T00:00:00.000Z", + "src": "user(0)", + "uri": "record(1)", + "val": "self-label-b", + }, + ], "viewer": Object { "blockedBy": false, "muted": false, }, }, - "cid": "cids(7)", + "cid": "cids(9)", "indexedAt": "1970-01-01T00:00:00.000Z", "labels": Array [], "likeCount": 0, @@ -2007,7 +2151,7 @@ Array [ }, "replyCount": 0, "repostCount": 0, - "uri": "record(7)", + "uri": "record(8)", "viewer": Object { "embeddingDisabled": false, "pinned": false, @@ -2018,15 +2162,33 @@ Array [ Object { "post": Object { "author": Object { + "avatar": "https://bsky.public.url/img/avatar/plain/user(1)/cids(1)@jpeg", + "createdAt": "1970-01-01T00:00:00.000Z", "did": "user(0)", + "displayName": "ali", "handle": "alice.test", - "labels": Array [], + "labels": Array [ + Object { + "cid": "cids(2)", + "cts": "1970-01-01T00:00:00.000Z", + "src": "user(0)", + "uri": "record(1)", + "val": "self-label-a", + }, + Object { + "cid": "cids(2)", + "cts": "1970-01-01T00:00:00.000Z", + "src": "user(0)", + "uri": "record(1)", + "val": "self-label-b", + }, + ], "viewer": Object { "blockedBy": false, "muted": false, }, }, - "cid": "cids(8)", + "cid": "cids(10)", "indexedAt": "1970-01-01T00:00:00.000Z", "labels": Array [], "likeCount": 0, @@ -2036,19 +2198,19 @@ Array [ "createdAt": "1970-01-01T00:00:00.000Z", "reply": Object { "parent": Object { - "cid": "cids(10)", - "uri": "record(10)", + "cid": "cids(12)", + "uri": "record(11)", }, "root": Object { - "cid": "cids(9)", - "uri": "record(9)", + "cid": "cids(11)", + "uri": "record(10)", }, }, "text": "thanks bob", }, "replyCount": 0, "repostCount": 1, - "uri": "record(8)", + "uri": "record(9)", "viewer": Object { "embeddingDisabled": false, "pinned": false, @@ -2057,9 +2219,27 @@ Array [ }, "reply": Object { "grandparentAuthor": Object { + "avatar": "https://bsky.public.url/img/avatar/plain/user(1)/cids(1)@jpeg", + "createdAt": "1970-01-01T00:00:00.000Z", "did": "user(0)", + "displayName": "ali", "handle": "alice.test", - "labels": Array [], + "labels": Array [ + Object { + "cid": "cids(2)", + "cts": "1970-01-01T00:00:00.000Z", + "src": "user(0)", + "uri": "record(1)", + "val": "self-label-a", + }, + Object { + "cid": "cids(2)", + "cts": "1970-01-01T00:00:00.000Z", + "src": "user(0)", + "uri": "record(1)", + "val": "self-label-b", + }, + ], "viewer": Object { "blockedBy": false, "muted": false, @@ -2068,44 +2248,44 @@ Array [ "parent": Object { "$type": "app.bsky.feed.defs#postView", "author": Object { - "avatar": "https://bsky.public.url/img/avatar/plain/user(2)/cids(11)@jpeg", + "avatar": "https://bsky.public.url/img/avatar/plain/user(3)/cids(1)@jpeg", "createdAt": "1970-01-01T00:00:00.000Z", - "did": "user(1)", + "did": "user(2)", "displayName": "bobby", "handle": "bob.test", "labels": Array [], "viewer": Object { "blockedBy": false, - "followedBy": "record(12)", - "following": "record(11)", + "followedBy": "record(13)", + "following": "record(12)", "muted": false, }, }, - "cid": "cids(10)", + "cid": "cids(12)", "embed": Object { "$type": "app.bsky.embed.images#view", "images": Array [ Object { "alt": "../dev-env/assets/key-landscape-small.jpg", - "fullsize": "https://bsky.public.url/img/feed_fullsize/plain/user(2)/cids(12)@jpeg", - "thumb": "https://bsky.public.url/img/feed_thumbnail/plain/user(2)/cids(12)@jpeg", + "fullsize": "https://bsky.public.url/img/feed_fullsize/plain/user(3)/cids(13)@jpeg", + "thumb": "https://bsky.public.url/img/feed_thumbnail/plain/user(3)/cids(13)@jpeg", }, ], }, "indexedAt": "1970-01-01T00:00:00.000Z", "labels": Array [ Object { - "cid": "cids(10)", + "cid": "cids(12)", "cts": "1970-01-01T00:00:00.000Z", "src": "did:example:labeler", - "uri": "record(10)", + "uri": "record(11)", "val": "test-label", }, Object { - "cid": "cids(10)", + "cid": "cids(12)", "cts": "1970-01-01T00:00:00.000Z", "src": "did:example:labeler", - "uri": "record(10)", + "uri": "record(11)", "val": "test-label-2", }, ], @@ -2123,7 +2303,7 @@ Array [ "$type": "blob", "mimeType": "image/jpeg", "ref": Object { - "$link": "cids(12)", + "$link": "cids(13)", }, "size": 4114, }, @@ -2132,19 +2312,19 @@ Array [ }, "reply": Object { "parent": Object { - "cid": "cids(9)", - "uri": "record(9)", + "cid": "cids(11)", + "uri": "record(10)", }, "root": Object { - "cid": "cids(9)", - "uri": "record(9)", + "cid": "cids(11)", + "uri": "record(10)", }, }, "text": "hear that label_me label_me_2", }, "replyCount": 1, "repostCount": 0, - "uri": "record(10)", + "uri": "record(11)", "viewer": Object { "embeddingDisabled": false, "threadMuted": false, @@ -2153,15 +2333,33 @@ Array [ "root": Object { "$type": "app.bsky.feed.defs#postView", "author": Object { + "avatar": "https://bsky.public.url/img/avatar/plain/user(1)/cids(1)@jpeg", + "createdAt": "1970-01-01T00:00:00.000Z", "did": "user(0)", + "displayName": "ali", "handle": "alice.test", - "labels": Array [], + "labels": Array [ + Object { + "cid": "cids(2)", + "cts": "1970-01-01T00:00:00.000Z", + "src": "user(0)", + "uri": "record(1)", + "val": "self-label-a", + }, + Object { + "cid": "cids(2)", + "cts": "1970-01-01T00:00:00.000Z", + "src": "user(0)", + "uri": "record(1)", + "val": "self-label-b", + }, + ], "viewer": Object { "blockedBy": false, "muted": false, }, }, - "cid": "cids(9)", + "cid": "cids(11)", "indexedAt": "1970-01-01T00:00:00.000Z", "labels": Array [], "likeCount": 3, @@ -2173,7 +2371,7 @@ Array [ }, "replyCount": 3, "repostCount": 1, - "uri": "record(9)", + "uri": "record(10)", "viewer": Object { "embeddingDisabled": false, "pinned": false, @@ -2185,15 +2383,33 @@ Array [ Object { "post": Object { "author": Object { + "avatar": "https://bsky.public.url/img/avatar/plain/user(1)/cids(1)@jpeg", + "createdAt": "1970-01-01T00:00:00.000Z", "did": "user(0)", + "displayName": "ali", "handle": "alice.test", - "labels": Array [], + "labels": Array [ + Object { + "cid": "cids(2)", + "cts": "1970-01-01T00:00:00.000Z", + "src": "user(0)", + "uri": "record(1)", + "val": "self-label-a", + }, + Object { + "cid": "cids(2)", + "cts": "1970-01-01T00:00:00.000Z", + "src": "user(0)", + "uri": "record(1)", + "val": "self-label-b", + }, + ], "viewer": Object { "blockedBy": false, "muted": false, }, }, - "cid": "cids(13)", + "cid": "cids(14)", "embed": Object { "$type": "app.bsky.embed.record#view", "record": Object { @@ -2204,40 +2420,40 @@ Array [ "allowIncoming": "none", }, }, - "did": "user(3)", + "did": "user(4)", "handle": "dan.test", "labels": Array [], "viewer": Object { "blockedBy": false, - "following": "record(15)", + "following": "record(16)", "muted": false, }, }, - "cid": "cids(14)", + "cid": "cids(15)", "embeds": Array [ Object { "$type": "app.bsky.embed.record#view", "record": Object { "$type": "app.bsky.embed.record#viewRecord", "author": Object { - "did": "user(4)", + "did": "user(5)", "handle": "carol.test", "labels": Array [], "viewer": Object { "blockedBy": false, - "followedBy": "record(18)", - "following": "record(17)", + "followedBy": "record(19)", + "following": "record(18)", "muted": false, }, }, - "cid": "cids(15)", + "cid": "cids(16)", "indexedAt": "1970-01-01T00:00:00.000Z", "labels": Array [], "likeCount": 2, "quoteCount": 1, "replyCount": 0, "repostCount": 0, - "uri": "record(16)", + "uri": "record(17)", "value": Object { "$type": "app.bsky.feed.post", "createdAt": "1970-01-01T00:00:00.000Z", @@ -2252,7 +2468,7 @@ Array [ "$type": "blob", "mimeType": "image/jpeg", "ref": Object { - "$link": "cids(12)", + "$link": "cids(13)", }, "size": 4114, }, @@ -2263,7 +2479,7 @@ Array [ "$type": "blob", "mimeType": "image/jpeg", "ref": Object { - "$link": "cids(16)", + "$link": "cids(17)", }, "size": 12736, }, @@ -2272,8 +2488,8 @@ Array [ }, "record": Object { "record": Object { - "cid": "cids(17)", - "uri": "record(19)", + "cid": "cids(18)", + "uri": "record(20)", }, }, }, @@ -2288,15 +2504,15 @@ Array [ "quoteCount": 1, "replyCount": 0, "repostCount": 1, - "uri": "record(14)", + "uri": "record(15)", "value": Object { "$type": "app.bsky.feed.post", "createdAt": "1970-01-01T00:00:00.000Z", "embed": Object { "$type": "app.bsky.embed.record", "record": Object { - "cid": "cids(15)", - "uri": "record(16)", + "cid": "cids(16)", + "uri": "record(17)", }, }, "facets": Array [ @@ -2320,10 +2536,10 @@ Array [ "indexedAt": "1970-01-01T00:00:00.000Z", "labels": Array [ Object { - "cid": "cids(13)", + "cid": "cids(14)", "cts": "1970-01-01T00:00:00.000Z", "src": "did:example:labeler", - "uri": "record(13)", + "uri": "record(14)", "val": "test-label", }, ], @@ -2335,15 +2551,15 @@ Array [ "embed": Object { "$type": "app.bsky.embed.record", "record": Object { - "cid": "cids(14)", - "uri": "record(14)", + "cid": "cids(15)", + "uri": "record(15)", }, }, "text": "yoohoo label_me", }, "replyCount": 0, "repostCount": 0, - "uri": "record(13)", + "uri": "record(14)", "viewer": Object { "embeddingDisabled": false, "pinned": false, @@ -2354,15 +2570,33 @@ Array [ Object { "post": Object { "author": Object { + "avatar": "https://bsky.public.url/img/avatar/plain/user(1)/cids(1)@jpeg", + "createdAt": "1970-01-01T00:00:00.000Z", "did": "user(0)", + "displayName": "ali", "handle": "alice.test", - "labels": Array [], + "labels": Array [ + Object { + "cid": "cids(2)", + "cts": "1970-01-01T00:00:00.000Z", + "src": "user(0)", + "uri": "record(1)", + "val": "self-label-a", + }, + Object { + "cid": "cids(2)", + "cts": "1970-01-01T00:00:00.000Z", + "src": "user(0)", + "uri": "record(1)", + "val": "self-label-b", + }, + ], "viewer": Object { "blockedBy": false, "muted": false, }, }, - "cid": "cids(9)", + "cid": "cids(11)", "indexedAt": "1970-01-01T00:00:00.000Z", "labels": Array [], "likeCount": 3, @@ -2374,7 +2608,7 @@ Array [ }, "replyCount": 3, "repostCount": 1, - "uri": "record(9)", + "uri": "record(10)", "viewer": Object { "embeddingDisabled": false, "pinned": false, @@ -2385,22 +2619,40 @@ Array [ Object { "post": Object { "author": Object { + "avatar": "https://bsky.public.url/img/avatar/plain/user(1)/cids(1)@jpeg", + "createdAt": "1970-01-01T00:00:00.000Z", "did": "user(0)", + "displayName": "ali", "handle": "alice.test", - "labels": Array [], + "labels": Array [ + Object { + "cid": "cids(2)", + "cts": "1970-01-01T00:00:00.000Z", + "src": "user(0)", + "uri": "record(1)", + "val": "self-label-a", + }, + Object { + "cid": "cids(2)", + "cts": "1970-01-01T00:00:00.000Z", + "src": "user(0)", + "uri": "record(1)", + "val": "self-label-b", + }, + ], "viewer": Object { "blockedBy": false, "muted": false, }, }, - "cid": "cids(18)", + "cid": "cids(19)", "indexedAt": "1970-01-01T00:00:00.000Z", "labels": Array [ Object { - "cid": "cids(18)", + "cid": "cids(19)", "cts": "1970-01-01T00:00:00.000Z", "src": "user(0)", - "uri": "record(20)", + "uri": "record(21)", "val": "self-label", }, ], @@ -2421,7 +2673,7 @@ Array [ }, "replyCount": 0, "repostCount": 0, - "uri": "record(20)", + "uri": "record(21)", "viewer": Object { "embeddingDisabled": false, "pinned": false, @@ -2437,9 +2689,27 @@ Array [ Object { "post": Object { "author": Object { + "avatar": "https://bsky.public.url/img/avatar/plain/user(1)/cids(1)@jpeg", + "createdAt": "1970-01-01T00:00:00.000Z", "did": "user(0)", + "displayName": "ali", "handle": "alice.test", - "labels": Array [], + "labels": Array [ + Object { + "cid": "cids(2)", + "cts": "1970-01-01T00:00:00.000Z", + "src": "user(0)", + "uri": "record(1)", + "val": "self-label-a", + }, + Object { + "cid": "cids(2)", + "cts": "1970-01-01T00:00:00.000Z", + "src": "user(0)", + "uri": "record(1)", + "val": "self-label-b", + }, + ], "viewer": Object { "blockedBy": false, "muted": false, @@ -2468,15 +2738,33 @@ Array [ Object { "post": Object { "author": Object { + "avatar": "https://bsky.public.url/img/avatar/plain/user(1)/cids(1)@jpeg", + "createdAt": "1970-01-01T00:00:00.000Z", "did": "user(0)", + "displayName": "ali", "handle": "alice.test", - "labels": Array [], + "labels": Array [ + Object { + "cid": "cids(2)", + "cts": "1970-01-01T00:00:00.000Z", + "src": "user(0)", + "uri": "record(1)", + "val": "self-label-a", + }, + Object { + "cid": "cids(2)", + "cts": "1970-01-01T00:00:00.000Z", + "src": "user(0)", + "uri": "record(1)", + "val": "self-label-b", + }, + ], "viewer": Object { "blockedBy": false, "muted": false, }, }, - "cid": "cids(1)", + "cid": "cids(3)", "indexedAt": "1970-01-01T00:00:00.000Z", "labels": Array [], "likeCount": 0, @@ -2488,7 +2776,7 @@ Array [ }, "replyCount": 0, "repostCount": 0, - "uri": "record(1)", + "uri": "record(2)", "viewer": Object { "embeddingDisabled": false, "pinned": false, @@ -2499,15 +2787,33 @@ Array [ Object { "post": Object { "author": Object { + "avatar": "https://bsky.public.url/img/avatar/plain/user(1)/cids(1)@jpeg", + "createdAt": "1970-01-01T00:00:00.000Z", "did": "user(0)", + "displayName": "ali", "handle": "alice.test", - "labels": Array [], + "labels": Array [ + Object { + "cid": "cids(2)", + "cts": "1970-01-01T00:00:00.000Z", + "src": "user(0)", + "uri": "record(1)", + "val": "self-label-a", + }, + Object { + "cid": "cids(2)", + "cts": "1970-01-01T00:00:00.000Z", + "src": "user(0)", + "uri": "record(1)", + "val": "self-label-b", + }, + ], "viewer": Object { "blockedBy": false, "muted": false, }, }, - "cid": "cids(2)", + "cid": "cids(4)", "indexedAt": "1970-01-01T00:00:00.000Z", "labels": Array [], "likeCount": 0, @@ -2519,7 +2825,7 @@ Array [ }, "replyCount": 0, "repostCount": 0, - "uri": "record(2)", + "uri": "record(3)", "viewer": Object { "embeddingDisabled": false, "pinned": false, @@ -2530,15 +2836,33 @@ Array [ Object { "post": Object { "author": Object { + "avatar": "https://bsky.public.url/img/avatar/plain/user(1)/cids(1)@jpeg", + "createdAt": "1970-01-01T00:00:00.000Z", "did": "user(0)", + "displayName": "ali", "handle": "alice.test", - "labels": Array [], + "labels": Array [ + Object { + "cid": "cids(2)", + "cts": "1970-01-01T00:00:00.000Z", + "src": "user(0)", + "uri": "record(1)", + "val": "self-label-a", + }, + Object { + "cid": "cids(2)", + "cts": "1970-01-01T00:00:00.000Z", + "src": "user(0)", + "uri": "record(1)", + "val": "self-label-b", + }, + ], "viewer": Object { "blockedBy": false, "muted": false, }, }, - "cid": "cids(3)", + "cid": "cids(5)", "indexedAt": "1970-01-01T00:00:00.000Z", "labels": Array [], "likeCount": 0, @@ -2550,7 +2874,7 @@ Array [ }, "replyCount": 0, "repostCount": 0, - "uri": "record(3)", + "uri": "record(4)", "viewer": Object { "embeddingDisabled": false, "pinned": false, @@ -2561,15 +2885,33 @@ Array [ Object { "post": Object { "author": Object { + "avatar": "https://bsky.public.url/img/avatar/plain/user(1)/cids(1)@jpeg", + "createdAt": "1970-01-01T00:00:00.000Z", "did": "user(0)", + "displayName": "ali", "handle": "alice.test", - "labels": Array [], + "labels": Array [ + Object { + "cid": "cids(2)", + "cts": "1970-01-01T00:00:00.000Z", + "src": "user(0)", + "uri": "record(1)", + "val": "self-label-a", + }, + Object { + "cid": "cids(2)", + "cts": "1970-01-01T00:00:00.000Z", + "src": "user(0)", + "uri": "record(1)", + "val": "self-label-b", + }, + ], "viewer": Object { "blockedBy": false, "muted": false, }, }, - "cid": "cids(4)", + "cid": "cids(6)", "indexedAt": "1970-01-01T00:00:00.000Z", "labels": Array [], "likeCount": 0, @@ -2581,7 +2923,7 @@ Array [ }, "replyCount": 0, "repostCount": 0, - "uri": "record(4)", + "uri": "record(5)", "viewer": Object { "embeddingDisabled": false, "pinned": false, @@ -2592,15 +2934,33 @@ Array [ Object { "post": Object { "author": Object { + "avatar": "https://bsky.public.url/img/avatar/plain/user(1)/cids(1)@jpeg", + "createdAt": "1970-01-01T00:00:00.000Z", "did": "user(0)", + "displayName": "ali", "handle": "alice.test", - "labels": Array [], + "labels": Array [ + Object { + "cid": "cids(2)", + "cts": "1970-01-01T00:00:00.000Z", + "src": "user(0)", + "uri": "record(1)", + "val": "self-label-a", + }, + Object { + "cid": "cids(2)", + "cts": "1970-01-01T00:00:00.000Z", + "src": "user(0)", + "uri": "record(1)", + "val": "self-label-b", + }, + ], "viewer": Object { "blockedBy": false, "muted": false, }, }, - "cid": "cids(5)", + "cid": "cids(7)", "indexedAt": "1970-01-01T00:00:00.000Z", "labels": Array [], "likeCount": 0, @@ -2612,7 +2972,7 @@ Array [ }, "replyCount": 0, "repostCount": 0, - "uri": "record(5)", + "uri": "record(6)", "viewer": Object { "embeddingDisabled": false, "pinned": false, @@ -2623,15 +2983,33 @@ Array [ Object { "post": Object { "author": Object { + "avatar": "https://bsky.public.url/img/avatar/plain/user(1)/cids(1)@jpeg", + "createdAt": "1970-01-01T00:00:00.000Z", "did": "user(0)", + "displayName": "ali", "handle": "alice.test", - "labels": Array [], + "labels": Array [ + Object { + "cid": "cids(2)", + "cts": "1970-01-01T00:00:00.000Z", + "src": "user(0)", + "uri": "record(1)", + "val": "self-label-a", + }, + Object { + "cid": "cids(2)", + "cts": "1970-01-01T00:00:00.000Z", + "src": "user(0)", + "uri": "record(1)", + "val": "self-label-b", + }, + ], "viewer": Object { "blockedBy": false, "muted": false, }, }, - "cid": "cids(6)", + "cid": "cids(8)", "indexedAt": "1970-01-01T00:00:00.000Z", "labels": Array [], "likeCount": 0, @@ -2643,7 +3021,7 @@ Array [ }, "replyCount": 0, "repostCount": 0, - "uri": "record(6)", + "uri": "record(7)", "viewer": Object { "embeddingDisabled": false, "pinned": false, @@ -2654,15 +3032,33 @@ Array [ Object { "post": Object { "author": Object { + "avatar": "https://bsky.public.url/img/avatar/plain/user(1)/cids(1)@jpeg", + "createdAt": "1970-01-01T00:00:00.000Z", "did": "user(0)", + "displayName": "ali", "handle": "alice.test", - "labels": Array [], + "labels": Array [ + Object { + "cid": "cids(2)", + "cts": "1970-01-01T00:00:00.000Z", + "src": "user(0)", + "uri": "record(1)", + "val": "self-label-a", + }, + Object { + "cid": "cids(2)", + "cts": "1970-01-01T00:00:00.000Z", + "src": "user(0)", + "uri": "record(1)", + "val": "self-label-b", + }, + ], "viewer": Object { "blockedBy": false, "muted": false, }, }, - "cid": "cids(7)", + "cid": "cids(9)", "indexedAt": "1970-01-01T00:00:00.000Z", "labels": Array [], "likeCount": 0, @@ -2672,19 +3068,19 @@ Array [ "createdAt": "1970-01-01T00:00:00.000Z", "reply": Object { "parent": Object { - "cid": "cids(9)", - "uri": "record(9)", + "cid": "cids(11)", + "uri": "record(10)", }, "root": Object { - "cid": "cids(8)", - "uri": "record(8)", + "cid": "cids(10)", + "uri": "record(9)", }, }, "text": "thanks bob", }, "replyCount": 0, "repostCount": 1, - "uri": "record(7)", + "uri": "record(8)", "viewer": Object { "embeddingDisabled": false, "pinned": false, @@ -2693,9 +3089,27 @@ Array [ }, "reply": Object { "grandparentAuthor": Object { + "avatar": "https://bsky.public.url/img/avatar/plain/user(1)/cids(1)@jpeg", + "createdAt": "1970-01-01T00:00:00.000Z", "did": "user(0)", + "displayName": "ali", "handle": "alice.test", - "labels": Array [], + "labels": Array [ + Object { + "cid": "cids(2)", + "cts": "1970-01-01T00:00:00.000Z", + "src": "user(0)", + "uri": "record(1)", + "val": "self-label-a", + }, + Object { + "cid": "cids(2)", + "cts": "1970-01-01T00:00:00.000Z", + "src": "user(0)", + "uri": "record(1)", + "val": "self-label-b", + }, + ], "viewer": Object { "blockedBy": false, "muted": false, @@ -2704,44 +3118,44 @@ Array [ "parent": Object { "$type": "app.bsky.feed.defs#postView", "author": Object { - "avatar": "https://bsky.public.url/img/avatar/plain/user(2)/cids(10)@jpeg", + "avatar": "https://bsky.public.url/img/avatar/plain/user(3)/cids(1)@jpeg", "createdAt": "1970-01-01T00:00:00.000Z", - "did": "user(1)", + "did": "user(2)", "displayName": "bobby", "handle": "bob.test", "labels": Array [], "viewer": Object { "blockedBy": false, - "followedBy": "record(11)", - "following": "record(10)", + "followedBy": "record(12)", + "following": "record(11)", "muted": false, }, }, - "cid": "cids(9)", + "cid": "cids(11)", "embed": Object { "$type": "app.bsky.embed.images#view", "images": Array [ Object { "alt": "../dev-env/assets/key-landscape-small.jpg", - "fullsize": "https://bsky.public.url/img/feed_fullsize/plain/user(2)/cids(11)@jpeg", - "thumb": "https://bsky.public.url/img/feed_thumbnail/plain/user(2)/cids(11)@jpeg", + "fullsize": "https://bsky.public.url/img/feed_fullsize/plain/user(3)/cids(12)@jpeg", + "thumb": "https://bsky.public.url/img/feed_thumbnail/plain/user(3)/cids(12)@jpeg", }, ], }, "indexedAt": "1970-01-01T00:00:00.000Z", "labels": Array [ Object { - "cid": "cids(9)", + "cid": "cids(11)", "cts": "1970-01-01T00:00:00.000Z", "src": "did:example:labeler", - "uri": "record(9)", + "uri": "record(10)", "val": "test-label", }, Object { - "cid": "cids(9)", + "cid": "cids(11)", "cts": "1970-01-01T00:00:00.000Z", "src": "did:example:labeler", - "uri": "record(9)", + "uri": "record(10)", "val": "test-label-2", }, ], @@ -2759,7 +3173,7 @@ Array [ "$type": "blob", "mimeType": "image/jpeg", "ref": Object { - "$link": "cids(11)", + "$link": "cids(12)", }, "size": 4114, }, @@ -2768,19 +3182,19 @@ Array [ }, "reply": Object { "parent": Object { - "cid": "cids(8)", - "uri": "record(8)", + "cid": "cids(10)", + "uri": "record(9)", }, "root": Object { - "cid": "cids(8)", - "uri": "record(8)", + "cid": "cids(10)", + "uri": "record(9)", }, }, "text": "hear that label_me label_me_2", }, "replyCount": 1, "repostCount": 0, - "uri": "record(9)", + "uri": "record(10)", "viewer": Object { "embeddingDisabled": false, "threadMuted": false, @@ -2789,15 +3203,33 @@ Array [ "root": Object { "$type": "app.bsky.feed.defs#postView", "author": Object { + "avatar": "https://bsky.public.url/img/avatar/plain/user(1)/cids(1)@jpeg", + "createdAt": "1970-01-01T00:00:00.000Z", "did": "user(0)", + "displayName": "ali", "handle": "alice.test", - "labels": Array [], + "labels": Array [ + Object { + "cid": "cids(2)", + "cts": "1970-01-01T00:00:00.000Z", + "src": "user(0)", + "uri": "record(1)", + "val": "self-label-a", + }, + Object { + "cid": "cids(2)", + "cts": "1970-01-01T00:00:00.000Z", + "src": "user(0)", + "uri": "record(1)", + "val": "self-label-b", + }, + ], "viewer": Object { "blockedBy": false, "muted": false, }, }, - "cid": "cids(8)", + "cid": "cids(10)", "indexedAt": "1970-01-01T00:00:00.000Z", "labels": Array [], "likeCount": 3, @@ -2809,7 +3241,7 @@ Array [ }, "replyCount": 3, "repostCount": 1, - "uri": "record(8)", + "uri": "record(9)", "viewer": Object { "embeddingDisabled": false, "pinned": false, @@ -2821,15 +3253,33 @@ Array [ Object { "post": Object { "author": Object { + "avatar": "https://bsky.public.url/img/avatar/plain/user(1)/cids(1)@jpeg", + "createdAt": "1970-01-01T00:00:00.000Z", "did": "user(0)", + "displayName": "ali", "handle": "alice.test", - "labels": Array [], + "labels": Array [ + Object { + "cid": "cids(2)", + "cts": "1970-01-01T00:00:00.000Z", + "src": "user(0)", + "uri": "record(1)", + "val": "self-label-a", + }, + Object { + "cid": "cids(2)", + "cts": "1970-01-01T00:00:00.000Z", + "src": "user(0)", + "uri": "record(1)", + "val": "self-label-b", + }, + ], "viewer": Object { "blockedBy": false, "muted": false, }, }, - "cid": "cids(12)", + "cid": "cids(13)", "embed": Object { "$type": "app.bsky.embed.record#view", "record": Object { @@ -2840,40 +3290,40 @@ Array [ "allowIncoming": "none", }, }, - "did": "user(3)", + "did": "user(4)", "handle": "dan.test", "labels": Array [], "viewer": Object { "blockedBy": false, - "following": "record(14)", + "following": "record(15)", "muted": false, }, }, - "cid": "cids(13)", + "cid": "cids(14)", "embeds": Array [ Object { "$type": "app.bsky.embed.record#view", "record": Object { "$type": "app.bsky.embed.record#viewRecord", "author": Object { - "did": "user(4)", + "did": "user(5)", "handle": "carol.test", "labels": Array [], "viewer": Object { "blockedBy": false, - "followedBy": "record(17)", - "following": "record(16)", + "followedBy": "record(18)", + "following": "record(17)", "muted": false, }, }, - "cid": "cids(14)", + "cid": "cids(15)", "indexedAt": "1970-01-01T00:00:00.000Z", "labels": Array [], "likeCount": 2, "quoteCount": 1, "replyCount": 0, "repostCount": 0, - "uri": "record(15)", + "uri": "record(16)", "value": Object { "$type": "app.bsky.feed.post", "createdAt": "1970-01-01T00:00:00.000Z", @@ -2888,7 +3338,7 @@ Array [ "$type": "blob", "mimeType": "image/jpeg", "ref": Object { - "$link": "cids(11)", + "$link": "cids(12)", }, "size": 4114, }, @@ -2899,7 +3349,7 @@ Array [ "$type": "blob", "mimeType": "image/jpeg", "ref": Object { - "$link": "cids(15)", + "$link": "cids(16)", }, "size": 12736, }, @@ -2908,8 +3358,8 @@ Array [ }, "record": Object { "record": Object { - "cid": "cids(16)", - "uri": "record(18)", + "cid": "cids(17)", + "uri": "record(19)", }, }, }, @@ -2924,15 +3374,15 @@ Array [ "quoteCount": 1, "replyCount": 0, "repostCount": 1, - "uri": "record(13)", + "uri": "record(14)", "value": Object { "$type": "app.bsky.feed.post", "createdAt": "1970-01-01T00:00:00.000Z", "embed": Object { "$type": "app.bsky.embed.record", "record": Object { - "cid": "cids(14)", - "uri": "record(15)", + "cid": "cids(15)", + "uri": "record(16)", }, }, "facets": Array [ @@ -2956,10 +3406,10 @@ Array [ "indexedAt": "1970-01-01T00:00:00.000Z", "labels": Array [ Object { - "cid": "cids(12)", + "cid": "cids(13)", "cts": "1970-01-01T00:00:00.000Z", "src": "did:example:labeler", - "uri": "record(12)", + "uri": "record(13)", "val": "test-label", }, ], @@ -2971,15 +3421,15 @@ Array [ "embed": Object { "$type": "app.bsky.embed.record", "record": Object { - "cid": "cids(13)", - "uri": "record(13)", + "cid": "cids(14)", + "uri": "record(14)", }, }, "text": "yoohoo label_me", }, "replyCount": 0, "repostCount": 0, - "uri": "record(12)", + "uri": "record(13)", "viewer": Object { "embeddingDisabled": false, "pinned": false, @@ -2990,15 +3440,33 @@ Array [ Object { "post": Object { "author": Object { + "avatar": "https://bsky.public.url/img/avatar/plain/user(1)/cids(1)@jpeg", + "createdAt": "1970-01-01T00:00:00.000Z", "did": "user(0)", + "displayName": "ali", "handle": "alice.test", - "labels": Array [], + "labels": Array [ + Object { + "cid": "cids(2)", + "cts": "1970-01-01T00:00:00.000Z", + "src": "user(0)", + "uri": "record(1)", + "val": "self-label-a", + }, + Object { + "cid": "cids(2)", + "cts": "1970-01-01T00:00:00.000Z", + "src": "user(0)", + "uri": "record(1)", + "val": "self-label-b", + }, + ], "viewer": Object { "blockedBy": false, "muted": false, }, }, - "cid": "cids(8)", + "cid": "cids(10)", "indexedAt": "1970-01-01T00:00:00.000Z", "labels": Array [], "likeCount": 3, @@ -3010,7 +3478,7 @@ Array [ }, "replyCount": 3, "repostCount": 1, - "uri": "record(8)", + "uri": "record(9)", "viewer": Object { "embeddingDisabled": false, "pinned": false, @@ -3021,22 +3489,40 @@ Array [ Object { "post": Object { "author": Object { + "avatar": "https://bsky.public.url/img/avatar/plain/user(1)/cids(1)@jpeg", + "createdAt": "1970-01-01T00:00:00.000Z", "did": "user(0)", + "displayName": "ali", "handle": "alice.test", - "labels": Array [], + "labels": Array [ + Object { + "cid": "cids(2)", + "cts": "1970-01-01T00:00:00.000Z", + "src": "user(0)", + "uri": "record(1)", + "val": "self-label-a", + }, + Object { + "cid": "cids(2)", + "cts": "1970-01-01T00:00:00.000Z", + "src": "user(0)", + "uri": "record(1)", + "val": "self-label-b", + }, + ], "viewer": Object { "blockedBy": false, "muted": false, }, }, - "cid": "cids(17)", + "cid": "cids(18)", "indexedAt": "1970-01-01T00:00:00.000Z", "labels": Array [ Object { - "cid": "cids(17)", + "cid": "cids(18)", "cts": "1970-01-01T00:00:00.000Z", "src": "user(0)", - "uri": "record(19)", + "uri": "record(20)", "val": "self-label", }, ], @@ -3057,7 +3543,7 @@ Array [ }, "replyCount": 0, "repostCount": 0, - "uri": "record(19)", + "uri": "record(20)", "viewer": Object { "embeddingDisabled": false, "pinned": false, @@ -3073,9 +3559,27 @@ Array [ Object { "post": Object { "author": Object { + "avatar": "https://bsky.public.url/img/avatar/plain/user(1)/cids(1)@jpeg", + "createdAt": "1970-01-01T00:00:00.000Z", "did": "user(0)", + "displayName": "ali", "handle": "alice.test", - "labels": Array [], + "labels": Array [ + Object { + "cid": "cids(2)", + "cts": "1970-01-01T00:00:00.000Z", + "src": "user(0)", + "uri": "record(1)", + "val": "self-label-a", + }, + Object { + "cid": "cids(2)", + "cts": "1970-01-01T00:00:00.000Z", + "src": "user(0)", + "uri": "record(1)", + "val": "self-label-b", + }, + ], "viewer": Object { "blockedBy": false, "muted": false, @@ -3107,15 +3611,33 @@ Array [ Object { "post": Object { "author": Object { + "avatar": "https://bsky.public.url/img/avatar/plain/user(1)/cids(1)@jpeg", + "createdAt": "1970-01-01T00:00:00.000Z", "did": "user(0)", + "displayName": "ali", "handle": "alice.test", - "labels": Array [], + "labels": Array [ + Object { + "cid": "cids(2)", + "cts": "1970-01-01T00:00:00.000Z", + "src": "user(0)", + "uri": "record(1)", + "val": "self-label-a", + }, + Object { + "cid": "cids(2)", + "cts": "1970-01-01T00:00:00.000Z", + "src": "user(0)", + "uri": "record(1)", + "val": "self-label-b", + }, + ], "viewer": Object { "blockedBy": false, "muted": false, }, }, - "cid": "cids(1)", + "cid": "cids(3)", "indexedAt": "1970-01-01T00:00:00.000Z", "labels": Array [], "likeCount": 0, @@ -3127,7 +3649,7 @@ Array [ }, "replyCount": 0, "repostCount": 0, - "uri": "record(1)", + "uri": "record(2)", "viewer": Object { "embeddingDisabled": false, "pinned": false, @@ -3138,15 +3660,33 @@ Array [ Object { "post": Object { "author": Object { + "avatar": "https://bsky.public.url/img/avatar/plain/user(1)/cids(1)@jpeg", + "createdAt": "1970-01-01T00:00:00.000Z", "did": "user(0)", + "displayName": "ali", "handle": "alice.test", - "labels": Array [], + "labels": Array [ + Object { + "cid": "cids(2)", + "cts": "1970-01-01T00:00:00.000Z", + "src": "user(0)", + "uri": "record(1)", + "val": "self-label-a", + }, + Object { + "cid": "cids(2)", + "cts": "1970-01-01T00:00:00.000Z", + "src": "user(0)", + "uri": "record(1)", + "val": "self-label-b", + }, + ], "viewer": Object { "blockedBy": false, "muted": false, }, }, - "cid": "cids(2)", + "cid": "cids(4)", "indexedAt": "1970-01-01T00:00:00.000Z", "labels": Array [], "likeCount": 0, @@ -3158,7 +3698,7 @@ Array [ }, "replyCount": 0, "repostCount": 0, - "uri": "record(2)", + "uri": "record(3)", "viewer": Object { "embeddingDisabled": false, "pinned": false, @@ -3174,9 +3714,27 @@ Array [ Object { "post": Object { "author": Object { + "avatar": "https://bsky.public.url/img/avatar/plain/user(1)/cids(1)@jpeg", + "createdAt": "1970-01-01T00:00:00.000Z", "did": "user(0)", + "displayName": "ali", "handle": "alice.test", - "labels": Array [], + "labels": Array [ + Object { + "cid": "cids(2)", + "cts": "1970-01-01T00:00:00.000Z", + "src": "user(0)", + "uri": "record(1)", + "val": "self-label-a", + }, + Object { + "cid": "cids(2)", + "cts": "1970-01-01T00:00:00.000Z", + "src": "user(0)", + "uri": "record(1)", + "val": "self-label-b", + }, + ], "viewer": Object { "blockedBy": false, "muted": false, @@ -3205,15 +3763,33 @@ Array [ Object { "post": Object { "author": Object { + "avatar": "https://bsky.public.url/img/avatar/plain/user(1)/cids(1)@jpeg", + "createdAt": "1970-01-01T00:00:00.000Z", "did": "user(0)", + "displayName": "ali", "handle": "alice.test", - "labels": Array [], + "labels": Array [ + Object { + "cid": "cids(2)", + "cts": "1970-01-01T00:00:00.000Z", + "src": "user(0)", + "uri": "record(1)", + "val": "self-label-a", + }, + Object { + "cid": "cids(2)", + "cts": "1970-01-01T00:00:00.000Z", + "src": "user(0)", + "uri": "record(1)", + "val": "self-label-b", + }, + ], "viewer": Object { "blockedBy": false, "muted": false, }, }, - "cid": "cids(1)", + "cid": "cids(3)", "indexedAt": "1970-01-01T00:00:00.000Z", "labels": Array [], "likeCount": 0, @@ -3225,7 +3801,7 @@ Array [ }, "replyCount": 0, "repostCount": 0, - "uri": "record(1)", + "uri": "record(2)", "viewer": Object { "embeddingDisabled": false, "pinned": false, @@ -3236,15 +3812,33 @@ Array [ Object { "post": Object { "author": Object { + "avatar": "https://bsky.public.url/img/avatar/plain/user(1)/cids(1)@jpeg", + "createdAt": "1970-01-01T00:00:00.000Z", "did": "user(0)", + "displayName": "ali", "handle": "alice.test", - "labels": Array [], + "labels": Array [ + Object { + "cid": "cids(2)", + "cts": "1970-01-01T00:00:00.000Z", + "src": "user(0)", + "uri": "record(1)", + "val": "self-label-a", + }, + Object { + "cid": "cids(2)", + "cts": "1970-01-01T00:00:00.000Z", + "src": "user(0)", + "uri": "record(1)", + "val": "self-label-b", + }, + ], "viewer": Object { "blockedBy": false, "muted": false, }, }, - "cid": "cids(2)", + "cid": "cids(4)", "indexedAt": "1970-01-01T00:00:00.000Z", "labels": Array [], "likeCount": 0, @@ -3256,7 +3850,7 @@ Array [ }, "replyCount": 0, "repostCount": 0, - "uri": "record(2)", + "uri": "record(3)", "viewer": Object { "embeddingDisabled": false, "pinned": false, @@ -3267,15 +3861,33 @@ Array [ Object { "post": Object { "author": Object { + "avatar": "https://bsky.public.url/img/avatar/plain/user(1)/cids(1)@jpeg", + "createdAt": "1970-01-01T00:00:00.000Z", "did": "user(0)", + "displayName": "ali", "handle": "alice.test", - "labels": Array [], + "labels": Array [ + Object { + "cid": "cids(2)", + "cts": "1970-01-01T00:00:00.000Z", + "src": "user(0)", + "uri": "record(1)", + "val": "self-label-a", + }, + Object { + "cid": "cids(2)", + "cts": "1970-01-01T00:00:00.000Z", + "src": "user(0)", + "uri": "record(1)", + "val": "self-label-b", + }, + ], "viewer": Object { "blockedBy": false, "muted": false, }, }, - "cid": "cids(3)", + "cid": "cids(5)", "indexedAt": "1970-01-01T00:00:00.000Z", "labels": Array [], "likeCount": 0, @@ -3287,7 +3899,7 @@ Array [ }, "replyCount": 0, "repostCount": 0, - "uri": "record(3)", + "uri": "record(4)", "viewer": Object { "embeddingDisabled": false, "pinned": false, @@ -3298,15 +3910,33 @@ Array [ Object { "post": Object { "author": Object { + "avatar": "https://bsky.public.url/img/avatar/plain/user(1)/cids(1)@jpeg", + "createdAt": "1970-01-01T00:00:00.000Z", "did": "user(0)", + "displayName": "ali", "handle": "alice.test", - "labels": Array [], + "labels": Array [ + Object { + "cid": "cids(2)", + "cts": "1970-01-01T00:00:00.000Z", + "src": "user(0)", + "uri": "record(1)", + "val": "self-label-a", + }, + Object { + "cid": "cids(2)", + "cts": "1970-01-01T00:00:00.000Z", + "src": "user(0)", + "uri": "record(1)", + "val": "self-label-b", + }, + ], "viewer": Object { "blockedBy": false, "muted": false, }, }, - "cid": "cids(4)", + "cid": "cids(6)", "indexedAt": "1970-01-01T00:00:00.000Z", "labels": Array [], "likeCount": 0, @@ -3316,19 +3946,19 @@ Array [ "createdAt": "1970-01-01T00:00:00.000Z", "reply": Object { "parent": Object { - "cid": "cids(6)", - "uri": "record(6)", + "cid": "cids(8)", + "uri": "record(7)", }, "root": Object { - "cid": "cids(5)", - "uri": "record(5)", + "cid": "cids(7)", + "uri": "record(6)", }, }, "text": "thanks bob", }, "replyCount": 0, "repostCount": 1, - "uri": "record(4)", + "uri": "record(5)", "viewer": Object { "embeddingDisabled": false, "pinned": false, @@ -3337,9 +3967,27 @@ Array [ }, "reply": Object { "grandparentAuthor": Object { + "avatar": "https://bsky.public.url/img/avatar/plain/user(1)/cids(1)@jpeg", + "createdAt": "1970-01-01T00:00:00.000Z", "did": "user(0)", + "displayName": "ali", "handle": "alice.test", - "labels": Array [], + "labels": Array [ + Object { + "cid": "cids(2)", + "cts": "1970-01-01T00:00:00.000Z", + "src": "user(0)", + "uri": "record(1)", + "val": "self-label-a", + }, + Object { + "cid": "cids(2)", + "cts": "1970-01-01T00:00:00.000Z", + "src": "user(0)", + "uri": "record(1)", + "val": "self-label-b", + }, + ], "viewer": Object { "blockedBy": false, "muted": false, @@ -3348,44 +3996,44 @@ Array [ "parent": Object { "$type": "app.bsky.feed.defs#postView", "author": Object { - "avatar": "https://bsky.public.url/img/avatar/plain/user(2)/cids(7)@jpeg", + "avatar": "https://bsky.public.url/img/avatar/plain/user(3)/cids(1)@jpeg", "createdAt": "1970-01-01T00:00:00.000Z", - "did": "user(1)", + "did": "user(2)", "displayName": "bobby", "handle": "bob.test", "labels": Array [], "viewer": Object { "blockedBy": false, - "followedBy": "record(8)", - "following": "record(7)", + "followedBy": "record(9)", + "following": "record(8)", "muted": false, }, }, - "cid": "cids(6)", + "cid": "cids(8)", "embed": Object { "$type": "app.bsky.embed.images#view", "images": Array [ Object { "alt": "../dev-env/assets/key-landscape-small.jpg", - "fullsize": "https://bsky.public.url/img/feed_fullsize/plain/user(2)/cids(8)@jpeg", - "thumb": "https://bsky.public.url/img/feed_thumbnail/plain/user(2)/cids(8)@jpeg", + "fullsize": "https://bsky.public.url/img/feed_fullsize/plain/user(3)/cids(9)@jpeg", + "thumb": "https://bsky.public.url/img/feed_thumbnail/plain/user(3)/cids(9)@jpeg", }, ], }, "indexedAt": "1970-01-01T00:00:00.000Z", "labels": Array [ Object { - "cid": "cids(6)", + "cid": "cids(8)", "cts": "1970-01-01T00:00:00.000Z", "src": "did:example:labeler", - "uri": "record(6)", + "uri": "record(7)", "val": "test-label", }, Object { - "cid": "cids(6)", + "cid": "cids(8)", "cts": "1970-01-01T00:00:00.000Z", "src": "did:example:labeler", - "uri": "record(6)", + "uri": "record(7)", "val": "test-label-2", }, ], @@ -3403,7 +4051,7 @@ Array [ "$type": "blob", "mimeType": "image/jpeg", "ref": Object { - "$link": "cids(8)", + "$link": "cids(9)", }, "size": 4114, }, @@ -3412,19 +4060,19 @@ Array [ }, "reply": Object { "parent": Object { - "cid": "cids(5)", - "uri": "record(5)", + "cid": "cids(7)", + "uri": "record(6)", }, "root": Object { - "cid": "cids(5)", - "uri": "record(5)", + "cid": "cids(7)", + "uri": "record(6)", }, }, "text": "hear that label_me label_me_2", }, "replyCount": 1, "repostCount": 0, - "uri": "record(6)", + "uri": "record(7)", "viewer": Object { "embeddingDisabled": false, "threadMuted": false, @@ -3433,15 +4081,33 @@ Array [ "root": Object { "$type": "app.bsky.feed.defs#postView", "author": Object { + "avatar": "https://bsky.public.url/img/avatar/plain/user(1)/cids(1)@jpeg", + "createdAt": "1970-01-01T00:00:00.000Z", "did": "user(0)", + "displayName": "ali", "handle": "alice.test", - "labels": Array [], + "labels": Array [ + Object { + "cid": "cids(2)", + "cts": "1970-01-01T00:00:00.000Z", + "src": "user(0)", + "uri": "record(1)", + "val": "self-label-a", + }, + Object { + "cid": "cids(2)", + "cts": "1970-01-01T00:00:00.000Z", + "src": "user(0)", + "uri": "record(1)", + "val": "self-label-b", + }, + ], "viewer": Object { "blockedBy": false, "muted": false, }, }, - "cid": "cids(5)", + "cid": "cids(7)", "indexedAt": "1970-01-01T00:00:00.000Z", "labels": Array [], "likeCount": 3, @@ -3453,7 +4119,7 @@ Array [ }, "replyCount": 3, "repostCount": 1, - "uri": "record(5)", + "uri": "record(6)", "viewer": Object { "embeddingDisabled": false, "pinned": false, @@ -3465,15 +4131,33 @@ Array [ Object { "post": Object { "author": Object { + "avatar": "https://bsky.public.url/img/avatar/plain/user(1)/cids(1)@jpeg", + "createdAt": "1970-01-01T00:00:00.000Z", "did": "user(0)", + "displayName": "ali", "handle": "alice.test", - "labels": Array [], + "labels": Array [ + Object { + "cid": "cids(2)", + "cts": "1970-01-01T00:00:00.000Z", + "src": "user(0)", + "uri": "record(1)", + "val": "self-label-a", + }, + Object { + "cid": "cids(2)", + "cts": "1970-01-01T00:00:00.000Z", + "src": "user(0)", + "uri": "record(1)", + "val": "self-label-b", + }, + ], "viewer": Object { "blockedBy": false, "muted": false, }, }, - "cid": "cids(9)", + "cid": "cids(10)", "embed": Object { "$type": "app.bsky.embed.record#view", "record": Object { @@ -3484,40 +4168,40 @@ Array [ "allowIncoming": "none", }, }, - "did": "user(3)", + "did": "user(4)", "handle": "dan.test", "labels": Array [], "viewer": Object { "blockedBy": false, - "following": "record(11)", + "following": "record(12)", "muted": false, }, }, - "cid": "cids(10)", + "cid": "cids(11)", "embeds": Array [ Object { "$type": "app.bsky.embed.record#view", "record": Object { "$type": "app.bsky.embed.record#viewRecord", "author": Object { - "did": "user(4)", + "did": "user(5)", "handle": "carol.test", "labels": Array [], "viewer": Object { "blockedBy": false, - "followedBy": "record(14)", - "following": "record(13)", + "followedBy": "record(15)", + "following": "record(14)", "muted": false, }, }, - "cid": "cids(11)", + "cid": "cids(12)", "indexedAt": "1970-01-01T00:00:00.000Z", "labels": Array [], "likeCount": 2, "quoteCount": 1, "replyCount": 0, "repostCount": 0, - "uri": "record(12)", + "uri": "record(13)", "value": Object { "$type": "app.bsky.feed.post", "createdAt": "1970-01-01T00:00:00.000Z", @@ -3532,7 +4216,7 @@ Array [ "$type": "blob", "mimeType": "image/jpeg", "ref": Object { - "$link": "cids(8)", + "$link": "cids(9)", }, "size": 4114, }, @@ -3543,7 +4227,7 @@ Array [ "$type": "blob", "mimeType": "image/jpeg", "ref": Object { - "$link": "cids(12)", + "$link": "cids(13)", }, "size": 12736, }, @@ -3552,8 +4236,8 @@ Array [ }, "record": Object { "record": Object { - "cid": "cids(13)", - "uri": "record(15)", + "cid": "cids(14)", + "uri": "record(16)", }, }, }, @@ -3568,15 +4252,15 @@ Array [ "quoteCount": 1, "replyCount": 0, "repostCount": 1, - "uri": "record(10)", + "uri": "record(11)", "value": Object { "$type": "app.bsky.feed.post", "createdAt": "1970-01-01T00:00:00.000Z", "embed": Object { "$type": "app.bsky.embed.record", "record": Object { - "cid": "cids(11)", - "uri": "record(12)", + "cid": "cids(12)", + "uri": "record(13)", }, }, "facets": Array [ @@ -3600,10 +4284,10 @@ Array [ "indexedAt": "1970-01-01T00:00:00.000Z", "labels": Array [ Object { - "cid": "cids(9)", + "cid": "cids(10)", "cts": "1970-01-01T00:00:00.000Z", "src": "did:example:labeler", - "uri": "record(9)", + "uri": "record(10)", "val": "test-label", }, ], @@ -3615,15 +4299,15 @@ Array [ "embed": Object { "$type": "app.bsky.embed.record", "record": Object { - "cid": "cids(10)", - "uri": "record(10)", + "cid": "cids(11)", + "uri": "record(11)", }, }, "text": "yoohoo label_me", }, "replyCount": 0, "repostCount": 0, - "uri": "record(9)", + "uri": "record(10)", "viewer": Object { "embeddingDisabled": false, "pinned": false, @@ -3634,15 +4318,33 @@ Array [ Object { "post": Object { "author": Object { + "avatar": "https://bsky.public.url/img/avatar/plain/user(1)/cids(1)@jpeg", + "createdAt": "1970-01-01T00:00:00.000Z", "did": "user(0)", + "displayName": "ali", "handle": "alice.test", - "labels": Array [], + "labels": Array [ + Object { + "cid": "cids(2)", + "cts": "1970-01-01T00:00:00.000Z", + "src": "user(0)", + "uri": "record(1)", + "val": "self-label-a", + }, + Object { + "cid": "cids(2)", + "cts": "1970-01-01T00:00:00.000Z", + "src": "user(0)", + "uri": "record(1)", + "val": "self-label-b", + }, + ], "viewer": Object { "blockedBy": false, "muted": false, }, }, - "cid": "cids(5)", + "cid": "cids(7)", "indexedAt": "1970-01-01T00:00:00.000Z", "labels": Array [], "likeCount": 3, @@ -3654,7 +4356,7 @@ Array [ }, "replyCount": 3, "repostCount": 1, - "uri": "record(5)", + "uri": "record(6)", "viewer": Object { "embeddingDisabled": false, "pinned": false, @@ -3665,22 +4367,40 @@ Array [ Object { "post": Object { "author": Object { + "avatar": "https://bsky.public.url/img/avatar/plain/user(1)/cids(1)@jpeg", + "createdAt": "1970-01-01T00:00:00.000Z", "did": "user(0)", + "displayName": "ali", "handle": "alice.test", - "labels": Array [], + "labels": Array [ + Object { + "cid": "cids(2)", + "cts": "1970-01-01T00:00:00.000Z", + "src": "user(0)", + "uri": "record(1)", + "val": "self-label-a", + }, + Object { + "cid": "cids(2)", + "cts": "1970-01-01T00:00:00.000Z", + "src": "user(0)", + "uri": "record(1)", + "val": "self-label-b", + }, + ], "viewer": Object { "blockedBy": false, "muted": false, }, }, - "cid": "cids(14)", + "cid": "cids(15)", "indexedAt": "1970-01-01T00:00:00.000Z", "labels": Array [ Object { - "cid": "cids(14)", + "cid": "cids(15)", "cts": "1970-01-01T00:00:00.000Z", "src": "user(0)", - "uri": "record(16)", + "uri": "record(17)", "val": "self-label", }, ], @@ -3701,7 +4421,7 @@ Array [ }, "replyCount": 0, "repostCount": 0, - "uri": "record(16)", + "uri": "record(17)", "viewer": Object { "embeddingDisabled": false, "pinned": false, @@ -3717,9 +4437,27 @@ Array [ Object { "post": Object { "author": Object { + "avatar": "https://bsky.public.url/img/avatar/plain/user(1)/cids(1)@jpeg", + "createdAt": "1970-01-01T00:00:00.000Z", "did": "user(0)", + "displayName": "ali", "handle": "alice.test", - "labels": Array [], + "labels": Array [ + Object { + "cid": "cids(2)", + "cts": "1970-01-01T00:00:00.000Z", + "src": "user(0)", + "uri": "record(1)", + "val": "self-label-a", + }, + Object { + "cid": "cids(2)", + "cts": "1970-01-01T00:00:00.000Z", + "src": "user(0)", + "uri": "record(1)", + "val": "self-label-b", + }, + ], "viewer": Object { "blockedBy": false, "muted": false, @@ -3751,15 +4489,33 @@ Array [ Object { "post": Object { "author": Object { + "avatar": "https://bsky.public.url/img/avatar/plain/user(1)/cids(1)@jpeg", + "createdAt": "1970-01-01T00:00:00.000Z", "did": "user(0)", + "displayName": "ali", "handle": "alice.test", - "labels": Array [], + "labels": Array [ + Object { + "cid": "cids(2)", + "cts": "1970-01-01T00:00:00.000Z", + "src": "user(0)", + "uri": "record(1)", + "val": "self-label-a", + }, + Object { + "cid": "cids(2)", + "cts": "1970-01-01T00:00:00.000Z", + "src": "user(0)", + "uri": "record(1)", + "val": "self-label-b", + }, + ], "viewer": Object { "blockedBy": false, "muted": false, }, }, - "cid": "cids(1)", + "cid": "cids(3)", "indexedAt": "1970-01-01T00:00:00.000Z", "labels": Array [], "likeCount": 0, @@ -3771,7 +4527,7 @@ Array [ }, "replyCount": 0, "repostCount": 0, - "uri": "record(1)", + "uri": "record(2)", "viewer": Object { "embeddingDisabled": false, "pinned": false, @@ -3782,15 +4538,33 @@ Array [ Object { "post": Object { "author": Object { + "avatar": "https://bsky.public.url/img/avatar/plain/user(1)/cids(1)@jpeg", + "createdAt": "1970-01-01T00:00:00.000Z", "did": "user(0)", + "displayName": "ali", "handle": "alice.test", - "labels": Array [], + "labels": Array [ + Object { + "cid": "cids(2)", + "cts": "1970-01-01T00:00:00.000Z", + "src": "user(0)", + "uri": "record(1)", + "val": "self-label-a", + }, + Object { + "cid": "cids(2)", + "cts": "1970-01-01T00:00:00.000Z", + "src": "user(0)", + "uri": "record(1)", + "val": "self-label-b", + }, + ], "viewer": Object { "blockedBy": false, "muted": false, }, }, - "cid": "cids(2)", + "cid": "cids(4)", "indexedAt": "1970-01-01T00:00:00.000Z", "labels": Array [], "likeCount": 0, @@ -3802,7 +4576,7 @@ Array [ }, "replyCount": 0, "repostCount": 0, - "uri": "record(2)", + "uri": "record(3)", "viewer": Object { "embeddingDisabled": false, "pinned": false, @@ -3813,15 +4587,33 @@ Array [ Object { "post": Object { "author": Object { + "avatar": "https://bsky.public.url/img/avatar/plain/user(1)/cids(1)@jpeg", + "createdAt": "1970-01-01T00:00:00.000Z", "did": "user(0)", + "displayName": "ali", "handle": "alice.test", - "labels": Array [], + "labels": Array [ + Object { + "cid": "cids(2)", + "cts": "1970-01-01T00:00:00.000Z", + "src": "user(0)", + "uri": "record(1)", + "val": "self-label-a", + }, + Object { + "cid": "cids(2)", + "cts": "1970-01-01T00:00:00.000Z", + "src": "user(0)", + "uri": "record(1)", + "val": "self-label-b", + }, + ], "viewer": Object { "blockedBy": false, "muted": false, }, }, - "cid": "cids(3)", + "cid": "cids(5)", "indexedAt": "1970-01-01T00:00:00.000Z", "labels": Array [], "likeCount": 0, @@ -3831,19 +4623,19 @@ Array [ "createdAt": "1970-01-01T00:00:00.000Z", "reply": Object { "parent": Object { - "cid": "cids(5)", - "uri": "record(5)", + "cid": "cids(7)", + "uri": "record(6)", }, "root": Object { - "cid": "cids(4)", - "uri": "record(4)", + "cid": "cids(6)", + "uri": "record(5)", }, }, "text": "thanks bob", }, "replyCount": 0, "repostCount": 1, - "uri": "record(3)", + "uri": "record(4)", "viewer": Object { "embeddingDisabled": false, "pinned": false, @@ -3852,9 +4644,27 @@ Array [ }, "reply": Object { "grandparentAuthor": Object { + "avatar": "https://bsky.public.url/img/avatar/plain/user(1)/cids(1)@jpeg", + "createdAt": "1970-01-01T00:00:00.000Z", "did": "user(0)", + "displayName": "ali", "handle": "alice.test", - "labels": Array [], + "labels": Array [ + Object { + "cid": "cids(2)", + "cts": "1970-01-01T00:00:00.000Z", + "src": "user(0)", + "uri": "record(1)", + "val": "self-label-a", + }, + Object { + "cid": "cids(2)", + "cts": "1970-01-01T00:00:00.000Z", + "src": "user(0)", + "uri": "record(1)", + "val": "self-label-b", + }, + ], "viewer": Object { "blockedBy": false, "muted": false, @@ -3863,44 +4673,44 @@ Array [ "parent": Object { "$type": "app.bsky.feed.defs#postView", "author": Object { - "avatar": "https://bsky.public.url/img/avatar/plain/user(2)/cids(6)@jpeg", + "avatar": "https://bsky.public.url/img/avatar/plain/user(3)/cids(1)@jpeg", "createdAt": "1970-01-01T00:00:00.000Z", - "did": "user(1)", + "did": "user(2)", "displayName": "bobby", "handle": "bob.test", "labels": Array [], "viewer": Object { "blockedBy": false, - "followedBy": "record(7)", - "following": "record(6)", + "followedBy": "record(8)", + "following": "record(7)", "muted": false, }, }, - "cid": "cids(5)", + "cid": "cids(7)", "embed": Object { "$type": "app.bsky.embed.images#view", "images": Array [ Object { "alt": "../dev-env/assets/key-landscape-small.jpg", - "fullsize": "https://bsky.public.url/img/feed_fullsize/plain/user(2)/cids(7)@jpeg", - "thumb": "https://bsky.public.url/img/feed_thumbnail/plain/user(2)/cids(7)@jpeg", + "fullsize": "https://bsky.public.url/img/feed_fullsize/plain/user(3)/cids(8)@jpeg", + "thumb": "https://bsky.public.url/img/feed_thumbnail/plain/user(3)/cids(8)@jpeg", }, ], }, "indexedAt": "1970-01-01T00:00:00.000Z", "labels": Array [ Object { - "cid": "cids(5)", + "cid": "cids(7)", "cts": "1970-01-01T00:00:00.000Z", "src": "did:example:labeler", - "uri": "record(5)", + "uri": "record(6)", "val": "test-label", }, Object { - "cid": "cids(5)", + "cid": "cids(7)", "cts": "1970-01-01T00:00:00.000Z", "src": "did:example:labeler", - "uri": "record(5)", + "uri": "record(6)", "val": "test-label-2", }, ], @@ -3918,7 +4728,7 @@ Array [ "$type": "blob", "mimeType": "image/jpeg", "ref": Object { - "$link": "cids(7)", + "$link": "cids(8)", }, "size": 4114, }, @@ -3927,19 +4737,19 @@ Array [ }, "reply": Object { "parent": Object { - "cid": "cids(4)", - "uri": "record(4)", + "cid": "cids(6)", + "uri": "record(5)", }, "root": Object { - "cid": "cids(4)", - "uri": "record(4)", + "cid": "cids(6)", + "uri": "record(5)", }, }, "text": "hear that label_me label_me_2", }, "replyCount": 1, "repostCount": 0, - "uri": "record(5)", + "uri": "record(6)", "viewer": Object { "embeddingDisabled": false, "threadMuted": false, @@ -3948,15 +4758,33 @@ Array [ "root": Object { "$type": "app.bsky.feed.defs#postView", "author": Object { + "avatar": "https://bsky.public.url/img/avatar/plain/user(1)/cids(1)@jpeg", + "createdAt": "1970-01-01T00:00:00.000Z", "did": "user(0)", + "displayName": "ali", "handle": "alice.test", - "labels": Array [], + "labels": Array [ + Object { + "cid": "cids(2)", + "cts": "1970-01-01T00:00:00.000Z", + "src": "user(0)", + "uri": "record(1)", + "val": "self-label-a", + }, + Object { + "cid": "cids(2)", + "cts": "1970-01-01T00:00:00.000Z", + "src": "user(0)", + "uri": "record(1)", + "val": "self-label-b", + }, + ], "viewer": Object { "blockedBy": false, "muted": false, }, }, - "cid": "cids(4)", + "cid": "cids(6)", "indexedAt": "1970-01-01T00:00:00.000Z", "labels": Array [], "likeCount": 3, @@ -3968,7 +4796,7 @@ Array [ }, "replyCount": 3, "repostCount": 1, - "uri": "record(4)", + "uri": "record(5)", "viewer": Object { "embeddingDisabled": false, "pinned": false, @@ -3980,15 +4808,33 @@ Array [ Object { "post": Object { "author": Object { + "avatar": "https://bsky.public.url/img/avatar/plain/user(1)/cids(1)@jpeg", + "createdAt": "1970-01-01T00:00:00.000Z", "did": "user(0)", + "displayName": "ali", "handle": "alice.test", - "labels": Array [], + "labels": Array [ + Object { + "cid": "cids(2)", + "cts": "1970-01-01T00:00:00.000Z", + "src": "user(0)", + "uri": "record(1)", + "val": "self-label-a", + }, + Object { + "cid": "cids(2)", + "cts": "1970-01-01T00:00:00.000Z", + "src": "user(0)", + "uri": "record(1)", + "val": "self-label-b", + }, + ], "viewer": Object { "blockedBy": false, "muted": false, }, }, - "cid": "cids(8)", + "cid": "cids(9)", "embed": Object { "$type": "app.bsky.embed.record#view", "record": Object { @@ -3999,40 +4845,40 @@ Array [ "allowIncoming": "none", }, }, - "did": "user(3)", + "did": "user(4)", "handle": "dan.test", "labels": Array [], "viewer": Object { "blockedBy": false, - "following": "record(10)", + "following": "record(11)", "muted": false, }, }, - "cid": "cids(9)", + "cid": "cids(10)", "embeds": Array [ Object { "$type": "app.bsky.embed.record#view", "record": Object { "$type": "app.bsky.embed.record#viewRecord", "author": Object { - "did": "user(4)", + "did": "user(5)", "handle": "carol.test", "labels": Array [], "viewer": Object { "blockedBy": false, - "followedBy": "record(13)", - "following": "record(12)", + "followedBy": "record(14)", + "following": "record(13)", "muted": false, }, }, - "cid": "cids(10)", + "cid": "cids(11)", "indexedAt": "1970-01-01T00:00:00.000Z", "labels": Array [], "likeCount": 2, "quoteCount": 1, "replyCount": 0, "repostCount": 0, - "uri": "record(11)", + "uri": "record(12)", "value": Object { "$type": "app.bsky.feed.post", "createdAt": "1970-01-01T00:00:00.000Z", @@ -4047,7 +4893,7 @@ Array [ "$type": "blob", "mimeType": "image/jpeg", "ref": Object { - "$link": "cids(7)", + "$link": "cids(8)", }, "size": 4114, }, @@ -4058,7 +4904,7 @@ Array [ "$type": "blob", "mimeType": "image/jpeg", "ref": Object { - "$link": "cids(11)", + "$link": "cids(12)", }, "size": 12736, }, @@ -4067,8 +4913,8 @@ Array [ }, "record": Object { "record": Object { - "cid": "cids(12)", - "uri": "record(14)", + "cid": "cids(13)", + "uri": "record(15)", }, }, }, @@ -4083,15 +4929,15 @@ Array [ "quoteCount": 1, "replyCount": 0, "repostCount": 1, - "uri": "record(9)", + "uri": "record(10)", "value": Object { "$type": "app.bsky.feed.post", "createdAt": "1970-01-01T00:00:00.000Z", "embed": Object { "$type": "app.bsky.embed.record", "record": Object { - "cid": "cids(10)", - "uri": "record(11)", + "cid": "cids(11)", + "uri": "record(12)", }, }, "facets": Array [ @@ -4115,10 +4961,10 @@ Array [ "indexedAt": "1970-01-01T00:00:00.000Z", "labels": Array [ Object { - "cid": "cids(8)", + "cid": "cids(9)", "cts": "1970-01-01T00:00:00.000Z", "src": "did:example:labeler", - "uri": "record(8)", + "uri": "record(9)", "val": "test-label", }, ], @@ -4130,15 +4976,15 @@ Array [ "embed": Object { "$type": "app.bsky.embed.record", "record": Object { - "cid": "cids(9)", - "uri": "record(9)", + "cid": "cids(10)", + "uri": "record(10)", }, }, "text": "yoohoo label_me", }, "replyCount": 0, "repostCount": 0, - "uri": "record(8)", + "uri": "record(9)", "viewer": Object { "embeddingDisabled": false, "pinned": false, @@ -4149,15 +4995,33 @@ Array [ Object { "post": Object { "author": Object { + "avatar": "https://bsky.public.url/img/avatar/plain/user(1)/cids(1)@jpeg", + "createdAt": "1970-01-01T00:00:00.000Z", "did": "user(0)", + "displayName": "ali", "handle": "alice.test", - "labels": Array [], + "labels": Array [ + Object { + "cid": "cids(2)", + "cts": "1970-01-01T00:00:00.000Z", + "src": "user(0)", + "uri": "record(1)", + "val": "self-label-a", + }, + Object { + "cid": "cids(2)", + "cts": "1970-01-01T00:00:00.000Z", + "src": "user(0)", + "uri": "record(1)", + "val": "self-label-b", + }, + ], "viewer": Object { "blockedBy": false, "muted": false, }, }, - "cid": "cids(4)", + "cid": "cids(6)", "indexedAt": "1970-01-01T00:00:00.000Z", "labels": Array [], "likeCount": 3, @@ -4169,7 +5033,7 @@ Array [ }, "replyCount": 3, "repostCount": 1, - "uri": "record(4)", + "uri": "record(5)", "viewer": Object { "embeddingDisabled": false, "pinned": false, @@ -4180,22 +5044,40 @@ Array [ Object { "post": Object { "author": Object { + "avatar": "https://bsky.public.url/img/avatar/plain/user(1)/cids(1)@jpeg", + "createdAt": "1970-01-01T00:00:00.000Z", "did": "user(0)", + "displayName": "ali", "handle": "alice.test", - "labels": Array [], + "labels": Array [ + Object { + "cid": "cids(2)", + "cts": "1970-01-01T00:00:00.000Z", + "src": "user(0)", + "uri": "record(1)", + "val": "self-label-a", + }, + Object { + "cid": "cids(2)", + "cts": "1970-01-01T00:00:00.000Z", + "src": "user(0)", + "uri": "record(1)", + "val": "self-label-b", + }, + ], "viewer": Object { "blockedBy": false, "muted": false, }, }, - "cid": "cids(13)", + "cid": "cids(14)", "indexedAt": "1970-01-01T00:00:00.000Z", "labels": Array [ Object { - "cid": "cids(13)", + "cid": "cids(14)", "cts": "1970-01-01T00:00:00.000Z", "src": "user(0)", - "uri": "record(15)", + "uri": "record(16)", "val": "self-label", }, ], @@ -4216,7 +5098,7 @@ Array [ }, "replyCount": 0, "repostCount": 0, - "uri": "record(15)", + "uri": "record(16)", "viewer": Object { "embeddingDisabled": false, "pinned": false, diff --git a/packages/bsky/tests/views/author-feed.test.ts b/packages/bsky/tests/views/author-feed.test.ts index 2e473d48a76..b1dbb225970 100644 --- a/packages/bsky/tests/views/author-feed.test.ts +++ b/packages/bsky/tests/views/author-feed.test.ts @@ -1,11 +1,24 @@ -import { AppBskyActorProfile, AppBskyFeedDefs, AtpAgent } from '@atproto/api' +import assert from 'node:assert' +import { AtpAgent } from '@atproto/api' import { SeedClient, TestNetwork, authorFeedSeed } from '@atproto/dev-env' import { ids } from '../../src/lexicon/lexicons' +import { + Record as Profile, + validateRecord as validatePostRecord, +} from '../../src/lexicon/types/app/bsky/actor/profile' import { isView as isImageEmbed } from '../../src/lexicon/types/app/bsky/embed/images' import { isView as isEmbedRecordWithMedia } from '../../src/lexicon/types/app/bsky/embed/recordWithMedia' import { isView as isVideoEmbed } from '../../src/lexicon/types/app/bsky/embed/video' -import { isPostView } from '../../src/lexicon/types/app/bsky/feed/defs' -import { ReplyRef, isRecord } from '../../src/lexicon/types/app/bsky/feed/post' +import { + isPostView, + isReasonPin, +} from '../../src/lexicon/types/app/bsky/feed/defs' +import { + ReplyRef, + isRecord, + validateReplyRef, +} from '../../src/lexicon/types/app/bsky/feed/post' +import { asPredicate } from '../../src/lexicon/util' import { uriToDid } from '../../src/util/uris' import { VideoEmbed } from '../../src/views/types' import { @@ -15,6 +28,9 @@ import { stripViewerFromPost, } from '../_util' +const isValidReplyRef = asPredicate(validateReplyRef) +const isValidProfile = asPredicate(validatePostRecord) + describe('pds author feed views', () => { let network: TestNetwork let agent: AtpAgent @@ -181,8 +197,8 @@ describe('pds author feed views', () => { } if (item.reply) { result.reply = { - parent: stripViewerFromPost(item.reply.parent), - root: stripViewerFromPost(item.reply.root), + parent: stripViewerFromPost(item.reply.parent, true), + root: stripViewerFromPost(item.reply.root, true), grandparentAuthor: item.reply.grandparentAuthor && stripViewer(item.reply.grandparentAuthor), @@ -291,7 +307,7 @@ describe('pds author feed views', () => { }) expect(carolFeed.feed.length).toBeGreaterThan(0) - expect( + assert( carolFeed.feed.every(({ post }) => { const isRecordWithActorMedia = isEmbedRecordWithMedia(post.embed) && isImageEmbed(post.embed?.media) @@ -300,18 +316,18 @@ describe('pds author feed views', () => { return (isRecordWithActorMedia || isActorMedia) && isFromActor }), - ).toBeTruthy() + ) const { data: bobFeed } = await agent.api.app.bsky.feed.getAuthorFeed({ actor: bob, filter: 'posts_with_media', }) - expect( + assert( bobFeed.feed.every(({ post }) => { return isImageEmbed(post.embed) && post.author.did === bob }), - ).toBeTruthy() + ) const { data: danFeed } = await agent.api.app.bsky.feed.getAuthorFeed({ actor: dan, @@ -389,14 +405,14 @@ describe('pds author feed views', () => { filter: 'posts_no_replies', }) - expect( + assert( carolFeed.feed.every(({ post }) => { return ( (isRecord(post.record) && !post.record.reply) || (isRecord(post.record) && post.record.reply) ) }), - ).toBeTruthy() + ) const { data: danFeed } = await agent.api.app.bsky.feed.getAuthorFeed({ actor: dan, @@ -430,7 +446,11 @@ describe('pds author feed views', () => { // does not include eve's replies to fred, even within her own thread. expect( eveFeed.feed.every(({ post, reply }) => { - if (!post || !isRecord(post.record) || !post.record.reply) { + if ( + !post || + !isRecord(post.record) || + !isValidReplyRef(post.record.reply) + ) { return true // not a reply } const replyToEve = isReplyTo(post.record.reply, eve) @@ -438,7 +458,7 @@ describe('pds author feed views', () => { reply && isPostView(reply.parent) && isRecord(reply.parent.record) && - (!reply.parent.record.reply || + (!isValidReplyRef(reply.parent.record.reply) || isReplyTo(reply.parent.record.reply, eve)) return replyToEve && replyToReplyByEve }), @@ -464,12 +484,10 @@ describe('pds author feed views', () => { rkey: 'self', }) - if (!AppBskyActorProfile.isRecord(profile.data.value)) { - throw new Error('') - } + assert(isValidProfile(profile.data.value)) - const newProfile: AppBskyActorProfile.Record = { - ...profile, + const newProfile: Profile = { + ...profile.data.value, pinnedPost: { uri: post.ref.uriStr, cid: post.ref.cid.toString(), @@ -505,8 +523,8 @@ describe('pds author feed views', () => { const pinnedPost = data.feed.at(0) expect(pinnedPost?.post?.uri).toEqual(post.ref.uriStr) - expect(pinnedPost?.post?.viewer?.pinned).toBeTruthy() - expect(AppBskyFeedDefs.isReasonPin(pinnedPost?.reason)).toBeTruthy() + assert(pinnedPost?.post?.viewer?.pinned) + assert(isReasonPin(pinnedPost?.reason)) const notPinnedPost = data.feed.at(1) expect(notPinnedPost?.post?.viewer?.pinned).toBeFalsy() @@ -533,8 +551,8 @@ describe('pds author feed views', () => { (item) => item.post.uri === post.ref.uriStr, ) expect(pinnedPost?.post?.uri).toEqual(post.ref.uriStr) - expect(pinnedPost?.post?.viewer?.pinned).toBeTruthy() - expect(AppBskyFeedDefs.isReasonPin(pinnedPost?.reason)).toBeTruthy() + assert(pinnedPost?.post?.viewer?.pinned) + assert(isReasonPin(pinnedPost?.reason)) expect(forSnapshot(page1.feed)).toMatchSnapshot() const { data: page2 } = await agent.api.app.bsky.feed.getAuthorFeed( @@ -556,8 +574,8 @@ describe('pds author feed views', () => { (item) => item.post.uri === post.ref.uriStr, ) expect(laterPinnedPost?.post?.uri).toEqual(post.ref.uriStr) - expect(laterPinnedPost?.post?.viewer?.pinned).toBeTruthy() - expect(AppBskyFeedDefs.isReasonPin(laterPinnedPost?.reason)).toBeFalsy() + assert(laterPinnedPost?.post?.viewer?.pinned) + expect(isReasonPin(laterPinnedPost?.reason)).toBeFalsy() expect(forSnapshot(page2.feed)).toMatchSnapshot() }) @@ -577,7 +595,7 @@ describe('pds author feed views', () => { const pinnedPost = data.feed.find( (item) => item.post.uri === post.ref.uriStr, ) - expect(AppBskyFeedDefs.isReasonPin(pinnedPost?.reason)).toBeFalsy() + expect(isReasonPin(pinnedPost?.reason)).toBeFalsy() expect(forSnapshot(data.feed)).toMatchSnapshot() }) @@ -592,12 +610,10 @@ describe('pds author feed views', () => { rkey: 'self', }) - if (!AppBskyActorProfile.isRecord(profile.data.value)) { - throw new Error('') - } + assert(isValidProfile(profile.data.value)) - const newProfile: AppBskyActorProfile.Record = { - ...profile, + const newProfile: Profile = { + ...profile.data.value, pinnedPost: { uri: bobPost.ref.uriStr, cid: bobPost.ref.cid.toString(), diff --git a/packages/bsky/tests/views/blocks.test.ts b/packages/bsky/tests/views/blocks.test.ts index a0e5a599625..ac3e8028389 100644 --- a/packages/bsky/tests/views/blocks.test.ts +++ b/packages/bsky/tests/views/blocks.test.ts @@ -2,6 +2,8 @@ import assert from 'node:assert' import { AtUri, AtpAgent } from '@atproto/api' import { RecordRef, SeedClient, TestNetwork, basicSeed } from '@atproto/dev-env' import { ids } from '../../src/lexicon/lexicons' +import { isView as isRecordEmbedView } from '../../src/lexicon/types/app/bsky/embed/record' +import { isPostView } from '../../src/lexicon/types/app/bsky/feed/defs' import { assertIsThreadViewPost, forSnapshot } from '../_util' describe('pds views with blocking', () => { @@ -220,7 +222,8 @@ describe('pds views with blocking', () => { resCarol.data.feed.some( (post) => post.post.author.did === dan || - post.reply?.parent.author?.['did'] === dan || + (isPostView(post.reply?.parent) && + post.reply.parent.author.did === dan) || post.reply?.grandparentAuthor?.did === dan, ), ).toBeFalsy() @@ -235,7 +238,8 @@ describe('pds views with blocking', () => { resDan.data.feed.some( (post) => post.post.author.did === carol || - post.reply?.parent.author?.['did'] === carol || + (isPostView(post.reply?.parent) && + post.reply.parent.author.did === carol) || post.reply?.grandparentAuthor?.did === carol, ), ).toBeFalsy() @@ -658,7 +662,8 @@ describe('pds views with blocking', () => { assertIsThreadViewPost(embedThenBlock.thread) - expect(embedThenBlock.thread.post.embed?.record).toMatchObject({ + assert(isRecordEmbedView(embedThenBlock.thread.post.embed)) + expect(embedThenBlock.thread.post.embed.record).toMatchObject({ $type: 'app.bsky.embed.record#viewBlocked', }) @@ -680,7 +685,8 @@ describe('pds views with blocking', () => { assertIsThreadViewPost(unblock.thread) - expect(unblock.thread.post?.embed?.record).toMatchObject({ + assert(isRecordEmbedView(unblock.thread.post?.embed)) + expect(unblock.thread.post?.embed.record).toMatchObject({ $type: 'app.bsky.embed.record#viewRecord', }) @@ -709,8 +715,8 @@ describe('pds views with blocking', () => { }, ) assertIsThreadViewPost(blockThenEmbed.thread) - - expect(blockThenEmbed.thread.post.embed?.record).toMatchObject({ + assert(isRecordEmbedView(blockThenEmbed.thread.post.embed)) + expect(blockThenEmbed.thread.post.embed.record).toMatchObject({ $type: 'app.bsky.embed.record#viewBlocked', }) @@ -753,7 +759,8 @@ describe('pds views with blocking', () => { (item) => item.post.uri === embedBlockedUri, ) assert(embedBlockedPost) - expect(embedBlockedPost.post.embed?.record).toMatchObject({ + assert(isRecordEmbedView(embedBlockedPost.post.embed)) + expect(embedBlockedPost.post.embed.record).toMatchObject({ $type: 'app.bsky.embed.record#viewBlocked', }) }) diff --git a/packages/bsky/tests/views/feed-view-post.test.ts b/packages/bsky/tests/views/feed-view-post.test.ts index d9fd35da1d8..673fe790085 100644 --- a/packages/bsky/tests/views/feed-view-post.test.ts +++ b/packages/bsky/tests/views/feed-view-post.test.ts @@ -1,3 +1,4 @@ +import assert from 'node:assert' import { AppBskyFeedDefs, AtUri, AtpAgent } from '@atproto/api' import { SeedClient, TestNetwork, basicSeed } from '@atproto/dev-env' import { ids } from '../../src/lexicon/lexicons' @@ -87,8 +88,8 @@ describe('pds thread views', () => { throw new Error('sliceA or sliceB is undefined') } - expect(AppBskyFeedDefs.isBlockedPost(sliceB.reply?.parent)).toBe(true) - expect(AppBskyFeedDefs.isBlockedPost(sliceB.reply?.root)).toBe(true) + assert(AppBskyFeedDefs.isBlockedPost(sliceB.reply?.parent)) + assert(AppBskyFeedDefs.isBlockedPost(sliceB.reply?.root)) await pdsAgent.api.app.bsky.graph.block.delete( { repo: alice, rkey: new AtUri(block.uri).rkey }, @@ -129,10 +130,10 @@ describe('pds thread views', () => { throw new Error('sliceC is undefined') } + assert(AppBskyFeedDefs.isPostView(sliceC.reply.parent)) expect(sliceC.reply.parent.uri).toEqual(B.ref.uriStr) + assert(AppBskyFeedDefs.isBlockedPost(sliceC.reply.root)) expect(sliceC.reply.root.uri).toEqual(A.ref.uriStr) - expect(AppBskyFeedDefs.isPostView(sliceC.reply.parent)).toBe(true) - expect(AppBskyFeedDefs.isBlockedPost(sliceC.reply.root)).toBe(true) await pdsAgent.api.app.bsky.graph.block.delete( { repo: alice, rkey: new AtUri(block.uri).rkey }, @@ -249,10 +250,10 @@ describe('pds thread views', () => { throw new Error('sliceD is undefined') } + assert(AppBskyFeedDefs.isPostView(sliceD.reply.parent)) expect(sliceD.reply.parent.uri).toEqual(C.ref.uriStr) + assert(AppBskyFeedDefs.isBlockedPost(sliceD.reply.root)) expect(sliceD.reply.root.uri).toEqual(A.ref.uriStr) - expect(AppBskyFeedDefs.isPostView(sliceD.reply.parent)).toBe(true) - expect(AppBskyFeedDefs.isBlockedPost(sliceD.reply.root)).toBe(true) await pdsAgent.api.app.bsky.graph.block.delete( { repo: alice, rkey: new AtUri(block.uri).rkey }, @@ -292,10 +293,10 @@ describe('pds thread views', () => { throw new Error('sliceD is undefined') } + assert(AppBskyFeedDefs.isPostView(sliceD.reply.parent)) expect(sliceD.reply.parent.uri).toEqual(C.ref.uriStr) + assert(AppBskyFeedDefs.isBlockedPost(sliceD.reply.root)) expect(sliceD.reply.root.uri).toEqual(A.ref.uriStr) - expect(AppBskyFeedDefs.isPostView(sliceD.reply.parent)).toBe(true) - expect(AppBskyFeedDefs.isBlockedPost(sliceD.reply.root)).toBe(true) await pdsAgent.api.app.bsky.graph.block.delete( { repo: alice, rkey: new AtUri(block.uri).rkey }, @@ -338,13 +339,13 @@ describe('pds thread views', () => { throw new Error('sliceD is undefined') } + assert(AppBskyFeedDefs.isPostView(sliceD.reply.parent)) expect(sliceD.reply.parent.uri).toEqual(C.ref.uriStr) - expect(sliceD.reply.root.uri).toEqual(A.ref.uriStr) - expect(AppBskyFeedDefs.isPostView(sliceD.reply.parent)).toBe(true) /* * We don't walk the reply ancestors past whats available in the ReplyRef */ - expect(AppBskyFeedDefs.isPostView(sliceD.reply.root)).toBe(true) + assert(AppBskyFeedDefs.isPostView(sliceD.reply.root)) + expect(sliceD.reply.root.uri).toEqual(A.ref.uriStr) await pdsAgent.api.app.bsky.graph.block.delete( { repo: alice, rkey: new AtUri(block.uri).rkey }, @@ -384,13 +385,14 @@ describe('pds thread views', () => { throw new Error('sliceD is undefined') } + assert(AppBskyFeedDefs.isPostView(sliceD.reply.parent)) expect(sliceD.reply.parent.uri).toEqual(C.ref.uriStr) - expect(sliceD.reply.root.uri).toEqual(A.ref.uriStr) - expect(AppBskyFeedDefs.isPostView(sliceD.reply.parent)).toBe(true) + /* * We don't walk the reply ancestors past whats available in the ReplyRef */ - expect(AppBskyFeedDefs.isPostView(sliceD.reply.root)).toBe(true) + assert(AppBskyFeedDefs.isPostView(sliceD.reply.root)) + expect(sliceD.reply.root.uri).toEqual(A.ref.uriStr) await pdsAgent.api.app.bsky.graph.block.delete( { repo: bob, rkey: new AtUri(block.uri).rkey }, @@ -434,9 +436,9 @@ describe('pds thread views', () => { throw new Error('sliceD or sliceC is undefined') } - expect(AppBskyFeedDefs.isBlockedPost(sliceD.reply?.root)).toBe(true) - expect(AppBskyFeedDefs.isPostView(sliceC.reply?.parent)).toBe(true) - expect(AppBskyFeedDefs.isPostView(sliceC.reply?.root)).toBe(true) + assert(AppBskyFeedDefs.isBlockedPost(sliceD.reply?.root)) + assert(AppBskyFeedDefs.isPostView(sliceC.reply?.parent)) + assert(AppBskyFeedDefs.isPostView(sliceC.reply?.root)) await pdsAgent.api.app.bsky.graph.block.delete( { repo: alice, rkey: new AtUri(block.uri).rkey }, @@ -488,10 +490,10 @@ describe('pds thread views', () => { throw new Error('sliceD is undefined') } + assert(AppBskyFeedDefs.isPostView(sliceD.reply.parent)) expect(sliceD.reply.parent.uri).toEqual(C.ref.uriStr) + assert(AppBskyFeedDefs.isBlockedPost(sliceD.reply.root)) expect(sliceD.reply.root.uri).toEqual(A.ref.uriStr) - expect(AppBskyFeedDefs.isPostView(sliceD.reply.parent)).toBe(true) - expect(AppBskyFeedDefs.isBlockedPost(sliceD.reply.root)).toBe(true) await pdsAgent.api.app.bsky.graph.block.delete( { repo: alice, rkey: new AtUri(block.uri).rkey }, diff --git a/packages/bsky/tests/views/labeler-service.test.ts b/packages/bsky/tests/views/labeler-service.test.ts index e78517f0fff..d3a03be3dc3 100644 --- a/packages/bsky/tests/views/labeler-service.test.ts +++ b/packages/bsky/tests/views/labeler-service.test.ts @@ -1,6 +1,8 @@ +import assert from 'node:assert' import { AtpAgent } from '@atproto/api' import { RecordRef, SeedClient, TestNetwork, basicSeed } from '@atproto/dev-env' import { ids } from '../../src/lexicon/lexicons' +import { isView as isRecordEmbedView } from '../../src/lexicon/types/app/bsky/embed/record' import { forSnapshot, stripViewerFromLabeler } from '../_util' describe('labeler service views', () => { @@ -150,7 +152,8 @@ describe('labeler service views', () => { const serviceViews = await agent.api.app.bsky.labeler.getServices({ dids: [alice], }) - expect(postViews.data.posts[0].embed?.record).toMatchObject( + assert(isRecordEmbedView(postViews.data.posts[0].embed)) + expect(postViews.data.posts[0].embed.record).toMatchObject( serviceViews.data.views[0], ) }) diff --git a/packages/bsky/tests/views/labels-takedown.test.ts b/packages/bsky/tests/views/labels-takedown.test.ts index 98b59810268..0cb2bbbbbac 100644 --- a/packages/bsky/tests/views/labels-takedown.test.ts +++ b/packages/bsky/tests/views/labels-takedown.test.ts @@ -1,4 +1,5 @@ -import { AtpAgent } from '@atproto/api' +import assert from 'node:assert' +import { AppBskyLabelerDefs, AtpAgent } from '@atproto/api' import { RecordRef, SeedClient, TestNetwork, basicSeed } from '@atproto/dev-env' import { ids } from '../../src/lexicon/lexicons' @@ -232,7 +233,8 @@ describe('bsky takedown labels', () => { dids: [sc.dids.labeler1, sc.dids.labeler2], }) expect(res.data.views.length).toBe(1) - expect(res.data.views[0].creator?.['did']).toBe(sc.dids.labeler2) + assert(AppBskyLabelerDefs.isLabelerView(res.data.views[0])) + expect(res.data.views[0].creator.did).toBe(sc.dids.labeler2) }) it('only applies if the relevant labeler is configured', async () => { diff --git a/packages/bsky/tests/views/list-feed.test.ts b/packages/bsky/tests/views/list-feed.test.ts index f869119f634..19bcc6d43d8 100644 --- a/packages/bsky/tests/views/list-feed.test.ts +++ b/packages/bsky/tests/views/list-feed.test.ts @@ -118,11 +118,14 @@ describe('list feed views', () => { } if (item.reply) { result.reply = { - parent: stripViewerFromPost(item.reply.parent), - root: stripViewerFromPost(item.reply.root), - grandparentAuthor: - item.reply.grandparentAuthor && - stripViewer(item.reply.grandparentAuthor), + parent: stripViewerFromPost(item.reply.parent, true), + root: stripViewerFromPost(item.reply.root, true), + } + + if (item.reply.grandparentAuthor) { + result.reply.grandparentAuthor = stripViewer( + item.reply.grandparentAuthor, + ) } } return result diff --git a/packages/bsky/tests/views/posts.test.ts b/packages/bsky/tests/views/posts.test.ts index 56f89832f73..1f90937e1dc 100644 --- a/packages/bsky/tests/views/posts.test.ts +++ b/packages/bsky/tests/views/posts.test.ts @@ -1,4 +1,4 @@ -import { AppBskyFeedPost, AtpAgent } from '@atproto/api' +import { AppBskyFeedPost, AtpAgent, Un$Typed } from '@atproto/api' import { SeedClient, TestNetwork, basicSeed } from '@atproto/dev-env' import { RecordWithMedia } from '../../dist/views/types' import { ids } from '../../src/lexicon/lexicons' @@ -96,7 +96,7 @@ describe('pds posts views', () => { }) it('allows for creating posts with tags', async () => { - const post: AppBskyFeedPost.Record = { + const post: Un$Typed = { text: 'hello world', tags: ['javascript', 'hehe'], createdAt: new Date().toISOString(), diff --git a/packages/bsky/tests/views/starter-packs.test.ts b/packages/bsky/tests/views/starter-packs.test.ts index 5751c948884..218a65fd801 100644 --- a/packages/bsky/tests/views/starter-packs.test.ts +++ b/packages/bsky/tests/views/starter-packs.test.ts @@ -1,10 +1,12 @@ import assert from 'node:assert' -import { AtpAgent } from '@atproto/api' +import { AtpAgent, asPredicate } from '@atproto/api' import { RecordRef, SeedClient, TestNetwork, basicSeed } from '@atproto/dev-env' import { ids } from '../../src/lexicon/lexicons' -import { isRecord as isProfile } from '../../src/lexicon/types/app/bsky/actor/profile' +import { validateRecord as validateProfileRecord } from '../../src/lexicon/types/app/bsky/actor/profile' import { forSnapshot } from '../_util' +const isValidProfile = asPredicate(validateProfileRecord) + describe('starter packs', () => { let network: TestNetwork let agent: AtpAgent @@ -147,7 +149,7 @@ describe('starter packs', () => { expect(notif.reason).toBe('starterpack-joined') expect(notif.reasonSubject).toBe(sp1.uriStr) expect(notif.uri).toMatch(/\/app\.bsky\.actor\.profile\/self$/) - assert(isProfile(notif.record), 'record is not profile') + assert(isValidProfile(notif.record), 'record is not profile') expect(notif.record.joinedViaStarterPack?.uri).toBe(sp1.uriStr) }) expect(forSnapshot(notifications)).toMatchSnapshot() diff --git a/packages/bsky/tests/views/thread.test.ts b/packages/bsky/tests/views/thread.test.ts index 4b46f06b4fd..4c3bf849053 100644 --- a/packages/bsky/tests/views/thread.test.ts +++ b/packages/bsky/tests/views/thread.test.ts @@ -1,6 +1,8 @@ +import assert from 'node:assert' import { AppBskyFeedGetPostThread, AtpAgent } from '@atproto/api' import { SeedClient, TestNetwork, basicSeed } from '@atproto/dev-env' import { ids } from '../../src/lexicon/lexicons' +import { isThreadViewPost } from '../../src/lexicon/types/app/bsky/feed/defs' import { assertIsThreadViewPost, forSnapshot, @@ -404,7 +406,10 @@ describe('pds thread views', () => { }, ) - const parent = threadPreTakedown.data.thread.parent?.['post'] + assert(isThreadViewPost(threadPreTakedown.data.thread)) + assert(isThreadViewPost(threadPreTakedown.data.thread.parent)) + + const parent = threadPreTakedown.data.thread.parent.post await network.bsky.ctx.dataplane.takedownRecord({ recordUri: parent.uri, @@ -439,8 +444,18 @@ describe('pds thread views', () => { ), }, ) + + assert(isThreadViewPost(threadPreTakedown.data.thread)) + assert(isThreadViewPost(threadPreTakedown.data.thread.replies?.[0])) + assert(isThreadViewPost(threadPreTakedown.data.thread.replies?.[1])) + assert( + isThreadViewPost( + threadPreTakedown.data.thread.replies?.[1].replies?.[0], + ), + ) + const post1 = threadPreTakedown.data.thread.replies?.[0].post - const post2 = threadPreTakedown.data.thread.replies?.[1].replies[0].post + const post2 = threadPreTakedown.data.thread.replies?.[1].replies?.[0].post await Promise.all( [post1, post2].map((post) => diff --git a/packages/dev-env/src/moderator-client.ts b/packages/dev-env/src/moderator-client.ts index eb9212fb913..603eef4fb3e 100644 --- a/packages/dev-env/src/moderator-client.ts +++ b/packages/dev-env/src/moderator-client.ts @@ -62,7 +62,7 @@ export class ModeratorClient { subjectBlobCids?: TakeActionInput['subjectBlobCids'] reason?: string createdBy?: string - meta?: TakeActionInput['meta'] + meta?: unknown }, role?: ModLevel, ) { @@ -74,7 +74,14 @@ export class ModeratorClient { createdBy = 'did:example:admin', } = opts const result = await this.agent.tools.ozone.moderation.emitEvent( - { event, subject, subjectBlobCids, createdBy, reason }, + { + event, + subject, + subjectBlobCids, + createdBy, + // @ts-expect-error this a valid input property + reason, + }, { encoding: 'application/json', headers: await this.ozone.modHeaders( diff --git a/packages/dev-env/src/seed/client.ts b/packages/dev-env/src/seed/client.ts index 61babc4dd23..d160f657d6a 100644 --- a/packages/dev-env/src/seed/client.ts +++ b/packages/dev-env/src/seed/client.ts @@ -4,6 +4,7 @@ import { CID } from 'multiformats/cid' import { AppBskyFeedLike, AppBskyFeedPost, + AppBskyGraphBlock, AppBskyGraphFollow, AppBskyGraphList, AppBskyRichtextFacet, @@ -264,7 +265,7 @@ export class SeedClient< async block( from: string, to: string, - overrides?: Partial, + overrides?: Partial, ) { const res = await this.agent.app.bsky.graph.block.create( { repo: from }, diff --git a/packages/lex-cli/src/codegen/client.ts b/packages/lex-cli/src/codegen/client.ts index 1ec1d6fa836..e0084410819 100644 --- a/packages/lex-cli/src/codegen/client.ts +++ b/packages/lex-cli/src/codegen/client.ts @@ -9,9 +9,9 @@ import { NSID } from '@atproto/syntax' import { GeneratedAPI } from '../types' import { gen, lexiconsTs, utilTs } from './common' import { + genCommonImports, genImports, - genObjHelpers, - genObject, + genRecord, genUserType, genXrpcInput, genXrpcOutput, @@ -81,6 +81,11 @@ const indexTs = ( }) .addNamedImports([{ name: 'CID' }]) + //= import {OmitKey} from './util' + file + .addImportDeclaration({ moduleSpecifier: `./util` }) + .addNamedImports([{ name: 'OmitKey' }, { name: 'Un$Typed' }]) + // generate type imports and re-exports for (const lexicon of lexiconDocs) { const moduleSpecifier = `./types/${lexicon.id.split('.').join('/')}` @@ -312,7 +317,7 @@ function genRecordCls(file: SourceFile, nsid: string, lexRecord: LexRecord) { }) method.addParameter({ name: 'params', - type: `Omit<${toTitleCase(ATP_METHODS.list)}.QueryParams, "collection">`, + type: `OmitKey<${toTitleCase(ATP_METHODS.list)}.QueryParams, "collection">`, }) method.setBodyText( [ @@ -330,7 +335,7 @@ function genRecordCls(file: SourceFile, nsid: string, lexRecord: LexRecord) { }) method.addParameter({ name: 'params', - type: `Omit<${toTitleCase(ATP_METHODS.get)}.QueryParams, "collection">`, + type: `OmitKey<${toTitleCase(ATP_METHODS.get)}.QueryParams, "collection">`, }) method.setBodyText( [ @@ -348,13 +353,13 @@ function genRecordCls(file: SourceFile, nsid: string, lexRecord: LexRecord) { }) method.addParameter({ name: 'params', - type: `Omit<${toTitleCase( + type: `OmitKey<${toTitleCase( ATP_METHODS.create, )}.InputSchema, "collection" | "record">`, }) method.addParameter({ name: 'record', - type: `${typeModule}.Record`, + type: `Un$Typed<${typeModule}.Record>`, }) method.addParameter({ name: 'headers?', @@ -365,8 +370,8 @@ function genRecordCls(file: SourceFile, nsid: string, lexRecord: LexRecord) { : '' method.setBodyText( [ - `record.$type = '${nsid}'`, - `const res = await this._client.call('${ATP_METHODS.create}', undefined, { collection: '${nsid}', ${maybeRkeyPart}...params, record }, {encoding: 'application/json', headers })`, + `const collection = '${nsid}'`, + `const res = await this._client.call('${ATP_METHODS.create}', undefined, { collection, ${maybeRkeyPart}...params, record: { ...record, $type: collection} }, {encoding: 'application/json', headers })`, `return res.data`, ].join('\n'), ) @@ -380,7 +385,7 @@ function genRecordCls(file: SourceFile, nsid: string, lexRecord: LexRecord) { // }) // method.addParameter({ // name: 'params', - // type: `Omit<${toTitleCase(ATP_METHODS.put)}.InputSchema, "collection" | "record">`, + // type: `OmitKey<${toTitleCase(ATP_METHODS.put)}.InputSchema, "collection" | "record">`, // }) // method.addParameter({ // name: 'record', @@ -407,7 +412,7 @@ function genRecordCls(file: SourceFile, nsid: string, lexRecord: LexRecord) { }) method.addParameter({ name: 'params', - type: `Omit<${toTitleCase( + type: `OmitKey<${toTitleCase( ATP_METHODS.delete, )}.InputSchema, "collection">`, }) @@ -429,8 +434,6 @@ const lexiconTs = (project, lexicons: Lexicons, lexiconDoc: LexiconDoc) => project, `/types/${lexiconDoc.id.split('.').join('/')}.ts`, async (file) => { - const imports: Set = new Set() - const main = lexiconDoc.defs.main if ( main?.type === 'query' || @@ -446,37 +449,10 @@ const lexiconTs = (project, lexicons: Lexicons, lexiconDoc: LexiconDoc) => { name: 'XRPCError' }, ]) } - //= import {ValidationResult, BlobRef} from '@atproto/lexicon' - file - .addImportDeclaration({ - moduleSpecifier: '@atproto/lexicon', - }) - .addNamedImports([{ name: 'ValidationResult' }, { name: 'BlobRef' }]) - //= import {isObj, hasProp} from '../../util.ts' - file - .addImportDeclaration({ - moduleSpecifier: `${lexiconDoc.id - .split('.') - .map((_str) => '..') - .join('/')}/util`, - }) - .addNamedImports([{ name: 'isObj' }, { name: 'hasProp' }]) - //= import {lexicons} from '../../lexicons.ts' - file - .addImportDeclaration({ - moduleSpecifier: `${lexiconDoc.id - .split('.') - .map((_str) => '..') - .join('/')}/lexicons`, - }) - .addNamedImports([{ name: 'lexicons' }]) - //= import {CID} from 'multiformats/cid' - file - .addImportDeclaration({ - moduleSpecifier: 'multiformats/cid', - }) - .addNamedImports([{ name: 'CID' }]) + genCommonImports(file, lexiconDoc.id) + + const imports: Set = new Set() for (const defId in lexiconDoc.defs) { const def = lexiconDoc.defs[defId] const lexUri = `${lexiconDoc.id}#${defId}` @@ -489,7 +465,7 @@ const lexiconTs = (project, lexicons: Lexicons, lexiconDoc: LexiconDoc) => } else if (def.type === 'subscription') { continue } else if (def.type === 'record') { - genClientRecord(file, imports, lexicons, lexUri) + genRecord(file, imports, lexicons, lexUri) } else { genUserType(file, imports, lexicons, lexUri) } @@ -586,17 +562,3 @@ function genClientXrpcCommon( : ['return e'], }) } - -function genClientRecord( - file: SourceFile, - imports: Set, - lexicons: Lexicons, - lexUri: string, -) { - const def = lexicons.getDefOrThrow(lexUri, ['record']) - - //= export interface Record {...} - genObject(file, imports, lexUri, def.record, 'Record') - //= export function isRecord(v: unknown): v is Record {...} - genObjHelpers(file, lexUri, 'Record') -} diff --git a/packages/lex-cli/src/codegen/common.ts b/packages/lex-cli/src/codegen/common.ts index 4ebaf748040..3438bc986e7 100644 --- a/packages/lex-cli/src/codegen/common.ts +++ b/packages/lex-cli/src/codegen/common.ts @@ -1,28 +1,133 @@ -import { format } from 'prettier' +import { Options as PrettierOptions, format } from 'prettier' import { Project, SourceFile, VariableDeclarationKind } from 'ts-morph' import { LexiconDoc } from '@atproto/lexicon' import { GeneratedFile } from '../types' -const PRETTIER_OPTS = { +const PRETTIER_OPTS: PrettierOptions = { parser: 'typescript', tabWidth: 2, semi: false, singleQuote: true, - trailingComma: 'all' as const, + trailingComma: 'all', } export const utilTs = (project) => gen(project, '/util.ts', async (file) => { file.replaceWithText(` -export function isObj(v: unknown): v is Record { - return typeof v === 'object' && v !== null +import { ValidationResult } from '@atproto/lexicon' + +export type OmitKey = { + [K2 in keyof T as K2 extends K ? never : K2]: T[K2] +} + +export type $Typed = V & { $type: T } +export type Un$Typed = OmitKey + +export type $Type = Hash extends 'main' + ? Id + : \`\${Id}#\${Hash}\` + +function isObject(v: V): v is V & object { + return v != null && typeof v === 'object' +} + +function compare$type( + $type: unknown, + id: Id, + hash: Hash, +): $type is $Type { + return hash === 'main' + ? $type === id + : // $type === \`\${id}#\${hash}\` + typeof $type === 'string' && + $type.length === id.length + 1 + hash.length && + $type.charCodeAt(id.length) === 35 /* '#' */ && + $type.startsWith(id) && + $type.endsWith(hash) +} +${ + /** + * The construct below allows to properly distinguish open unions. Consider + * the following example: + * + * ```ts + * type Foo = { $type?: $Type<'foo', 'main'>; foo: string } + * type Bar = { $type?: $Type<'bar', 'main'>; bar: string } + * type OpenFooBarUnion = $Typed | $Typed | { $type: string } + * ``` + * + * In the context of lexicons, when there is a open union as shown above, the + * if `$type` if either `foo` or `bar`, then the object IS of type `Foo` or + * `Bar`. + * + * ```ts + * declare const obj1: OpenFooBarUnion + * if (is$typed(obj1, 'foo', 'main')) { + * obj1.$type // $Type<'foo', 'main'> + * obj1.foo // string + * } + * ``` + * + * Similarly, if an object is of type `unknown`, then the `is$typed` function + * should only return assurance about the `$type` property, which is what it + * actually checks: + * + * ```ts + * declare const obj2: unknown + * if (is$typed(obj2, 'foo', 'main')) { + * obj2.$type // $Type<'foo', 'main'> + * // @ts-expect-error + * obj2.foo + * } + * ``` + * + * The construct bellow is what makes these two scenarios possible. + */ + '' } +export type $TypedObject = V extends { + $type: $Type +} + ? V + : V extends { $type?: string } + ? V extends { $type?: infer T extends $Type } + ? V & { $type: T } + : never + : V & { $type: $Type } + +export function is$typed( + v: V, + id: Id, + hash: Hash, +): v is $TypedObject { + return isObject(v) && '$type' in v && compare$type(v.$type, id, hash) +} + +export function maybe$typed( + v: V, + id: Id, + hash: Hash, +): v is V & object & { $type?: $Type } { + return ( + isObject(v) && + ('$type' in v + ? v.$type === undefined || compare$type(v.$type, id, hash) + : true) + ) +} + +export type Validator = (v: unknown) => ValidationResult +export type ValidatorParam = + V extends Validator ? R : never -export function hasProp( - data: object, - prop: K, -): data is Record { - return prop in data +/** + * Utility function that allows to convert a "validate*" utility function into a + * type predicate. + */ +export function asPredicate(validate: V) { + return function (v: T): v is T & ValidatorParam { + return validate(v).success + } } `) }) @@ -41,7 +146,23 @@ export const lexiconsTs = (project, lexicons: LexiconDoc[]) => .addImportDeclaration({ moduleSpecifier: '@atproto/lexicon', }) - .addNamedImports([{ name: 'LexiconDoc' }, { name: 'Lexicons' }]) + .addNamedImports([ + { name: 'LexiconDoc' }, + { name: 'Lexicons' }, + { name: 'ValidationError' }, + { name: 'ValidationResult' }, + ]) + + //= import {is$typed, maybe$typed, $Typed} from './util' + file + .addImportDeclaration({ + moduleSpecifier: './util', + }) + .addNamedImports([ + { name: '$Typed' }, + { name: 'is$typed' }, + { name: 'maybe$typed' }, + ]) //= export const schemaDict = {...} as const satisfies Record file.addVariableStatement({ @@ -66,14 +187,14 @@ export const lexiconsTs = (project, lexicons: LexiconDoc[]) => ], }) - //= export const schemas = Object.values(schemaDict) + //= export const schemas = Object.values(schemaDict) satisfies LexiconDoc[] file.addVariableStatement({ isExported: true, declarationKind: VariableDeclarationKind.Const, declarations: [ { name: 'schemas', - initializer: 'Object.values(schemaDict)', + initializer: 'Object.values(schemaDict) satisfies LexiconDoc[]', }, ], }) @@ -91,6 +212,44 @@ export const lexiconsTs = (project, lexicons: LexiconDoc[]) => ], }) + file.addFunction({ + isExported: true, + name: 'validate', + overloads: [ + { + typeParameters: ['T extends { $type: string }'], + parameters: [ + { name: 'v', type: 'unknown' }, + { name: 'id', type: 'string' }, + { name: 'hash', type: 'string' }, + { name: 'requiredType', type: 'true' }, + ], + returnType: 'ValidationResult', + }, + { + typeParameters: ['T extends { $type?: string }'], + parameters: [ + { name: 'v', type: 'unknown' }, + { name: 'id', type: 'string' }, + { name: 'hash', type: 'string' }, + { name: 'requiredType', type: 'false', hasQuestionToken: true }, + ], + returnType: 'ValidationResult', + }, + ], + parameters: [ + { name: 'v', type: 'unknown' }, + { name: 'id', type: 'string' }, + { name: 'hash', type: 'string' }, + { name: 'requiredType', type: 'boolean', hasQuestionToken: true }, + ], + statements: [ + // If $type is present, make sure it is valid before validating the rest of the object + 'return (requiredType ? is$typed : maybe$typed)(v, id, hash) ? lexicons.validate(`${id}#${hash}`, v) : { success: false, error: new ValidationError(`Must be an object with "${hash === \'main\' ? id : `${id}#${hash}`}" $type property`) }', + ], + returnType: 'ValidationResult', + }) + //= export const ids = {...} file.addVariableStatement({ isExported: true, @@ -98,14 +257,11 @@ export const lexiconsTs = (project, lexicons: LexiconDoc[]) => declarations: [ { name: 'ids', - initializer: JSON.stringify( - lexicons.reduce((acc, cur) => { - return { - ...acc, - [nsidToEnum(cur.id)]: cur.id, - } - }, {}), - ), + initializer: `{${lexicons + .map( + (lex) => `\n ${nsidToEnum(lex.id)}: ${JSON.stringify(lex.id)},`, + ) + .join('')}\n} as const`, }, ], }) @@ -118,12 +274,11 @@ export async function gen( ): Promise { const file = project.createSourceFile(path) await gen(file) - file.saveSync() - const src = project.getFileSystem().readFileSync(path) - return { - path: path, - content: `${banner()}${await format(src, PRETTIER_OPTS)}`, - } + await file.save() // Save in the "in memory" file system + const src = `${banner()}${file.getFullText()}` + const content = await format(src, PRETTIER_OPTS) + + return { path, content } } function banner() { diff --git a/packages/lex-cli/src/codegen/lex-gen.ts b/packages/lex-cli/src/codegen/lex-gen.ts index ebc04fedbd7..ee181cd004b 100644 --- a/packages/lex-cli/src/codegen/lex-gen.ts +++ b/packages/lex-cli/src/codegen/lex-gen.ts @@ -27,6 +27,68 @@ export function genComment( return commentable as T } +export function genCommonImports(file: SourceFile, baseNsid: string) { + //= import {ValidationResult, BlobRef} from '@atproto/lexicon' + file + .addImportDeclaration({ + moduleSpecifier: '@atproto/lexicon', + }) + .addNamedImports([{ name: 'ValidationResult' }, { name: 'BlobRef' }]) + + //= import {CID} from 'multiformats/cid' + file + .addImportDeclaration({ + moduleSpecifier: 'multiformats/cid', + }) + .addNamedImports([{ name: 'CID' }]) + + //= import { validate as _validate } from '../../lexicons.ts' + file + .addImportDeclaration({ + moduleSpecifier: `${baseNsid + .split('.') + .map((_str) => '..') + .join('/')}/lexicons`, + }) + .addNamedImports([{ name: 'validate', alias: '_validate' }]) + + //= import { $Type, $Typed, is$typed as _is$typed, OmitKey } from '../../util.ts' + file + .addImportDeclaration({ + moduleSpecifier: `${baseNsid + .split('.') + .map((_str) => '..') + .join('/')}/util`, + }) + .addNamedImports([ + { name: '$Type' }, + { name: '$Typed' }, + { name: 'is$typed', alias: '_is$typed' }, + { name: 'OmitKey' }, + ]) + + // tsc adds protection against circular imports, which hurts bundle size. + // Since we know that lexicon.ts and util.ts do not depend on the file being + // generated, we can safely bypass this protection. + // Note that we are not using `import * as util from '../../util'` because + // typescript will emit is own helpers for the import, which we want to avoid. + file.addVariableStatement({ + isExported: false, + declarationKind: VariableDeclarationKind.Const, + declarations: [ + { name: 'is$typed', initializer: '_is$typed' }, + { name: 'validate', initializer: '_validate' }, + ], + }) + + //= const id = "{baseNsid}" + file.addVariableStatement({ + isExported: false, // Do not export to allow tree-shaking + declarationKind: VariableDeclarationKind.Const, + declarations: [{ name: 'id', initializer: JSON.stringify(baseNsid) }], + }) +} + export function genImports( file: SourceFile, imports: Set, @@ -41,6 +103,7 @@ export function genImports( resolvedPath = `./${resolvedPath}` } file.addImportDeclaration({ + isTypeOnly: true, moduleSpecifier: resolvedPath, namespaceImport: toTitleCase(nsid), }) @@ -61,10 +124,16 @@ export function genUserType( case 'token': genToken(file, lexUri, def) break - case 'object': - genObject(file, imports, lexUri, def) - genObjHelpers(file, lexUri) + case 'object': { + const ifaceName: string = toTitleCase(getHash(lexUri)) + genObject(file, imports, lexUri, def, ifaceName, { + typeProperty: true, + }) + genObjHelpers(file, lexUri, ifaceName, { + requireTypeProperty: false, + }) break + } case 'blob': case 'bytes': @@ -83,19 +152,39 @@ export function genUserType( } } -export function genObject( +function genObject( file: SourceFile, imports: Set, lexUri: string, def: LexObject, - ifaceName?: string, - defaultsArePresent = true, + ifaceName: string, + { + defaultsArePresent = true, + allowUnknownProperties = false, + typeProperty = false, + }: { + defaultsArePresent?: boolean + allowUnknownProperties?: boolean + typeProperty?: boolean | 'required' + } = {}, ) { const iface = file.addInterface({ - name: ifaceName || toTitleCase(getHash(lexUri)), + name: ifaceName, isExported: true, }) genComment(iface, def) + + if (typeProperty) { + const hash = getHash(lexUri) + const baseNsid = stripScheme(stripHash(lexUri)) + + //= $type?: + iface.addProperty({ + name: typeProperty === 'required' ? `$type` : `$type?`, + type: `$Type<${JSON.stringify(baseNsid)}, ${JSON.stringify(hash)}>`, + }) + } + const nullableProps = new Set(def.nullable) if (def.properties) { for (const propKey in def.properties) { @@ -108,12 +197,12 @@ export function genObject( propDef.default !== undefined) if (propDef.type === 'ref' || propDef.type === 'union') { //= propName: External|External - const refs = propDef.type === 'union' ? propDef.refs : [propDef.ref] - const types = refs.map((ref) => - refToType(ref, stripScheme(stripHash(lexUri)), imports), - ) + const types = + propDef.type === 'union' + ? propDef.refs.map((ref) => refToUnionType(ref, lexUri, imports)) + : [refToType(propDef.ref, stripScheme(stripHash(lexUri)), imports)] if (propDef.type === 'union' && !propDef.closed) { - types.push('{$type: string; [k: string]: unknown}') + types.push('{ $type: string }') } iface.addProperty({ name: `${propKey}${req ? '' : '?'}`, @@ -141,10 +230,10 @@ export function genObject( }) } else if (propDef.items.type === 'union') { const types = propDef.items.refs.map((ref) => - refToType(ref, stripScheme(stripHash(lexUri)), imports), + refToUnionType(ref, lexUri, imports), ) if (!propDef.items.closed) { - types.push('{$type: string; [k: string]: unknown}') + types.push('{ $type: string }') } propAst = iface.addProperty({ name: `${propKey}${req ? '' : '?'}`, @@ -177,16 +266,21 @@ export function genObject( } } } - //= [k: string]: unknown - iface.addIndexSignature({ - keyName: 'k', - keyType: 'string', - returnType: 'unknown', - }) + + if (allowUnknownProperties) { + //= [k: string]: unknown + iface.addIndexSignature({ + keyName: 'k', + keyType: 'string', + returnType: 'unknown', + }) + } } } export function genToken(file: SourceFile, lexUri: string, def: LexToken) { + //= /** */ + //= export const = `${id}#` genComment( file.addVariableStatement({ isExported: true, @@ -194,7 +288,7 @@ export function genToken(file: SourceFile, lexUri: string, def: LexToken) { declarations: [ { name: toScreamingSnakeCase(getHash(lexUri)), - initializer: `"${stripScheme(lexUri)}"`, + initializer: `\`\${id}#${getHash(lexUri)}\``, }, ], }), @@ -220,10 +314,10 @@ export function genArray( }) } else if (def.items.type === 'union') { const types = def.items.refs.map((ref) => - refToType(ref, stripScheme(stripHash(lexUri)), imports), + refToUnionType(ref, lexUri, imports), ) if (!def.items.closed) { - types.push('{$type: string; [k: string]: unknown}') + types.push('{ $type: string }') } file.addTypeAlias({ name: toTitleCase(getHash(lexUri)), @@ -308,15 +402,22 @@ export function genXrpcInput( if (def.type === 'procedure' && def.input?.schema) { if (def.input.schema.type === 'ref' || def.input.schema.type === 'union') { //= export type InputSchema = ... - const refs = + + const types = def.input.schema.type === 'union' - ? def.input.schema.refs - : [def.input.schema.ref] - const types = refs.map((ref) => - refToType(ref, stripScheme(stripHash(lexUri)), imports), - ) + ? def.input.schema.refs.map((ref) => + refToUnionType(ref, lexUri, imports), + ) + : [ + refToType( + def.input.schema.ref, + stripScheme(stripHash(lexUri)), + imports, + ), + ] + if (def.input.schema.type === 'union' && !def.input.schema.closed) { - types.push('{$type: string; [k: string]: unknown}') + types.push('{ $type: string }') } file.addTypeAlias({ name: 'InputSchema', @@ -325,14 +426,9 @@ export function genXrpcInput( }) } else { //= export interface InputSchema {...} - genObject( - file, - imports, - lexUri, - def.input.schema, - `InputSchema`, + genObject(file, imports, lexUri, def.input.schema, `InputSchema`, { defaultsArePresent, - ) + }) } } else if (def.type === 'procedure' && def.input?.encoding) { //= export type InputSchema = string | Uint8Array | Blob @@ -369,12 +465,12 @@ export function genXrpcOutput( if (schema) { if (schema.type === 'ref' || schema.type === 'union') { //= export type OutputSchema = ... - const refs = schema.type === 'union' ? schema.refs : [schema.ref] - const types = refs.map((ref) => - refToType(ref, stripScheme(stripHash(lexUri)), imports), - ) + const types = + schema.type === 'union' + ? schema.refs.map((ref) => refToUnionType(ref, lexUri, imports)) + : [refToType(schema.ref, stripScheme(stripHash(lexUri)), imports)] if (schema.type === 'union' && !schema.closed) { - types.push('{$type: string; [k: string]: unknown}') + types.push('{ $type: string }') } file.addTypeAlias({ name: 'OutputSchema', @@ -383,50 +479,79 @@ export function genXrpcOutput( }) } else { //= export interface OutputSchema {...} - genObject( - file, - imports, - lexUri, - schema, - `OutputSchema`, + genObject(file, imports, lexUri, schema, `OutputSchema`, { defaultsArePresent, - ) + }) } } } -export function genObjHelpers( +export function genRecord( + file: SourceFile, + imports: Set, + lexicons: Lexicons, + lexUri: string, +) { + const def = lexicons.getDefOrThrow(lexUri, ['record']) + + //= export interface Record {...} + genObject(file, imports, lexUri, def.record, 'Record', { + defaultsArePresent: true, + allowUnknownProperties: true, + typeProperty: 'required', + }) + + //= export function isRecord(v: unknown): v is Record {...} + genObjHelpers(file, lexUri, 'Record', { + requireTypeProperty: true, + }) +} + +function genObjHelpers( file: SourceFile, lexUri: string, - ifaceName?: string, + ifaceName: string, + { + requireTypeProperty, + }: { + requireTypeProperty: boolean + }, ) { const hash = getHash(lexUri) - //= export function is{X}(v: unknown): v is X {...} + const hashVar = `hash${ifaceName}` + + file.addVariableStatement({ + isExported: false, + declarationKind: VariableDeclarationKind.Const, + declarations: [{ name: hashVar, initializer: JSON.stringify(hash) }], + }) + + const isX = toCamelCase(`is-${ifaceName}`) + + //= export function is{X}(v: V) {...} file .addFunction({ - name: toCamelCase(`is-${ifaceName || hash}`), - parameters: [{ name: `v`, type: `unknown` }], - returnType: `v is ${ifaceName || toTitleCase(hash)}`, + name: isX, + typeParameters: [{ name: `V` }], + parameters: [{ name: `v`, type: `V` }], isExported: true, }) - .setBodyText( - hash === 'main' - ? `return isObj(v) && hasProp(v, '$type') && (v.$type === "${lexUri}" || v.$type === "${stripHash( - lexUri, - )}")` - : `return isObj(v) && hasProp(v, '$type') && v.$type === "${lexUri}"`, - ) + .setBodyText(`return is$typed(v, id, ${hashVar})`) + + const validateX = toCamelCase(`validate-${ifaceName}`) - //= export function validate{X}(v: unknown): ValidationResult {...} + //= export function validate{X}(v: unknown) {...} file .addFunction({ - name: toCamelCase(`validate-${ifaceName || hash}`), - parameters: [{ name: `v`, type: `unknown` }], - returnType: `ValidationResult`, + name: validateX, + typeParameters: [{ name: `V` }], + parameters: [{ name: `v`, type: `V` }], isExported: true, }) - .setBodyText(`return lexicons.validate("${lexUri}", v)`) + .setBodyText( + `return validate<${ifaceName} & V>(v, id, ${hashVar}${requireTypeProperty ? ', true' : ''})`, + ) } export function stripScheme(uri: string): string { @@ -449,7 +574,16 @@ export function ipldToType(def: LexCidLink | LexBytes) { return 'CID' } -export function refToType( +function refToUnionType( + ref: string, + lexUri: string, + imports: Set, +): string { + const baseNsid = stripScheme(stripHash(lexUri)) + return `$Typed<${refToType(ref, baseNsid, imports)}>` +} + +function refToType( ref: string, baseNsid: string, imports: Set, @@ -510,7 +644,10 @@ export function primitiveToType(def: LexPrimitive): string { } return 'boolean' case 'unknown': - return '{}' + // @TODO Should we use "object" here ? + // the "Record" identifier from typescript get overwritten by the Record + // interface created by lex-cli. + return '{ [_ in string]: unknown }' // Record default: throw new Error(`Unexpected primitive type: ${JSON.stringify(def)}`) } diff --git a/packages/lex-cli/src/codegen/server.ts b/packages/lex-cli/src/codegen/server.ts index 110e8a0b13e..b4ed1141b8b 100644 --- a/packages/lex-cli/src/codegen/server.ts +++ b/packages/lex-cli/src/codegen/server.ts @@ -9,9 +9,9 @@ import { NSID } from '@atproto/syntax' import { GeneratedAPI } from '../types' import { gen, lexiconsTs, utilTs } from './common' import { + genCommonImports, genImports, - genObjHelpers, - genObject, + genRecord, genUserType, genXrpcInput, genXrpcOutput, @@ -319,8 +319,6 @@ const lexiconTs = (project, lexicons: Lexicons, lexiconDoc: LexiconDoc) => project, `/types/${lexiconDoc.id.split('.').join('/')}.ts`, async (file) => { - const imports: Set = new Set() - const main = lexiconDoc.defs.main if (main?.type === 'query' || main?.type === 'procedure') { //= import express from 'express' @@ -342,37 +340,10 @@ const lexiconTs = (project, lexicons: Lexicons, lexiconDoc: LexiconDoc) => }) } } - //= import {ValidationResult, BlobRef} from '@atproto/lexicon' - file - .addImportDeclaration({ - moduleSpecifier: '@atproto/lexicon', - }) - .addNamedImports([{ name: 'ValidationResult' }, { name: 'BlobRef' }]) - //= import {lexicons} from '../../lexicons.ts' - file - .addImportDeclaration({ - moduleSpecifier: `${lexiconDoc.id - .split('.') - .map((_str) => '..') - .join('/')}/lexicons`, - }) - .addNamedImports([{ name: 'lexicons' }]) - //= import {isObj, hasProp} from '../../util.ts' - file - .addImportDeclaration({ - moduleSpecifier: `${lexiconDoc.id - .split('.') - .map((_str) => '..') - .join('/')}/util`, - }) - .addNamedImports([{ name: 'isObj' }, { name: 'hasProp' }]) - //= import {CID} from 'multiformats/cid' - file - .addImportDeclaration({ - moduleSpecifier: 'multiformats/cid', - }) - .addNamedImports([{ name: 'CID' }]) + genCommonImports(file, lexiconDoc.id) + + const imports: Set = new Set() for (const defId in lexiconDoc.defs) { const def = lexiconDoc.defs[defId] const lexUri = `${lexiconDoc.id}#${defId}` @@ -387,7 +358,7 @@ const lexiconTs = (project, lexicons: Lexicons, lexiconDoc: LexiconDoc) => genXrpcOutput(file, imports, lexicons, lexUri, false) genServerXrpcStreaming(file, lexicons, lexUri) } else if (def.type === 'record') { - genServerRecord(file, imports, lexicons, lexUri) + genRecord(file, imports, lexicons, lexUri) } else { genUserType(file, imports, lexicons, lexUri) } @@ -589,20 +560,6 @@ function genServerXrpcStreaming( }) } -function genServerRecord( - file: SourceFile, - imports: Set, - lexicons: Lexicons, - lexUri: string, -) { - const def = lexicons.getDefOrThrow(lexUri, ['record']) - - //= export interface Record {...} - genObject(file, imports, lexUri, def.record, 'Record') - //= export function isRecord(v: unknown): v is Record {...} - genObjHelpers(file, lexUri, 'Record') -} - function arrayToUnion(arr?: string[]) { if (!arr?.length) { return 'never' diff --git a/packages/lexicon/src/lexicons.ts b/packages/lexicon/src/lexicons.ts index cdd12b88fcd..37f9c42488c 100644 --- a/packages/lexicon/src/lexicons.ts +++ b/packages/lexicon/src/lexicons.ts @@ -6,7 +6,6 @@ import { LexiconDoc, ValidationError, ValidationResult, - hasProp, isObj, } from './types' import { toLexUri } from './util' @@ -17,7 +16,7 @@ import { assertValidXrpcOutput, assertValidXrpcParams, } from './validation' -import * as ComplexValidators from './validators/complex' +import { object as validateObject } from './validators/complex' /** * A collection of compiled lexicons. @@ -127,15 +126,17 @@ export class Lexicons implements Iterable { * Validate a record or object. */ validate(lexUri: string, value: unknown): ValidationResult { - lexUri = toLexUri(lexUri) - const def = this.getDefOrThrow(lexUri, ['record', 'object']) if (!isObj(value)) { throw new ValidationError(`Value must be an object`) } + + const lexUriNormalized = toLexUri(lexUri) + const def = this.getDefOrThrow(lexUriNormalized, ['record', 'object']) + if (def.type === 'record') { - return ComplexValidators.object(this, 'Record', def.record, value) + return validateObject(this, 'Record', def.record, value) } else if (def.type === 'object') { - return ComplexValidators.object(this, 'Object', def, value) + return validateObject(this, 'Object', def, value) } else { // shouldn't happen throw new InvalidLexiconError('Definition must be a record or object') @@ -146,20 +147,25 @@ export class Lexicons implements Iterable { * Validate a record and throw on any error. */ assertValidRecord(lexUri: string, value: unknown) { - lexUri = toLexUri(lexUri) - const def = this.getDefOrThrow(lexUri, ['record']) if (!isObj(value)) { throw new ValidationError(`Record must be an object`) } - if (!hasProp(value, '$type') || typeof value.$type !== 'string') { + if (!('$type' in value)) { throw new ValidationError(`Record/$type must be a string`) } - const $type = (value as Record).$type || '' - if (toLexUri($type) !== lexUri) { + const { $type } = value + if (typeof $type !== 'string') { + throw new ValidationError(`Record/$type must be a string`) + } + + const lexUriNormalized = toLexUri(lexUri) + if (toLexUri($type) !== lexUriNormalized) { throw new ValidationError( - `Invalid $type: must be ${lexUri}, got ${$type}`, + `Invalid $type: must be ${lexUriNormalized}, got ${$type}`, ) } + + const def = this.getDefOrThrow(lexUriNormalized, ['record']) return assertValidRecord(this, def as LexRecord, value) } diff --git a/packages/lexicon/src/types.ts b/packages/lexicon/src/types.ts index 6b9b5f9e5c8..a686826a9ac 100644 --- a/packages/lexicon/src/types.ts +++ b/packages/lexicon/src/types.ts @@ -447,23 +447,13 @@ export function isValidLexiconDoc(v: unknown): v is LexiconDoc { return lexiconDoc.safeParse(v).success } -export function isObj(obj: unknown): obj is Record { - return obj !== null && typeof obj === 'object' +export function isObj(v: V): v is V & object { + return v != null && typeof v === 'object' } -export function hasProp( - data: object, - prop: K, -): data is Record { - return prop in data -} - -export const discriminatedObject = z.object({ $type: z.string() }) -export type DiscriminatedObject = z.infer -export function isDiscriminatedObject( - value: unknown, -): value is DiscriminatedObject { - return discriminatedObject.safeParse(value).success +export type DiscriminatedObject = { $type: string } +export function isDiscriminatedObject(v: unknown): v is DiscriminatedObject { + return isObj(v) && '$type' in v && typeof v.$type === 'string' } export function parseLexiconDoc(v: unknown): LexiconDoc { @@ -471,10 +461,10 @@ export function parseLexiconDoc(v: unknown): LexiconDoc { return v as LexiconDoc } -export type ValidationResult = +export type ValidationResult = | { success: true - value: unknown + value: V } | { success: false diff --git a/packages/lexicon/src/util.ts b/packages/lexicon/src/util.ts index beae2922d59..5a1c4d49c4b 100644 --- a/packages/lexicon/src/util.ts +++ b/packages/lexicon/src/util.ts @@ -1,6 +1,4 @@ import { z } from 'zod' -import { Lexicons } from './lexicons' -import { LexRefVariant, LexUserType } from './types' export function toLexUri(str: string, baseUri?: string): string { if (str.split('#').length > 2) { @@ -19,19 +17,6 @@ export function toLexUri(str: string, baseUri?: string): string { return `lex:${str}` } -export function toConcreteTypes( - lexicons: Lexicons, - def: LexRefVariant | LexUserType, -): LexUserType[] { - if (def.type === 'ref') { - return [lexicons.getDefOrThrow(def.ref)] - } else if (def.type === 'union') { - return def.refs.map((ref) => lexicons.getDefOrThrow(ref)).flat() - } else { - return [def] - } -} - export function requiredPropertiesRefinement< ObjectType extends { required?: string[] diff --git a/packages/lexicon/src/validators/complex.ts b/packages/lexicon/src/validators/complex.ts index 6f066071883..f59cff66cad 100644 --- a/packages/lexicon/src/validators/complex.ts +++ b/packages/lexicon/src/validators/complex.ts @@ -1,16 +1,16 @@ import { Lexicons } from '../lexicons' import { LexArray, - LexObject, LexRefVariant, LexUserType, ValidationError, ValidationResult, isDiscriminatedObject, + isObj, } from '../types' -import { toConcreteTypes, toLexUri } from '../util' +import { toLexUri } from '../util' import { blob } from './blob' -import { boolean, bytes, cidLink, integer, string, unknown } from './primitives' +import { validate as validatePrimitive } from './primitives' export function validate( lexicons: Lexicons, @@ -19,18 +19,6 @@ export function validate( value: unknown, ): ValidationResult { switch (def.type) { - case 'boolean': - return boolean(lexicons, path, def, value) - case 'integer': - return integer(lexicons, path, def, value) - case 'string': - return string(lexicons, path, def, value) - case 'bytes': - return bytes(lexicons, path, def, value) - case 'cid-link': - return cidLink(lexicons, path, def, value) - case 'unknown': - return unknown(lexicons, path, def, value) case 'object': return object(lexicons, path, def, value) case 'array': @@ -38,10 +26,7 @@ export function validate( case 'blob': return blob(lexicons, path, def, value) default: - return { - success: false, - error: new ValidationError(`Unexpected lexicon type: ${def.type}`), - } + return validatePrimitive(lexicons, path, def, value) } } @@ -103,35 +88,31 @@ export function object( def: LexUserType, value: unknown, ): ValidationResult { - def = def as LexObject - // type - if (!value || typeof value !== 'object') { + if (!isObj(value)) { return { success: false, error: new ValidationError(`${path} must be an object`), } } - const requiredProps = new Set(def.required) - const nullableProps = new Set(def.nullable) - // properties let resultValue = value - if (typeof def.properties === 'object') { + if ('properties' in def && def.properties != null) { for (const key in def.properties) { - if (value[key] === null && nullableProps.has(key)) { + const keyValue = value[key] + if (keyValue === null && def.nullable?.includes(key)) { continue } const propDef = def.properties[key] - if (typeof value[key] === 'undefined' && !requiredProps.has(key)) { + if (keyValue === undefined && !def.required?.includes(key)) { // Fast path for non-required undefined props. if ( propDef.type === 'integer' || propDef.type === 'boolean' || propDef.type === 'string' ) { - if (typeof propDef.default === 'undefined') { + if (propDef.default === undefined) { continue } } else { @@ -140,20 +121,27 @@ export function object( } } const propPath = `${path}/${key}` - const validated = validateOneOf(lexicons, propPath, propDef, value[key]) - const propValue = validated.success ? validated.value : value[key] - const propIsUndefined = typeof propValue === 'undefined' + const validated = validateOneOf(lexicons, propPath, propDef, keyValue) + const propValue = validated.success ? validated.value : keyValue + // Return error for bad validation, giving required rule precedence - if (propIsUndefined && requiredProps.has(key)) { - return { - success: false, - error: new ValidationError(`${path} must have the property "${key}"`), + if (propValue === undefined) { + if (def.required?.includes(key)) { + return { + success: false, + error: new ValidationError( + `${path} must have the property "${key}"`, + ), + } + } + } else { + if (!validated.success) { + return validated } - } else if (!propIsUndefined && !validated.success) { - return validated } + // Adjust value based on e.g. applied defaults, cloning shallowly if there was a changed value - if (propValue !== value[key]) { + if (propValue !== keyValue) { if (resultValue === value) { // Lazy shallow clone resultValue = { ...value } @@ -173,9 +161,8 @@ export function validateOneOf( value: unknown, mustBeObj = false, // this is the only type constraint we need currently (used by xrpc body schema validators) ): ValidationResult { - let error + let concreteDef: LexUserType - let concreteDefs if (def.type === 'union') { if (!isDiscriminatedObject(value)) { return { @@ -196,33 +183,17 @@ export function validateOneOf( } return { success: true, value } } else { - concreteDefs = toConcreteTypes(lexicons, { - type: 'ref', - ref: value.$type, - }) + concreteDef = lexicons.getDefOrThrow(value.$type) } + } else if (def.type === 'ref') { + concreteDef = lexicons.getDefOrThrow(def.ref) } else { - concreteDefs = toConcreteTypes(lexicons, def) + concreteDef = def } - for (const concreteDef of concreteDefs) { - const result = mustBeObj - ? object(lexicons, path, concreteDef, value) - : validate(lexicons, path, concreteDef, value) - if (result.success) { - return result - } - error ??= result.error - } - if (concreteDefs.length > 1) { - return { - success: false, - error: new ValidationError( - `${path} did not match any of the expected definitions`, - ), - } - } - return { success: false, error } + return mustBeObj + ? object(lexicons, path, concreteDef, value) + : validate(lexicons, path, concreteDef, value) } // to avoid bugs like #0189 this needs to handle both @@ -234,7 +205,7 @@ const refsContainType = (refs: string[], type: string) => { } if (lexUri.endsWith('#main')) { - return refs.includes(lexUri.replace('#main', '')) + return refs.includes(lexUri.slice(0, -5)) } else { return refs.includes(lexUri + '#main') } diff --git a/packages/lexicon/src/validators/formats.ts b/packages/lexicon/src/validators/formats.ts index 36a037ebfff..e9700f14265 100644 --- a/packages/lexicon/src/validators/formats.ts +++ b/packages/lexicon/src/validators/formats.ts @@ -7,7 +7,7 @@ import { ensureValidHandle, ensureValidNsid, ensureValidRecordKey, - ensureValidTid, + isValidTid, } from '@atproto/syntax' import { ValidationError, ValidationResult } from '../types' @@ -131,17 +131,14 @@ export function language(path: string, value: string): ValidationResult { } export function tid(path: string, value: string): ValidationResult { - try { - ensureValidTid(value) - } catch { - return { - success: false, - error: new ValidationError( - `${path} must be a valid TID (timestamp identifier)`, - ), - } + if (isValidTid(value)) { + return { success: true, value } + } + + return { + success: false, + error: new ValidationError(`${path} must be a valid TID`), } - return { success: true, value } } export function recordKey(path: string, value: string): ValidationResult { diff --git a/packages/lexicon/src/validators/primitives.ts b/packages/lexicon/src/validators/primitives.ts index 919173fe697..7837cb0246d 100644 --- a/packages/lexicon/src/validators/primitives.ts +++ b/packages/lexicon/src/validators/primitives.ts @@ -39,7 +39,7 @@ export function validate( } } -export function boolean( +function boolean( lexicons: Lexicons, path: string, def: LexUserType, @@ -77,7 +77,7 @@ export function boolean( return { success: true, value } } -export function integer( +function integer( lexicons: Lexicons, path: string, def: LexUserType, @@ -151,7 +151,7 @@ export function integer( return { success: true, value } } -export function string( +function string( lexicons: Lexicons, path: string, def: LexUserType, @@ -340,7 +340,7 @@ export function string( return { success: true, value } } -export function bytes( +function bytes( lexicons: Lexicons, path: string, def: LexUserType, @@ -382,7 +382,7 @@ export function bytes( return { success: true, value } } -export function cidLink( +function cidLink( lexicons: Lexicons, path: string, def: LexUserType, @@ -398,7 +398,7 @@ export function cidLink( return { success: true, value } } -export function unknown( +function unknown( lexicons: Lexicons, path: string, def: LexUserType, diff --git a/packages/ozone/src/api/moderation/emitEvent.ts b/packages/ozone/src/api/moderation/emitEvent.ts index 85a5fe3d3e1..d978cde3ac1 100644 --- a/packages/ozone/src/api/moderation/emitEvent.ts +++ b/packages/ozone/src/api/moderation/emitEvent.ts @@ -5,7 +5,6 @@ import { Server } from '../../lexicon' import { ModEventTag, isModEventAcknowledge, - isModEventDivert, isModEventEmail, isModEventLabel, isModEventMuteReporter, @@ -14,8 +13,10 @@ import { isModEventTag, isModEventTakedown, isModEventUnmuteReporter, + validateModEventDivert, } from '../../lexicon/types/tools/ozone/moderation/defs' import { HandlerInput } from '../../lexicon/types/tools/ozone/moderation/emitEvent' +import { asPredicate } from '../../lexicon/util' import { subjectFromInput } from '../../mod-service/subject' import { ProtectedTagSettingKey } from '../../setting/constants' import { SettingService } from '../../setting/service' @@ -24,6 +25,8 @@ import { TagService } from '../../tag-service' import { getTagForReport } from '../../tag-service/util' import { retryHttp } from '../../util' +const isValidModEventDivert = asPredicate(validateModEventDivert) + const handleModerationEvent = async ({ ctx, input, @@ -127,7 +130,7 @@ const handleModerationEvent = async ({ ) } - if (isModEventDivert(event) && subject.isRecord()) { + if (isValidModEventDivert(event) && subject.isRecord()) { if (!ctx.blobDiverter) { throw new InvalidRequestError( 'BlobDiverter not configured for this service', @@ -228,7 +231,7 @@ export default function (server: Server, ctx: AppContext) { }) // On divert events, we need to automatically take down the blobs - if (isModEventDivert(input.body.event)) { + if (isValidModEventDivert(input.body.event)) { await handleModerationEvent({ auth, ctx, diff --git a/packages/ozone/src/api/moderation/getRepos.ts b/packages/ozone/src/api/moderation/getRepos.ts index eb1fe9487ec..affe89291b3 100644 --- a/packages/ozone/src/api/moderation/getRepos.ts +++ b/packages/ozone/src/api/moderation/getRepos.ts @@ -23,12 +23,12 @@ export default function (server: Server, ctx: AppContext) { } } return { - $type: 'tools.ozone.moderation.defs#repoViewDetail', ...addAccountInfoToRepoViewDetail( partialRepo, accountInfo.get(did) || null, auth.credentials.isModerator, ), + $type: 'tools.ozone.moderation.defs#repoViewDetail', } }) diff --git a/packages/ozone/src/api/util.ts b/packages/ozone/src/api/util.ts index 089ed3838eb..c9cd5a4a96b 100644 --- a/packages/ozone/src/api/util.ts +++ b/packages/ozone/src/api/util.ts @@ -13,6 +13,7 @@ import { REASONSEXUAL, REASONSPAM, REASONVIOLATION, + ReasonType, } from '../lexicon/types/com/atproto/moderation/defs' import { REVIEWCLOSED, @@ -51,12 +52,26 @@ export const getPdsAccountInfos = async ( } } +function un$type(obj: T): Omit { + if ('$type' in obj) { + const { $type: _, ...rest } = obj + return rest + } + return obj +} + export const addAccountInfoToRepoViewDetail = ( - repoView: RepoViewDetail, + repoView: RepoView | RepoViewDetail, accountInfo: AccountView | null, includeEmail = false, ): RepoViewDetail => { - if (!accountInfo) return repoView + if (!accountInfo) { + return un$type({ + ...repoView, + moderation: un$type(repoView.moderation), + }) + } + const { email, deactivatedAt, @@ -67,6 +82,7 @@ export const addAccountInfoToRepoViewDetail = ( invitesDisabled, threatSignatures, // pick some duplicate/unwanted details out + $type: _accountType, did: _did, handle: _handle, indexedAt: _indexedAt, @@ -75,7 +91,8 @@ export const addAccountInfoToRepoViewDetail = ( } = accountInfo return { ...otherAccountInfo, - ...repoView, + ...un$type(repoView), + moderation: un$type(repoView.moderation), email: includeEmail ? email : undefined, invitedBy, invitesDisabled, @@ -106,7 +123,7 @@ export const addAccountInfoToRepoView = ( export const getReasonType = (reasonType: ReportInput['reasonType']) => { if (reasonTypes.has(reasonType)) { - return reasonType as NonNullable['reportType'] + return reasonType } throw new InvalidRequestError('Invalid reason type') } @@ -128,7 +145,7 @@ export const getReviewState = (reviewState?: string) => { const reviewStates = new Set([REVIEWCLOSED, REVIEWESCALATED, REVIEWOPEN]) -const reasonTypes = new Set([ +const reasonTypes = new Set([ REASONOTHER, REASONSPAM, REASONMISLEADING, diff --git a/packages/ozone/src/mod-service/index.ts b/packages/ozone/src/mod-service/index.ts index 59e9588223d..03a43f0b9b6 100644 --- a/packages/ozone/src/mod-service/index.ts +++ b/packages/ozone/src/mod-service/index.ts @@ -21,6 +21,7 @@ import { ImageInvalidator } from '../image-invalidator' import { ids } from '../lexicon/lexicons' import { RepoBlobRef, RepoRef } from '../lexicon/types/com/atproto/admin/defs' import { Label } from '../lexicon/types/com/atproto/label/defs' +import { ReasonType } from '../lexicon/types/com/atproto/moderation/defs' import { Main as StrongRef } from '../lexicon/types/com/atproto/repo/strongRef' import { REVIEWESCALATED, @@ -471,7 +472,11 @@ export class ModerationService { const modEvent = await this.db.db .insertInto('moderation_event') .values({ - comment: event.comment ? `${event.comment}` : null, + comment: + ('comment' in event && + typeof event.comment === 'string' && + event.comment) || + null, action: event.$type as ModerationEvent['action'], createdAt: createdAt.toISOString(), createdBy, @@ -479,9 +484,10 @@ export class ModerationService { negateLabelVals, addedTags, removedTags, - durationInHours: event.durationInHours - ? Number(event.durationInHours) - : null, + durationInHours: + 'durationInHours' in event && event.durationInHours + ? Number(event.durationInHours) + : null, meta: Object.assign(meta, subjectInfo.meta), expiresAt: (isModEventTakedown(event) || isModEventMute(event)) && @@ -783,7 +789,7 @@ export class ModerationService { } async report(info: { - reasonType: NonNullable['reportType'] + reasonType: ReasonType reason?: string subject: ModSubject reportedBy: string diff --git a/packages/ozone/src/mod-service/subject.ts b/packages/ozone/src/mod-service/subject.ts index 5a46a567e9c..4a8a29ca52c 100644 --- a/packages/ozone/src/mod-service/subject.ts +++ b/packages/ozone/src/mod-service/subject.ts @@ -1,44 +1,52 @@ import { AtUri } from '@atproto/syntax' import { InvalidRequestError } from '@atproto/xrpc-server' +import * as ChatBskyConvoDefs from '../lexicon/types/chat/bsky/convo/defs' import { MessageRef } from '../lexicon/types/chat/bsky/convo/defs' -import { RepoRef } from '../lexicon/types/com/atproto/admin/defs' +import { RepoRef, isRepoRef } from '../lexicon/types/com/atproto/admin/defs' import { InputSchema as ReportInput } from '../lexicon/types/com/atproto/moderation/createReport' +import * as ComAtprotoRepoStrongRef from '../lexicon/types/com/atproto/repo/strongRef' import { Main as StrongRef } from '../lexicon/types/com/atproto/repo/strongRef' import { InputSchema as ActionInput } from '../lexicon/types/tools/ozone/moderation/emitEvent' +import { $Typed, asPredicate } from '../lexicon/util' import { ModerationEventRow, ModerationSubjectStatusRow } from './types' type SubjectInput = ReportInput['subject'] | ActionInput['subject'] +type StrongRef = ComAtprotoRepoStrongRef.Main +const isStrongRef = asPredicate(ComAtprotoRepoStrongRef.validateMain) + +type MessageRef = ChatBskyConvoDefs.MessageRef +const isValidMessageRef = asPredicate(ChatBskyConvoDefs.validateMessageRef) + +const isMessageRefWithoutConvoId = ( + subject: unknown, +): subject is $Typed & { convoId?: string }> => + subject != null && + typeof subject === 'object' && + isValidMessageRef({ convoId: '', ...subject }) + export const subjectFromInput = ( subject: SubjectInput, blobs?: string[], ): ModSubject => { - if ( - subject.$type === 'com.atproto.admin.defs#repoRef' && - typeof subject.did === 'string' - ) { + if (isRepoRef(subject)) { if (blobs && blobs.length > 0) { throw new InvalidRequestError('Blobs do not apply to repo subjects') } return new RepoSubject(subject.did) } - if ( - subject.$type === 'com.atproto.repo.strongRef' && - typeof subject.uri === 'string' && - typeof subject.cid === 'string' - ) { + if (isStrongRef(subject)) { return new RecordSubject(subject.uri, subject.cid, blobs) } // @NOTE #messageRef is not a report input for com.atproto.moderation.createReport. // we are taking advantage of the open union in order for bsky.chat to interoperate here. - if ( - subject.$type === 'chat.bsky.convo.defs#messageRef' && - typeof subject.did === 'string' && - (typeof subject.convoId === 'string' || subject.convoId === undefined) && - typeof subject.messageId === 'string' - ) { - // @TODO we should start to require subject.convoId is a string in order to properly validate - // the #messageRef. temporarily allowing it to be optional as a stopgap for rollout. + if (isValidMessageRef(subject)) { + return new MessageSubject(subject.did, subject.convoId, subject.messageId) + } + // @TODO we should start to require subject.convoId is a string in order to properly validate + // the #messageRef. temporarily allowing it to be optional as a stopgap for rollout. + // The next "if" can be removed once convoId is consistently provided. + if (isMessageRefWithoutConvoId(subject)) { return new MessageSubject( subject.did, subject.convoId ?? '', @@ -106,7 +114,7 @@ export interface ModSubject { isRecord(): this is RecordSubject isMessage(): this is MessageSubject info(): SubjectInfo - lex(): RepoRef | StrongRef | MessageRef + lex(): $Typed | $Typed | $Typed } export class RepoSubject implements ModSubject { @@ -133,7 +141,7 @@ export class RepoSubject implements ModSubject { meta: null, } } - lex(): RepoRef { + lex(): $Typed { return { $type: 'com.atproto.admin.defs#repoRef', did: this.did, @@ -174,7 +182,7 @@ export class RecordSubject implements ModSubject { meta: null, } } - lex(): StrongRef { + lex(): $Typed { return { $type: 'com.atproto.repo.strongRef', uri: this.uri, @@ -211,7 +219,7 @@ export class MessageSubject implements ModSubject { meta: { convoId: this.convoId || undefined }, } } - lex(): MessageRef { + lex(): $Typed { return { $type: 'chat.bsky.convo.defs#messageRef', did: this.did, diff --git a/packages/ozone/src/mod-service/types.ts b/packages/ozone/src/mod-service/types.ts index 6e620307f45..4941768c40f 100644 --- a/packages/ozone/src/mod-service/types.ts +++ b/packages/ozone/src/mod-service/types.ts @@ -1,7 +1,7 @@ import { type Selectable } from 'kysely' -import { ToolsOzoneModerationDefs } from '@atproto/api' import { ModerationEvent } from '../db/schema/moderation_event' import { ModerationSubjectStatus } from '../db/schema/moderation_subject_status' +import { ModEventView } from '../lexicon/types/tools/ozone/moderation/defs' import { ModSubject } from './subject' export type ModerationEventRow = Selectable @@ -42,20 +42,7 @@ export type ModerationSubjectStatusRowWithStats = ModerationSubjectStatusRow & { export type ModerationSubjectStatusRowWithHandle = ModerationSubjectStatusRowWithStats & { handle: string | null } -export type ModEventType = - | ToolsOzoneModerationDefs.ModEventTakedown - | ToolsOzoneModerationDefs.ModEventAcknowledge - | ToolsOzoneModerationDefs.ModEventEscalate - | ToolsOzoneModerationDefs.ModEventComment - | ToolsOzoneModerationDefs.ModEventLabel - | ToolsOzoneModerationDefs.ModEventReport - | ToolsOzoneModerationDefs.ModEventMute - | ToolsOzoneModerationDefs.ModEventReverseTakedown - | ToolsOzoneModerationDefs.ModEventTag - | ToolsOzoneModerationDefs.AccountEvent - | ToolsOzoneModerationDefs.IdentityEvent - | ToolsOzoneModerationDefs.RecordEvent - | ToolsOzoneModerationDefs.ModEventPriorityScore +export type ModEventType = ModEventView['event'] type AccountHostingView = { $type: 'tools.ozone.moderation.defs#accountHosting' diff --git a/packages/ozone/src/mod-service/util.ts b/packages/ozone/src/mod-service/util.ts index c07ce48de8c..4c2e61969f7 100644 --- a/packages/ozone/src/mod-service/util.ts +++ b/packages/ozone/src/mod-service/util.ts @@ -16,7 +16,7 @@ export const formatLabel = (row: LabelRow): Label => { cts: row.cts, exp: row.exp ?? undefined, sig: row.sig ? new Uint8Array(row.sig) : undefined, - }) as Label + } satisfies Label) as unknown as Label } export const formatLabelRow = ( @@ -50,7 +50,7 @@ export const signLabel = async ( neg: neg === true ? true : undefined, cts, exp, - }) as Label + } satisfies Label) as unknown as Label const bytes = cborEncode(reformatted) const sig = await signingKey.sign(bytes) diff --git a/packages/ozone/src/mod-service/views.ts b/packages/ozone/src/mod-service/views.ts index 7cf7e4775e2..10f81049467 100644 --- a/packages/ozone/src/mod-service/views.ts +++ b/packages/ozone/src/mod-service/views.ts @@ -1,5 +1,5 @@ import { sql } from 'kysely' -import { AppBskyFeedDefs, AtpAgent } from '@atproto/api' +import { AtpAgent } from '@atproto/api' import { dedupeStrs } from '@atproto/common' import { Keypair } from '@atproto/crypto' import { BlobRef } from '@atproto/lexicon' @@ -7,8 +7,12 @@ import { AtUri, INVALID_HANDLE, normalizeDatetimeAlways } from '@atproto/syntax' import { Database } from '../db' import { LabelRow } from '../db/schema/label' import { ids } from '../lexicon/lexicons' +import { FeedViewPost } from '../lexicon/types/app/bsky/feed/defs' import { AccountView } from '../lexicon/types/com/atproto/admin/defs' -import { Label, isSelfLabels } from '../lexicon/types/com/atproto/label/defs' +import { + Label, + validateSelfLabels, +} from '../lexicon/types/com/atproto/label/defs' import { OutputSchema as ReportOutput } from '../lexicon/types/com/atproto/moderation/createReport' import { REASONOTHER } from '../lexicon/types/com/atproto/moderation/defs' import { @@ -19,7 +23,21 @@ import { RecordViewDetail, RepoView, SubjectStatusView, + isAccountEvent, + isIdentityEvent, + isModEventAcknowledge, + isModEventComment, + isModEventEmail, + isModEventEscalate, + isModEventLabel, + isModEventMute, + isModEventMuteReporter, + isModEventReport, + isModEventTag, + isModEventTakedown, + isRecordEvent, } from '../lexicon/types/tools/ozone/moderation/defs' +import { Un$Typed, asPredicate } from '../lexicon/util' import { dbLogger, httpLogger } from '../logger' import { ParsedLabelers } from '../util' import { moderationSubjectStatusQueryBuilder } from './status' @@ -30,6 +48,13 @@ import { } from './types' import { formatLabel, signLabel } from './util' +const isValidSelfLabels = asPredicate(validateSelfLabels) + +const ifString = (val: unknown): string | undefined => + typeof val === 'string' ? val : undefined +const ifBoolean = (val: unknown): boolean | undefined => + typeof val === 'boolean' ? val : undefined + export type AuthHeaders = { headers: { authorization: string @@ -93,34 +118,48 @@ export class ModerationViews { }, new Map()) } - formatEvent(event: ModerationEventRowWithHandle): ModEventView { - const eventView: ModEventView = { - id: event.id, + formatEvent(row: ModerationEventRowWithHandle): Un$Typed { + const eventView: Un$Typed = { + id: row.id, event: { - $type: event.action, - comment: event.comment ?? undefined, + $type: row.action, + comment: row.comment ?? undefined, }, - subject: subjectFromEventRow(event).lex(), - subjectBlobCids: event.subjectBlobCids ?? [], - createdBy: event.createdBy, - createdAt: event.createdAt, - subjectHandle: event.subjectHandle ?? undefined, - creatorHandle: event.creatorHandle ?? undefined, + subject: subjectFromEventRow(row).lex(), + subjectBlobCids: row.subjectBlobCids ?? [], + createdBy: row.createdBy, + createdAt: row.createdAt, + subjectHandle: row.subjectHandle ?? undefined, + creatorHandle: row.creatorHandle ?? undefined, } + const { event } = eventView + const meta = row.meta || {} + if ( - [ - 'tools.ozone.moderation.defs#modEventMuteReporter', - 'tools.ozone.moderation.defs#modEventTakedown', - 'tools.ozone.moderation.defs#modEventLabel', - 'tools.ozone.moderation.defs#modEventMute', - 'tools.ozone.moderation.defs#modEventPriorityScore', - ].includes(event.action) + isModEventMuteReporter(event) || + isModEventTakedown(event) || + isModEventMute(event) ) { - eventView.event = { - ...eventView.event, - durationInHours: event.durationInHours ?? undefined, - } + event.durationInHours = row.durationInHours ?? undefined + } + + // 'tools.ozone.moderation.defs#modEventMuteReporter', + // 'tools.ozone.moderation.defs#modEventTakedown', + // 'tools.ozone.moderation.defs#modEventLabel', + // 'tools.ozone.moderation.defs#modEventMute', + // 'tools.ozone.moderation.defs#modEventPriorityScore', + if ( + (isModEventMuteReporter(event) || + isModEventLabel(event) || + isModEventMute(event) || + isModEventTakedown(event) || + isModEventAcknowledge(event)) && + meta.acknowledgeAccountSubjects + ) { + event.acknowledgeAccountSubjects = ifBoolean( + meta.acknowledgeAccountSubjects, + )! } if (event.action === 'tools.ozone.moderation.defs#modEventPriorityScore') { @@ -131,107 +170,75 @@ export class ModerationViews { } if ( - (event.action === 'tools.ozone.moderation.defs#modEventTakedown' || - event.action === 'tools.ozone.moderation.defs#modEventAcknowledge') && - event.meta?.acknowledgeAccountSubjects + isModEventTakedown(event) && + typeof meta.policies === 'string' && + meta.policies.length > 0 ) { - eventView.event = { - ...eventView.event, - acknowledgeAccountSubjects: event.meta.acknowledgeAccountSubjects, - } + event.policies = meta.policies.split(',') } - if ( - event.action === 'tools.ozone.moderation.defs#modEventTakedown' && - typeof event.meta?.policies === 'string' && - event.meta.policies.length > 0 - ) { - eventView.event = { - ...eventView.event, - policies: event.meta.policies.split(','), - } - } - - if (event.action === 'tools.ozone.moderation.defs#modEventLabel') { - eventView.event = { - ...eventView.event, - createLabelVals: event.createLabelVals?.length - ? event.createLabelVals.split(' ') - : [], - negateLabelVals: event.negateLabelVals?.length - ? event.negateLabelVals.split(' ') - : [], - } + if (isModEventLabel(event)) { + event.createLabelVals = row.createLabelVals?.length + ? row.createLabelVals.split(' ') + : [] + event.negateLabelVals = row.negateLabelVals?.length + ? row.negateLabelVals.split(' ') + : [] } // This is for legacy data only, for new events, these types of events won't have labels attached if ( - [ - 'tools.ozone.moderation.defs#modEventAcknowledge', - 'tools.ozone.moderation.defs#modEventTakedown', - 'tools.ozone.moderation.defs#modEventEscalate', - ].includes(event.action) + isModEventAcknowledge(event) || + isModEventTakedown(event) || + isModEventEscalate(event) ) { - if (event.createLabelVals?.length) { - eventView.event = { - ...eventView.event, - createLabelVals: event.createLabelVals.split(' '), - } + if (row.createLabelVals?.length) { + // @ts-expect-error legacy + event.createLabelVals = row.createLabelVals.split(' ') } - if (event.negateLabelVals?.length) { - eventView.event = { - ...eventView.event, - negateLabelVals: event.negateLabelVals.split(' '), - } + if (row.negateLabelVals?.length) { + // @ts-expect-error legacy + event.negateLabelVals = row.negateLabelVals.split(' ') } } - if (event.action === 'tools.ozone.moderation.defs#modEventReport') { - eventView.event = { - ...eventView.event, - reportType: event.meta?.reportType ?? undefined, - isReporterMuted: !!event.meta?.isReporterMuted, - } + if (isModEventReport(event)) { + event.isReporterMuted = !!meta.isReporterMuted + event.reportType = ifString(meta.reportType)! } - if (event.action === 'tools.ozone.moderation.defs#modEventEmail') { - eventView.event = { - ...eventView.event, - subjectLine: event.meta?.subjectLine ?? '', - content: event.meta?.content, - } + if (isModEventEmail(event)) { + event.content = ifString(meta.content)! + event.subjectLine = ifString(meta.subjectLine)! } - if ( - event.action === 'tools.ozone.moderation.defs#modEventComment' && - event.meta?.sticky - ) { - eventView.event.sticky = true + if (isModEventComment(event) && meta.sticky) { + event.sticky = true } - if (event.action === 'tools.ozone.moderation.defs#modEventTag') { - eventView.event.add = event.addedTags || [] - eventView.event.remove = event.removedTags || [] + if (isModEventTag(event)) { + event.add = row.addedTags || [] + event.remove = row.removedTags || [] } - if (event.action === 'tools.ozone.moderation.defs#accountEvent') { - eventView.event.active = !!event.meta?.active - eventView.event.timestamp = event.meta?.timestamp - eventView.event.status = event.meta?.status + if (isAccountEvent(event)) { + event.active = !!meta.active + event.timestamp = ifString(meta.timestamp)! + event.status = ifString(meta.status)! } - if (event.action === 'tools.ozone.moderation.defs#identityEvent') { - eventView.event.timestamp = event.meta?.timestamp - eventView.event.handle = event.meta?.handle - eventView.event.pdsHost = event.meta?.pdsHost - eventView.event.tombstone = !!event.meta?.tombstone + if (isIdentityEvent(event)) { + event.timestamp = ifString(meta.timestamp)! + event.handle = ifString(meta.handle)! + event.pdsHost = ifString(meta.pdsHost)! + event.tombstone = !!meta.tombstone } - if (event.action === 'tools.ozone.moderation.defs#recordEvent') { - eventView.event.op = event.meta?.op - eventView.event.cid = event.meta?.cid - eventView.event.timestamp = event.meta?.timestamp + if (isRecordEvent(event)) { + event.op = ifString(meta.op)! + event.cid = ifString(meta.cid)! + event.timestamp = ifString(meta.timestamp)! } return eventView @@ -249,7 +256,7 @@ export class ModerationViews { } const subject = await this.subject(subjectId) const eventView = this.formatEvent(result) - const allBlobs = findBlobRefs(subject.value) + const allBlobs = 'value' in subject ? findBlobRefs(subject.value) : [] const subjectBlobs = await this.blob( allBlobs.filter((blob) => eventView.subjectBlobCids.includes(blob.ref.toString()), @@ -323,7 +330,7 @@ export class ModerationViews { }, new Map()) } - async records(subjects: RecordSubject[]): Promise> { + async records(subjects: RecordSubject[]) { const uris = subjects.map((record) => new AtUri(record.uri)) const dids = uris.map((u) => u.hostname) @@ -333,13 +340,26 @@ export class ModerationViews { this.fetchRecords(subjects), ]) - return uris.reduce((acc, uri) => { + const map = new Map< + string, + // Because the result of this function is used to build RecordViewDetail, + // we explicitly type the result without the $type field, so can be used + // as both RecordView and RecordViewDetail, without having to cast or + // override the $type field. + RecordView & { + $type?: undefined + moderation: { $type?: undefined; subjectStatus?: SubjectStatusView } + } + >() + + for (const uri of uris) { const repo = repos.get(uri.hostname) - if (!repo) return acc + if (!repo) continue const record = records.get(uri.toString()) - if (!record) return acc + if (!record) continue const subjectStatus = subjectStatuses.get(uri.toString()) - return acc.set(uri.toString(), { + + map.set(uri.toString(), { uri: uri.toString(), cid: record.cid, value: record.value, @@ -352,7 +372,9 @@ export class ModerationViews { : undefined, }, }) - }, new Map()) + } + + return map } async recordDetails( @@ -443,7 +465,7 @@ export class ModerationViews { : REASONOTHER, reason: report.comment ?? undefined, reportedBy: report.createdBy, - subject: subjectFromEventRow(report).lex(), + subject: subjectFromEventRow(report).lex() as ReportOutput['subject'], } } // Partial view for subjects @@ -454,12 +476,12 @@ export class ModerationViews { const repo = repos.get(subject) if (repo) { return { - $type: 'com.atproto.admin.defs#repoView', ...repo, + $type: 'tools.ozone.moderation.defs#repoView', } } else { return { - $type: 'com.atproto.admin.defs#repoViewNotFound', + $type: 'tools.ozone.moderation.defs#repoViewNotFound', did: subject, } } @@ -468,12 +490,12 @@ export class ModerationViews { const record = records.get(subject) if (record) { return { - $type: 'com.atproto.admin.defs#recordView', ...record, + $type: 'tools.ozone.moderation.defs#recordView', } } else { return { - $type: 'com.atproto.admin.defs#recordViewNotFound', + $type: 'tools.ozone.moderation.defs#recordViewNotFound', uri: subject, } } @@ -621,7 +643,9 @@ export class ModerationViews { subjectBlobCids: status.blobCids || [], tags: status.tags || [], priorityScore: status.priorityScore, - subject: subjectFromStatusRow(status).lex(), + subject: subjectFromStatusRow( + status, + ).lex() as SubjectStatusView['subject'], accountStats: { // Explicitly typing to allow for easy manipulation (e.g. to strip from tests snapshots) @@ -674,9 +698,7 @@ export class ModerationViews { return statusView } - async fetchAuthorFeed( - actor: string, - ): Promise { + async fetchAuthorFeed(actor: string): Promise { const auth = await this.appviewAuth(ids.AppBskyFeedGetAuthorFeed) if (!auth) return [] const { @@ -730,7 +752,7 @@ export function getSelfLabels(details: { }): Label[] { const { uri, cid, record } = details if (!uri || !cid || !record) return [] - if (!isSelfLabels(record.labels)) return [] + if (!isValidSelfLabels(record.labels)) return [] const src = new AtUri(uri).host // record creator const cts = typeof record.createdAt === 'string' diff --git a/packages/ozone/src/tag-service/embed-tagger.ts b/packages/ozone/src/tag-service/embed-tagger.ts index 5f8161f295e..cd7d5a19f0d 100644 --- a/packages/ozone/src/tag-service/embed-tagger.ts +++ b/packages/ozone/src/tag-service/embed-tagger.ts @@ -28,12 +28,14 @@ export class EmbedTagger extends ContentTagger { return [] } const tags: string[] = [] - if (AppBskyFeedPost.isRecord(recordValue)) { + const result = AppBskyFeedPost.validateRecord(recordValue) + + if (result.success) { const embedContent = AppBskyEmbedRecordWithMedia.isMain( - recordValue.embed, + result.value.embed, ) - ? recordValue.embed.media - : recordValue.embed + ? result.value.embed.media + : result.value.embed if (AppBskyEmbedImages.isMain(embedContent)) { tags.push(`${this.tagPrefix}image`) diff --git a/packages/ozone/src/tag-service/language-tagger.ts b/packages/ozone/src/tag-service/language-tagger.ts index 9d461d3f35c..7c0099ed224 100644 --- a/packages/ozone/src/tag-service/language-tagger.ts +++ b/packages/ozone/src/tag-service/language-tagger.ts @@ -8,6 +8,11 @@ import { langLogger as log } from '../logger' import { ContentTagger } from './content-tagger' import { code3ToCode2 } from './language-data' +const ifString = (value: unknown): string | undefined => + typeof value === 'string' ? value : undefined +const isStringProp = (obj: object, prop: string): string | undefined => + prop in obj ? ifString(obj[prop]) : undefined + export class LanguageTagger extends ContentTagger { tagPrefix = 'lang:' @@ -27,18 +32,22 @@ export class LanguageTagger extends ContentTagger { } } - getTextFromRecord(recordValue?: Record): string | undefined { + getTextFromRecord(recordValue: Record): string | undefined { let text: string | undefined if (AppBskyGraphList.isRecord(recordValue)) { - text = recordValue.description || recordValue.name + text = + isStringProp(recordValue, 'description') || + isStringProp(recordValue, 'name') } else if ( AppBskyFeedGenerator.isRecord(recordValue) || AppBskyActorProfile.isRecord(recordValue) ) { - text = recordValue.description || recordValue.displayName + text = + isStringProp(recordValue, 'description') || + isStringProp(recordValue, 'displayName') } else if (AppBskyFeedPost.isRecord(recordValue)) { - text = recordValue.text + text = isStringProp(recordValue, 'text') } return text?.trim() @@ -70,7 +79,9 @@ export class LanguageTagger extends ContentTagger { ]) const record = recordByUri.get(this.subject.uri) const recordLang = record?.value.langs as string[] | null - const recordText = this.getTextFromRecord(record?.value) + const recordText = record + ? this.getTextFromRecord(record.value) + : undefined if (recordLang?.length) { recordLang .map((lang) => lang.split('-')[0]) diff --git a/packages/ozone/tests/__snapshots__/moderation-events.test.ts.snap b/packages/ozone/tests/__snapshots__/moderation-events.test.ts.snap index d4988121159..595a9b9e151 100644 --- a/packages/ozone/tests/__snapshots__/moderation-events.test.ts.snap +++ b/packages/ozone/tests/__snapshots__/moderation-events.test.ts.snap @@ -12,7 +12,7 @@ Object { }, "id": 1, "subject": Object { - "$type": "com.atproto.admin.defs#repoView", + "$type": "tools.ozone.moderation.defs#repoView", "did": "user(0)", "handle": "alice.test", "indexedAt": "1970-01-01T00:00:00.000Z", diff --git a/packages/ozone/tests/_util.ts b/packages/ozone/tests/_util.ts index 144d26940a5..6aa2e8be483 100644 --- a/packages/ozone/tests/_util.ts +++ b/packages/ozone/tests/_util.ts @@ -4,14 +4,22 @@ import { type Express } from 'express' import { CID } from 'multiformats/cid' import { lexToJson } from '@atproto/lexicon' import { AtUri } from '@atproto/syntax' -import { isViewRecord } from '../src/lexicon/types/app/bsky/embed/record' +import { + isView as isEmbedRecordView, + isViewRecord, +} from '../src/lexicon/types/app/bsky/embed/record' +import { isView as isEmbedRecordWithMediaView } from '../src/lexicon/types/app/bsky/embed/recordWithMedia' import { FeedViewPost, PostView, + ThreadViewPost, isPostView, + isReasonRepost, isThreadViewPost, } from '../src/lexicon/types/app/bsky/feed/defs' +export const identity = (x: T) => x + // Swap out identifiers and dates with stable // values for the purpose of snapshot testing export const forSnapshot = (obj: unknown) => { @@ -86,10 +94,10 @@ export const forSnapshot = (obj: unknown) => { // Feed testing utils export const getOriginator = (item: FeedViewPost) => { - if (!item.reason) { - return item.post.author.did + if (isReasonRepost(item.reason)) { + return item.reason.by.did } else { - return (item.reason.by as { [did: string]: string }).did + return item.post.author.did } } @@ -149,34 +157,35 @@ export const paginateAll = async ( } // @NOTE mutates -export const stripViewer = }>( - val: T, -): T => { +export const stripViewer = (val: T): T => { delete val.viewer return val } +const extractRecordEmbed = (embed: PostView['embed']) => + isEmbedRecordView(embed) + ? isViewRecord(embed.record) + ? embed.record + : undefined + : isEmbedRecordWithMediaView(embed) + ? isViewRecord(embed.record.record) + ? embed.record.record + : undefined + : undefined + // @NOTE mutates -export const stripViewerFromPost = (postUnknown: unknown): PostView => { - if (postUnknown?.['$type'] && !isPostView(postUnknown)) { +export const stripViewerFromPost = (postUnknown: object): PostView => { + if ('$type' in postUnknown && !isPostView(postUnknown)) { throw new Error('Expected post view') } const post = postUnknown as PostView post.author = stripViewer(post.author) - const recordEmbed = - post.embed && isViewRecord(post.embed.record) - ? post.embed.record // Record from record embed - : post.embed?.['record'] && isViewRecord(post.embed['record']['record']) - ? post.embed['record']['record'] // Record from record-with-media embed - : undefined + + const recordEmbed = extractRecordEmbed(post.embed) if (recordEmbed) { recordEmbed.author = stripViewer(recordEmbed.author) recordEmbed.embeds?.forEach((deepEmbed) => { - const deepRecordEmbed = isViewRecord(deepEmbed.record) - ? deepEmbed.record // Record from record embed - : deepEmbed['record'] && isViewRecord(deepEmbed['record']['record']) - ? deepEmbed['record']['record'] // Record from record-with-media embed - : undefined + const deepRecordEmbed = extractRecordEmbed(deepEmbed) if (deepRecordEmbed) { deepRecordEmbed.author = stripViewer(deepRecordEmbed.author) } @@ -186,15 +195,20 @@ export const stripViewerFromPost = (postUnknown: unknown): PostView => { } // @NOTE mutates -export const stripViewerFromThread = (thread: T): T => { +export const stripViewerFromThread = ( + thread: T, +): Omit => { if (!isThreadViewPost(thread)) return thread + // @ts-expect-error delete thread.viewer thread.post = stripViewerFromPost(thread.post) if (isThreadViewPost(thread.parent)) { thread.parent = stripViewerFromThread(thread.parent) } if (thread.replies) { - thread.replies = thread.replies.map(stripViewerFromThread) + thread.replies = thread.replies.map((r) => + isThreadViewPost(r) ? stripViewerFromThread(r) : r, + ) } return thread } diff --git a/packages/ozone/tests/blob-divert.test.ts b/packages/ozone/tests/blob-divert.test.ts index 4637f3bc420..04a3578af26 100644 --- a/packages/ozone/tests/blob-divert.test.ts +++ b/packages/ozone/tests/blob-divert.test.ts @@ -1,4 +1,5 @@ import assert from 'node:assert' +import { ToolsOzoneModerationDefs } from '@atproto/api' import { ModeratorClient, SeedClient, @@ -6,7 +7,7 @@ import { basicSeed, } from '@atproto/dev-env' import { ResponseType, XRPCError } from '@atproto/xrpc' -import { forSnapshot } from './_util' +import { forSnapshot, identity } from './_util' describe('blob divert', () => { let network: TestNetwork @@ -56,10 +57,11 @@ describe('blob divert', () => { modClient.emitEvent( { subject: getSubject(), - event: { + // @ts-expect-error "tools.ozone.moderation.defs#modEventDivert" is not part of the event open union + event: identity({ $type: 'tools.ozone.moderation.defs#modEventDivert', comment: 'Diverting for test', - }, + }), createdBy: sc.dids.alice, subjectBlobCids: getImages().map((img) => img.image.ref.toString()), }, diff --git a/packages/ozone/tests/moderation-appeals.test.ts b/packages/ozone/tests/moderation-appeals.test.ts index e1b8731598b..d81b0be425e 100644 --- a/packages/ozone/tests/moderation-appeals.test.ts +++ b/packages/ozone/tests/moderation-appeals.test.ts @@ -98,6 +98,7 @@ describe('moderation-appeals', () => { // Verify that appeal status changed when appeal report was emitted by moderator const status = await assertBobsPostStatus(REVIEWESCALATED, true) + // @ts-expect-error unspecced ? expect(status?.appealedAt).not.toBeNull() // Create a report as normal user for carol's post diff --git a/packages/ozone/tests/moderation-events.test.ts b/packages/ozone/tests/moderation-events.test.ts index af671e4e70a..288e821f5b6 100644 --- a/packages/ozone/tests/moderation-events.test.ts +++ b/packages/ozone/tests/moderation-events.test.ts @@ -7,11 +7,13 @@ import { TestNetwork, basicSeed, } from '@atproto/dev-env' +import { isRepoRef } from '../src/lexicon/types/com/atproto/admin/defs' import { REASONAPPEAL, REASONMISLEADING, REASONSPAM, } from '../src/lexicon/types/com/atproto/moderation/defs' +import { isMain as isStrongRef } from '../src/lexicon/types/com/atproto/repo/strongRef' import { forSnapshot } from './_util' describe('moderation-events', () => { @@ -361,6 +363,7 @@ describe('moderation-events', () => { expect(addAndRemoveEvent.id).toEqual(addAndRemoveFinder.events[0].id) expect(addAndRemoveEvent.id).toEqual(addAndRemoveFinder.events[0].id) + assert(ToolsOzoneModerationDefs.isModEventTag(addAndRemoveEvent.event)) expect(addAndRemoveEvent.event.add).toEqual(['L3']) expect(addAndRemoveEvent.event.remove).toEqual(['L2']) }) @@ -411,11 +414,13 @@ describe('moderation-events', () => { ]) expect(onlyStarterPackReports.events.length).toEqual(1) + assert(isStrongRef(onlyStarterPackReports.events[0].subject)) expect(onlyStarterPackReports.events[0].subject.uri).toContain( 'app.bsky.graph.starterpack', ) expect(onlyAlicesStarterPackReports.events.length).toEqual(1) + assert(isStrongRef(onlyAlicesStarterPackReports.events[0].subject)) expect(onlyAlicesStarterPackReports.events[0].subject.uri).toContain( sp.uriStr, ) @@ -441,20 +446,24 @@ describe('moderation-events', () => { }), ]) - // only account reports are returned, no event has a uri - expect( - onlyAccountReports.events.every((e) => !e.subject.uri), - ).toBeTruthy() + assert( + onlyAccountReports.events.every((e) => !isStrongRef(e.subject)), + 'only account reports are returned, no event has a uri', + ) - // only record reports are returned, all events have a uri - expect(onlyRecordReports.events.every((e) => e.subject.uri)).toBeTruthy() + assert( + onlyRecordReports.events.every( + (e) => isStrongRef(e.subject) && e.subject.uri, + ), + 'only record reports are returned, all events have a uri', + ) - // only bob's account reports are returned, no events have a URI even though the subjectType is record - expect( + assert( onlyReportsOnBobsAccount.events.every( - (e) => !e.subject.uri && e.subject.did === sc.dids.bob, + (e) => isRepoRef(e.subject) && e.subject.did === sc.dids.bob, ), - ).toBeTruthy() + "only bob's account reports are returned, no events have a URI even though the subjectType is record", + ) }) }) diff --git a/packages/ozone/tests/moderation-status-tags.test.ts b/packages/ozone/tests/moderation-status-tags.test.ts index 532a87a77c2..f28566ed084 100644 --- a/packages/ozone/tests/moderation-status-tags.test.ts +++ b/packages/ozone/tests/moderation-status-tags.test.ts @@ -1,3 +1,5 @@ +import assert from 'node:assert' +import { ComAtprotoAdminDefs } from '@atproto/api' import { ModeratorClient, SeedClient, @@ -96,14 +98,17 @@ describe('moderation-status-tags', () => { // Verify that the queue only contains 1 item with both en and ja tags which is alice's account expect(englishAndJapaneseQueue.subjectStatuses.length).toEqual(1) - expect(englishAndJapaneseQueue.subjectStatuses[0].subject.did).toEqual( - sc.dids.alice, - ) + const { subject } = englishAndJapaneseQueue.subjectStatuses[0] + assert(ComAtprotoAdminDefs.isRepoRef(subject)) + expect(subject.did).toEqual(sc.dids.alice) // Verify that when querying for either en or ja tags, both alice and bob are returned expect(englishOrJapaneseQueue.subjectStatuses.length).toEqual(2) const englishOrJapaneseDids = englishOrJapaneseQueue.subjectStatuses.map( - ({ subject }) => subject.did, + ({ subject }) => { + assert(ComAtprotoAdminDefs.isRepoRef(subject)) + return subject.did + }, ) expect(englishOrJapaneseDids).toContain(sc.dids.alice) expect(englishOrJapaneseDids).toContain(sc.dids.bob) diff --git a/packages/ozone/tests/moderation-statuses.test.ts b/packages/ozone/tests/moderation-statuses.test.ts index b50f68606da..c8d17b60cf7 100644 --- a/packages/ozone/tests/moderation-statuses.test.ts +++ b/packages/ozone/tests/moderation-statuses.test.ts @@ -9,10 +9,12 @@ import { TestNetwork, basicSeed, } from '@atproto/dev-env' +import { isRepoRef } from '../src/lexicon/types/com/atproto/admin/defs' import { REASONMISLEADING, REASONSPAM, } from '../src/lexicon/types/com/atproto/moderation/defs' +import { isMain as isStrongRef } from '../src/lexicon/types/com/atproto/repo/strongRef' import { REVIEWNONE, REVIEWOPEN, @@ -220,10 +222,14 @@ describe('moderation-statuses', () => { ]) expect(onlyStarterPackStatuses.subjectStatuses.length).toEqual(1) + assert(isStrongRef(onlyStarterPackStatuses.subjectStatuses[0].subject)) expect(onlyStarterPackStatuses.subjectStatuses[0].subject.uri).toContain( 'app.bsky.graph.starterpack', ) expect(onlyAlicesStarterPackStatuses.subjectStatuses.length).toEqual(1) + assert( + isStrongRef(onlyAlicesStarterPackStatuses.subjectStatuses[0].subject), + ) expect( onlyAlicesStarterPackStatuses.subjectStatuses[0].subject.uri, ).toEqual(sp.uriStr) @@ -249,22 +255,26 @@ describe('moderation-statuses', () => { }), ]) - // only account statuses are returned, no event has a uri - expect( - onlyAccountStatuses.subjectStatuses.every((e) => !e.subject.uri), - ).toBeTruthy() + assert( + onlyAccountStatuses.subjectStatuses.every( + (e) => !isStrongRef(e.subject), + ), + 'only account statuses are returned, no event has a uri', + ) - // only record statuses are returned, all events have a uri - expect( - onlyRecordStatuses.subjectStatuses.every((e) => e.subject.uri), - ).toBeTruthy() + assert( + onlyRecordStatuses.subjectStatuses.every( + (e) => isStrongRef(e.subject) && e.subject.uri, + ), + 'only record statuses are returned, all events have a uri', + ) - // only bob's account statuses are returned, no events have a URI even though the subjectType is record - expect( + assert( onlyStatusesOnBobsAccount.subjectStatuses.every( - (e) => !e.subject.uri && e.subject.did === sc.dids.bob, + (e) => isRepoRef(e.subject) && e.subject.did === sc.dids.bob, ), - ).toBeTruthy() + "only bob's account statuses are returned, no events have a URI even though the subjectType is record", + ) }) }) diff --git a/packages/ozone/tests/moderation.test.ts b/packages/ozone/tests/moderation.test.ts index e029017e83d..f9f82e3e1e5 100644 --- a/packages/ozone/tests/moderation.test.ts +++ b/packages/ozone/tests/moderation.test.ts @@ -1,4 +1,8 @@ -import { AtpAgent, ToolsOzoneModerationEmitEvent } from '@atproto/api' +import { + AtpAgent, + ChatBskyConvoDefs, + ToolsOzoneModerationEmitEvent, +} from '@atproto/api' import { HOUR } from '@atproto/common' import { ImageRef, @@ -24,7 +28,7 @@ import { REVIEWESCALATED, } from '../src/lexicon/types/tools/ozone/moderation/defs' import { TAKEDOWN_LABEL } from '../src/mod-service' -import { forSnapshot } from './_util' +import { forSnapshot, identity } from './_util' describe('moderation', () => { let network: TestNetwork @@ -158,23 +162,26 @@ describe('moderation', () => { const reportA = await sc.createReport({ reportedBy: sc.dids.alice, reasonType: REASONSPAM, - subject: { + // @ts-expect-error "chat.bsky.convo.defs#messageRef" is not spec'd as subject + subject: identity({ $type: 'chat.bsky.convo.defs#messageRef', did: sc.dids.carol, messageId: messageId1, convoId: 'testconvoid1', - }, + }), }) const reportB = await sc.createReport({ reportedBy: sc.dids.carol, reasonType: REASONOTHER, reason: 'defamation', - subject: { + // @ts-expect-error "chat.bsky.convo.defs#messageRef" is not spec'd as subject + subject: identity({ $type: 'chat.bsky.convo.defs#messageRef', did: sc.dids.carol, messageId: messageId2, - // @TODO convoId intentionally missing, restore once this behavior is deprecated - }, + // @ts-expect-error convoId intentionally missing, restore once this behavior is deprecated + convoId: undefined, + }), }) expect(forSnapshot([reportA, reportB])).toMatchSnapshot() const events = await ozone.ctx.db.db @@ -731,7 +738,7 @@ describe('moderation', () => { }, ) { const { createLabelVals, negateLabelVals, durationInHours } = opts - const result = await modClient.emitEvent({ + const event = await modClient.emitEvent({ event: { $type: 'tools.ozone.moderation.defs#modEventLabel', createLabelVals, @@ -742,7 +749,7 @@ describe('moderation', () => { reason: 'Y', ...opts, }) - return result.data + return event } async function reverse( diff --git a/packages/ozone/tests/protected-tags.test.ts b/packages/ozone/tests/protected-tags.test.ts index dfdb3279e3a..60e814f5d36 100644 --- a/packages/ozone/tests/protected-tags.test.ts +++ b/packages/ozone/tests/protected-tags.test.ts @@ -50,6 +50,7 @@ describe('protected-tags', () => { modClient.upsertSettingOption({ ...basicSetting, managerRole: ROLEADMIN, + // @ts-expect-error testing invalid value here value: ['test'], }), ).rejects.toThrow('Invalid configuration') diff --git a/packages/ozone/tests/record-and-account-events.test.ts b/packages/ozone/tests/record-and-account-events.test.ts index 669031bb2ac..f98b18794cf 100644 --- a/packages/ozone/tests/record-and-account-events.test.ts +++ b/packages/ozone/tests/record-and-account-events.test.ts @@ -1,15 +1,20 @@ -import { - ComAtprotoModerationDefs, - ToolsOzoneModerationDefs, - ToolsOzoneModerationEmitEvent as EmitModerationEvent, -} from '@atproto/api' +import assert from 'node:assert' import { ModeratorClient, SeedClient, TestNetwork, basicSeed, } from '@atproto/dev-env' -import { REVIEWOPEN } from '../src/lexicon/types/tools/ozone/moderation/defs' +import { isRepoRef } from '../src/lexicon/types/com/atproto/admin/defs' +import { REASONMISLEADING } from '../src/lexicon/types/com/atproto/moderation/defs' +import { isMain as isStrongRef } from '../src/lexicon/types/com/atproto/repo/strongRef' +import { + REVIEWOPEN, + SubjectStatusView, + isAccountHosting, + isRecordHosting, +} from '../src/lexicon/types/tools/ozone/moderation/defs' +import { InputSchema } from '../src/lexicon/types/tools/ozone/moderation/emitEvent' describe('record and account events on moderation subjects', () => { let network: TestNetwork @@ -32,7 +37,7 @@ describe('record and account events on moderation subjects', () => { const getSubjectStatus = async ( subject: string, - ): Promise => { + ): Promise => { const res = await modClient.queryStatuses({ subject, }) @@ -41,7 +46,7 @@ describe('record and account events on moderation subjects', () => { describe('record events', () => { const emitRecordEvent = async ( - subject: EmitModerationEvent.InputSchema['subject'], + subject: InputSchema['subject'], op: 'create' | 'update' | 'delete', ) => { return await modClient.emitEvent( @@ -67,26 +72,28 @@ describe('record and account events on moderation subjects', () => { await sc.createReport({ reportedBy: sc.dids.carol, - reasonType: ComAtprotoModerationDefs.REASONMISLEADING, + reasonType: REASONMISLEADING, reason: 'misleading', subject: bobsPostSubject, }) await emitRecordEvent(bobsPostSubject, 'update') const statusAfterUpdate = await getSubjectStatus(bobsPostSubject.uri) - expect(statusAfterUpdate?.hosting?.updatedAt).toBeTruthy() + assert(isRecordHosting(statusAfterUpdate?.hosting)) + expect(statusAfterUpdate.hosting?.updatedAt).toBeTruthy() await emitRecordEvent(bobsPostSubject, 'delete') const statusAfterDelete = await getSubjectStatus(bobsPostSubject.uri) - expect(statusAfterDelete?.hosting?.deletedAt).toBeTruthy() - expect(statusAfterDelete?.hosting?.status).toEqual('deleted') + assert(isRecordHosting(statusAfterDelete?.hosting)) + expect(statusAfterDelete.hosting?.deletedAt).toBeTruthy() + expect(statusAfterDelete.hosting?.status).toEqual('deleted') // Ensure that due to delete or update event, review state does not change - expect(statusAfterDelete?.reviewState).toEqual(REVIEWOPEN) + expect(statusAfterDelete.reviewState).toEqual(REVIEWOPEN) }) }) describe('account/identity events', () => { const emitAccountEvent = async ( - subject: EmitModerationEvent.InputSchema['subject'], + subject: InputSchema['subject'], active: boolean, status?: 'takendown' | 'deleted' | 'deactivated' | 'suspended', ) => { @@ -113,7 +120,7 @@ describe('record and account events on moderation subjects', () => { await sc.createReport({ reportedBy: sc.dids.carol, - reasonType: ComAtprotoModerationDefs.REASONMISLEADING, + reasonType: REASONMISLEADING, reason: 'misleading', subject: carolsAccountSubject, }) @@ -122,17 +129,19 @@ describe('record and account events on moderation subjects', () => { const statusAfterDeactivation = await getSubjectStatus( carolsAccountSubject.did, ) - expect(statusAfterDeactivation?.hosting?.deactivatedAt).toBeTruthy() - expect(statusAfterDeactivation?.hosting?.status).toEqual('deactivated') - expect(statusAfterDeactivation?.reviewState).toEqual(REVIEWOPEN) + assert(isAccountHosting(statusAfterDeactivation?.hosting)) + expect(statusAfterDeactivation.hosting.deactivatedAt).toBeTruthy() + expect(statusAfterDeactivation.hosting.status).toEqual('deactivated') + expect(statusAfterDeactivation.reviewState).toEqual(REVIEWOPEN) await emitAccountEvent(carolsAccountSubject, true) const statusAfterReactivation = await getSubjectStatus( carolsAccountSubject.did, ) - expect(statusAfterReactivation?.hosting?.updatedAt).toBeTruthy() - expect(statusAfterReactivation?.hosting?.status).toEqual('active') - expect(statusAfterReactivation?.hosting?.deletedAt).toBeFalsy() + assert(isAccountHosting(statusAfterReactivation?.hosting)) + expect(statusAfterReactivation.hosting.updatedAt).toBeTruthy() + expect(statusAfterReactivation.hosting.status).toEqual('active') + expect(statusAfterReactivation.hosting.deletedAt).toBeFalsy() }) it('gets statuses by hosting properties', async () => { @@ -176,9 +185,11 @@ describe('record and account events on moderation subjects', () => { expect(deactivatedOrDeletedStatuses.length).toEqual(3) expect(deletedStatusesInPastDay.length).toEqual(2) + assert(isStrongRef(deletedStatusesInPastDay[0]?.subject)) expect(deletedStatusesInPastDay[0]?.subject.uri).toEqual( sc.posts[sc.dids.bob][1].ref.uriStr, ) + assert(isRepoRef(deletedStatusesInPastDay[1]?.subject)) expect(deletedStatusesInPastDay[1]?.subject.did).toEqual(sc.dids.bob) expect(deletedStatusesBeforeYesterday.length).toEqual(0) }) diff --git a/packages/ozone/tests/takedown.test.ts b/packages/ozone/tests/takedown.test.ts index fa5c4f2dc84..4e95902457f 100644 --- a/packages/ozone/tests/takedown.test.ts +++ b/packages/ozone/tests/takedown.test.ts @@ -1,3 +1,5 @@ +import assert from 'node:assert' +import { ComAtprotoAdminDefs, ToolsOzoneModerationDefs } from '@atproto/api' import { ModeratorClient, SeedClient, @@ -37,12 +39,15 @@ describe('moderation', () => { }) // Verify that that the takedown even exposes the policy specified for it - const { events } = await modClient.queryEvents({ + const { events: eventViews } = await modClient.queryEvents({ subject: sc.dids.bob, types: ['tools.ozone.moderation.defs#modEventTakedown'], }) - expect(events[0].event.policies?.[0]).toEqual('trolling') + const { event } = eventViews[0] + + assert(ToolsOzoneModerationDefs.isModEventTakedown(event)) + expect(event.policies?.[0]).toEqual('trolling') // Verify that event stream can be filtered by policy const { events: filteredEvents } = await modClient.queryEvents({ @@ -50,6 +55,9 @@ describe('moderation', () => { policies: ['trolling'], }) - expect(filteredEvents[0].subject.did).toEqual(sc.dids.bob) + const { subject } = filteredEvents[0] + + assert(ComAtprotoAdminDefs.isRepoRef(subject)) + expect(subject.did).toEqual(sc.dids.bob) }) }) diff --git a/packages/pds/src/actor-store/record/reader.ts b/packages/pds/src/actor-store/record/reader.ts index c9b36c31cad..ae95dbbe4f4 100644 --- a/packages/pds/src/actor-store/record/reader.ts +++ b/packages/pds/src/actor-store/record/reader.ts @@ -70,7 +70,7 @@ export class RecordReader { rkeyStart?: string rkeyEnd?: string includeSoftDeleted?: boolean - }): Promise<{ uri: string; cid: string; value: object }[]> { + }): Promise<{ uri: string; cid: string; value: Record }[]> { const { collection, limit, @@ -125,7 +125,7 @@ export class RecordReader { ): Promise<{ uri: string cid: string - value: object + value: Record indexedAt: string takedownRef: string | null } | null> { diff --git a/packages/pds/src/api/app/bsky/feed/getActorLikes.ts b/packages/pds/src/api/app/bsky/feed/getActorLikes.ts index f5133c92428..2069e7b1736 100644 --- a/packages/pds/src/api/app/bsky/feed/getActorLikes.ts +++ b/packages/pds/src/api/app/bsky/feed/getActorLikes.ts @@ -1,6 +1,6 @@ import { AppContext } from '../../../../context' import { Server } from '../../../../lexicon' -import { OutputSchema } from '../../../../lexicon/types/app/bsky/feed/getAuthorFeed' +import { OutputSchema } from '../../../../lexicon/types/app/bsky/feed/getActorLikes' import { LocalRecords, LocalViewer, diff --git a/packages/pds/src/api/app/bsky/feed/getPostThread.ts b/packages/pds/src/api/app/bsky/feed/getPostThread.ts index 07da7194f31..a21d207233b 100644 --- a/packages/pds/src/api/app/bsky/feed/getPostThread.ts +++ b/packages/pds/src/api/app/bsky/feed/getPostThread.ts @@ -13,6 +13,7 @@ import { QueryParams, } from '../../../../lexicon/types/app/bsky/feed/getPostThread' import { Record as PostRecord } from '../../../../lexicon/types/app/bsky/feed/post' +import { $Typed } from '../../../../lexicon/util' import { LocalRecords, LocalViewer, @@ -98,12 +99,12 @@ const getPostThreadMunge = async ( const addPostsToThread = async ( localViewer: LocalViewer, - original: ThreadViewPost, + original: $Typed, posts: RecordDescript[], ) => { const inThread = findPostsInThread(original, posts) if (inThread.length === 0) return original - let thread: ThreadViewPost = original + let thread: $Typed = original for (const record of inThread) { thread = await insertIntoThreadReplies(localViewer, thread, record) } @@ -124,9 +125,9 @@ const findPostsInThread = ( const insertIntoThreadReplies = async ( localViewer: LocalViewer, - view: ThreadViewPost, + view: $Typed, descript: RecordDescript, -): Promise => { +): Promise<$Typed> => { if (descript.record.reply?.parent.uri === view.post.uri) { const postView = await threadPostView(localViewer, descript) if (!postView) return view @@ -153,7 +154,7 @@ const insertIntoThreadReplies = async ( const threadPostView = async ( localViewer: LocalViewer, descript: RecordDescript, -): Promise => { +): Promise<$Typed | null> => { const postView = await localViewer.getPost(descript) if (!postView) return null return { diff --git a/packages/pds/src/api/com/atproto/identity/signPlcOperation.ts b/packages/pds/src/api/com/atproto/identity/signPlcOperation.ts index 01f9b613d1c..ecfd7882a3d 100644 --- a/packages/pds/src/api/com/atproto/identity/signPlcOperation.ts +++ b/packages/pds/src/api/com/atproto/identity/signPlcOperation.ts @@ -50,8 +50,16 @@ export default function (server: Server, ctx: AppContext) { rotationKeys: input.body.rotationKeys ?? lastOp.rotationKeys, alsoKnownAs: input.body.alsoKnownAs ?? lastOp.alsoKnownAs, verificationMethods: - input.body.verificationMethods ?? lastOp.verificationMethods, - services: input.body.services ?? lastOp.services, + // @TODO: actually validate instead of type casting + (input.body.verificationMethods as + | undefined + | Record) ?? lastOp.verificationMethods, + services: + // @TODO: actually validate instead of type casting + (input.body.services as + | undefined + | Record) ?? + lastOp.services, }), ) return { diff --git a/packages/pds/src/api/com/atproto/identity/updateHandle.ts b/packages/pds/src/api/com/atproto/identity/updateHandle.ts index 7af0a0929cf..b2058c92f29 100644 --- a/packages/pds/src/api/com/atproto/identity/updateHandle.ts +++ b/packages/pds/src/api/com/atproto/identity/updateHandle.ts @@ -32,6 +32,7 @@ export default function (server: Server, ctx: AppContext) { // -> entryway(identity.updateHandle) [update handle, submit plc op] // -> pds(admin.updateAccountHandle) [track handle, sequence handle update] await ctx.entrywayAgent.com.atproto.identity.updateHandle( + // @ts-expect-error "did" is not in the schema { did: requester, handle: input.body.handle }, await ctx.serviceAuthHeaders( auth.credentials.did, diff --git a/packages/pds/src/api/com/atproto/repo/putRecord.ts b/packages/pds/src/api/com/atproto/repo/putRecord.ts index d6d8f6019c8..3b72e255e7b 100644 --- a/packages/pds/src/api/com/atproto/repo/putRecord.ts +++ b/packages/pds/src/api/com/atproto/repo/putRecord.ts @@ -147,7 +147,7 @@ export default function (server: Server, ctx: AppContext) { // WARNING: mutates object const updateProfileLegacyBlobRef = async ( actorStore: ActorStoreTransactor, - record: ProfileRecord, + record: Partial, ) => { if (record.avatar && !record.avatar.original['$type']) { const blob = await actorStore.repo.blob.getBlobMetadata(record.avatar.ref) diff --git a/packages/pds/src/pipethrough.ts b/packages/pds/src/pipethrough.ts index a10133fee62..58b88ffc727 100644 --- a/packages/pds/src/pipethrough.ts +++ b/packages/pds/src/pipethrough.ts @@ -472,7 +472,7 @@ function* responseHeaders( // Utils // ------------------- -export const PRIVILEGED_METHODS = new Set([ +export const PRIVILEGED_METHODS = new Set([ ids.ChatBskyActorDeleteAccount, ids.ChatBskyActorExportAccountData, ids.ChatBskyConvoDeleteMessageForSelf, @@ -493,7 +493,7 @@ export const PRIVILEGED_METHODS = new Set([ // These endpoints are related to account management and must be used directly, // not proxied or service-authed. Service auth may be utilized between PDS and // entryway for these methods. -export const PROTECTED_METHODS = new Set([ +export const PROTECTED_METHODS = new Set([ ids.ComAtprotoAdminSendEmail, ids.ComAtprotoIdentityRequestPlcOperationSignature, ids.ComAtprotoIdentitySignPlcOperation, diff --git a/packages/pds/src/read-after-write/viewer.ts b/packages/pds/src/read-after-write/viewer.ts index 9fc649a0c70..c71e20e0911 100644 --- a/packages/pds/src/read-after-write/viewer.ts +++ b/packages/pds/src/read-after-write/viewer.ts @@ -13,10 +13,12 @@ import { import { Record as ProfileRecord } from '../lexicon/types/app/bsky/actor/profile' import { Main as EmbedExternal, + View as EmbedExternalView, isMain as isEmbedExternal, } from '../lexicon/types/app/bsky/embed/external' import { Main as EmbedImages, + View as EmbedImagesView, isMain as isEmbedImages, } from '../lexicon/types/app/bsky/embed/images' import { @@ -36,6 +38,7 @@ import { } from '../lexicon/types/app/bsky/feed/defs' import { Record as PostRecord } from '../lexicon/types/app/bsky/feed/post' import { ListView } from '../lexicon/types/app/bsky/graph/defs' +import { $Typed } from '../lexicon/util' import { LocalRecords, RecordDescript } from './types' type CommonSignedUris = 'avatar' | 'banner' | 'feed_thumbnail' | 'feed_fullsize' @@ -167,7 +170,9 @@ export class LocalViewer { } } - async formatSimpleEmbed(embed: EmbedImages | EmbedExternal) { + async formatSimpleEmbed( + embed: $Typed | $Typed, + ): Promise<$Typed | $Typed> { if (isEmbedImages(embed)) { const images = embed.images.map((img) => ({ thumb: this.getImageUrl('feed_thumbnail', img.image.ref.toString()), @@ -179,7 +184,7 @@ export class LocalViewer { $type: 'app.bsky.embed.images#view', images, } - } else { + } else if (isEmbedExternal(embed)) { const { uri, title, description, thumb } = embed.external return { $type: 'app.bsky.embed.external#view', @@ -192,10 +197,15 @@ export class LocalViewer { : undefined, }, } + } else { + // @ts-expect-error + throw new TypeError(`Unexpected embed type: ${embed.$type}`) } } - async formatRecordEmbed(embed: EmbedRecord): Promise { + async formatRecordEmbed( + embed: EmbedRecord, + ): Promise<$Typed> { const view = await this.formatRecordEmbedInternal(embed) return { $type: 'app.bsky.embed.record#view', @@ -211,7 +221,9 @@ export class LocalViewer { private async formatRecordEmbedInternal( embed: EmbedRecord, - ): Promise { + ): Promise< + null | $Typed | $Typed | $Typed + > { if (!this.bskyAppView) { return null } @@ -271,10 +283,9 @@ export class LocalViewer { } } - updateProfileViewBasic( - view: ProfileViewBasic, - record: ProfileRecord, - ): ProfileViewBasic { + updateProfileViewBasic< + T extends ProfileViewDetailed | ProfileViewBasic | ProfileView, + >(view: T, record: ProfileRecord): T { return { ...view, displayName: record.displayName, @@ -284,17 +295,19 @@ export class LocalViewer { } } - updateProfileView(view: ProfileView, record: ProfileRecord): ProfileView { + updateProfileView< + T extends ProfileViewDetailed | ProfileViewBasic | ProfileView, + >(view: T, record: ProfileRecord): T { return { ...this.updateProfileViewBasic(view, record), description: record.description, } } - updateProfileDetailed( - view: ProfileViewDetailed, + updateProfileDetailed( + view: T, record: ProfileRecord, - ): ProfileViewDetailed { + ): T { return { ...this.updateProfileView(view, record), banner: record.banner diff --git a/packages/pds/src/repo/prepare.ts b/packages/pds/src/repo/prepare.ts index eb86f4e9537..6fcea2f00dc 100644 --- a/packages/pds/src/repo/prepare.ts +++ b/packages/pds/src/repo/prepare.ts @@ -24,12 +24,13 @@ import { } from '@atproto/syntax' import { hasExplicitSlur } from '../handle/explicit-slurs' import * as lex from '../lexicon/lexicons' -import { isRecord as isProfile } from '../lexicon/types/app/bsky/actor/profile' -import { isRecord as isFeedGenerator } from '../lexicon/types/app/bsky/feed/generator' -import { isRecord as isPost } from '../lexicon/types/app/bsky/feed/post' -import { isRecord as isList } from '../lexicon/types/app/bsky/graph/list' -import { isRecord as isStarterPack } from '../lexicon/types/app/bsky/graph/starterpack' +import * as AppBskyActorProfile from '../lexicon/types/app/bsky/actor/profile' +import * as AppBskyFeedGenerator from '../lexicon/types/app/bsky/feed/generator' +import * as AppBskyFeedPost from '../lexicon/types/app/bsky/feed/post' +import * as AppBskyGraphList from '../lexicon/types/app/bsky/graph/list' +import * as AppBskyGraphStarterpack from '../lexicon/types/app/bsky/graph/starterpack' import { isTag } from '../lexicon/types/app/bsky/richtext/facet' +import { asPredicate } from '../lexicon/util' import { InvalidRecordError, PreparedBlobRef, @@ -40,6 +41,12 @@ import { ValidationStatus, } from './types' +const isValidFeedGenerator = asPredicate(AppBskyFeedGenerator.validateRecord) +const isValidStarterPack = asPredicate(AppBskyGraphStarterpack.validateRecord) +const isValidPost = asPredicate(AppBskyFeedPost.validateRecord) +const isValidList = asPredicate(AppBskyGraphList.validateRecord) +const isValidProfile = asPredicate(AppBskyActorProfile.validateRecord) + export const assertValidRecordWithStatus = ( record: Record, opts: { requireLexicon: boolean }, @@ -222,30 +229,31 @@ async function cidForSafeRecord(record: RepoRecord) { } function assertNoExplicitSlurs(rkey: string, record: RepoRecord) { - let toCheck = '' - if (isProfile(record)) { - toCheck += ' ' + record.displayName - } else if (isList(record)) { - toCheck += ' ' + record.name - } else if (isStarterPack(record)) { - toCheck += ' ' + record.name - } else if (isFeedGenerator(record)) { - toCheck += ' ' + rkey - toCheck += ' ' + record.displayName - } else if (isPost(record)) { + const toCheck: string[] = [] + + if (isValidProfile(record)) { + if (record.displayName) toCheck.push(record.displayName) + } else if (isValidList(record)) { + toCheck.push(record.name) + } else if (isValidStarterPack(record)) { + toCheck.push(record.name) + } else if (isValidFeedGenerator(record)) { + toCheck.push(rkey) + toCheck.push(record.displayName) + } else if (isValidPost(record)) { if (record.tags) { - toCheck += record.tags.join(' ') + toCheck.push(...record.tags) } for (const facet of record.facets || []) { for (const feat of facet.features) { if (isTag(feat)) { - toCheck += ' ' + feat.tag + toCheck.push(feat.tag) } } } } - if (hasExplicitSlur(toCheck)) { + if (hasExplicitSlur(toCheck.join(' '))) { throw new InvalidRecordError('Unacceptable slur in record') } } diff --git a/packages/pds/tests/_util.ts b/packages/pds/tests/_util.ts index d3cd7b2c82a..335318c6291 100644 --- a/packages/pds/tests/_util.ts +++ b/packages/pds/tests/_util.ts @@ -5,7 +5,10 @@ import { CID } from 'multiformats/cid' import { ToolsOzoneModerationDefs } from '@atproto/api' import { lexToJson } from '@atproto/lexicon' import { AtUri } from '@atproto/syntax' -import { FeedViewPost } from '../src/lexicon/types/app/bsky/feed/defs' +import { + FeedViewPost, + isReasonRepost, +} from '../src/lexicon/types/app/bsky/feed/defs' // Swap out identifiers and dates with stable // values for the purpose of snapshot testing @@ -98,10 +101,10 @@ export const forSnapshot = (obj: unknown) => { // Feed testing utils export const getOriginator = (item: FeedViewPost) => { - if (!item.reason) { - return item.post.author.did + if (isReasonRepost(item.reason)) { + return item.reason.by.did } else { - return (item.reason.by as { [did: string]: string }).did + return item.post.author.did } } diff --git a/packages/pds/tests/app-passwords.test.ts b/packages/pds/tests/app-passwords.test.ts index 7368007f7ec..06dd557a0b1 100644 --- a/packages/pds/tests/app-passwords.test.ts +++ b/packages/pds/tests/app-passwords.test.ts @@ -73,7 +73,7 @@ describe('app_passwords', () => { it('allows actions to be performed from app', async () => { await appAgent.api.app.bsky.feed.post.create( { - repo: appAgent.session?.did, + repo: appAgent.assertDid, }, { text: 'Testing testing', @@ -82,7 +82,7 @@ describe('app_passwords', () => { ) await priviAgent.api.app.bsky.feed.post.create( { - repo: priviAgent.session?.did, + repo: priviAgent.assertDid, }, { text: 'testing again', @@ -142,7 +142,7 @@ describe('app_passwords', () => { // allows any access auth await appAgent.api.app.bsky.feed.post.create( { - repo: appAgent.session?.did, + repo: appAgent.assertDid, }, { text: 'Testing testing', @@ -188,7 +188,7 @@ describe('app_passwords', () => { // allows any access auth await priviAgent.api.app.bsky.feed.post.create( { - repo: priviAgent.session?.did, + repo: priviAgent.assertDid, }, { text: 'Testing testing', diff --git a/packages/pds/tests/create-post.test.ts b/packages/pds/tests/create-post.test.ts index f1120d7fb38..ba86ecfb61f 100644 --- a/packages/pds/tests/create-post.test.ts +++ b/packages/pds/tests/create-post.test.ts @@ -4,6 +4,7 @@ import { AtUri, AtpAgent, RichText, + Un$Typed, } from '@atproto/api' import { SeedClient, TestNetworkNoAppView } from '@atproto/dev-env' import basicSeed from './seeds/basic' @@ -28,7 +29,7 @@ describe('pds posts record creation', () => { }) it('allows for creating posts with tags', async () => { - const post: AppBskyFeedPost.Record = { + const post: Un$Typed = { text: 'hello world', tags: ['javascript', 'hehe'], createdAt: new Date().toISOString(), @@ -52,7 +53,7 @@ describe('pds posts record creation', () => { const rt = new RichText({ text: 'hello #world' }) await rt.detectFacets(agent) - const post: AppBskyFeedPost.Record = { + const post: Un$Typed = { text: rt.text, facets: rt.facets, createdAt: new Date().toISOString(), diff --git a/packages/pds/tests/crud.test.ts b/packages/pds/tests/crud.test.ts index e0509ebcf7f..4a898c244db 100644 --- a/packages/pds/tests/crud.test.ts +++ b/packages/pds/tests/crud.test.ts @@ -8,6 +8,7 @@ import { BlobNotFoundError } from '@atproto/repo' import { AtUri } from '@atproto/syntax' import { AppContext } from '../src/context' import { ids, lexicons } from '../src/lexicon/lexicons' +import { isMain as isImagesEmbed } from '../src/lexicon/types/app/bsky/embed/images' import * as Post from '../src/lexicon/types/app/bsky/feed/post' import { forSnapshot, paginateAll } from './_util' @@ -185,7 +186,8 @@ describe('crud operations', () => { rkey: postUri.rkey, repo: aliceAgent.accountDid, }) - const images = post.value.embed?.images as { image: BlobRef }[] + assert(isImagesEmbed(post.value.embed)) + const images = post.value.embed.images expect(images.length).toEqual(1) expect(uploaded.ref.equals(images[0].image.ref)).toBeTruthy() // Ensure that the uploaded image is now in the blobstore, i.e. doesn't throw BlobNotFoundError @@ -768,7 +770,6 @@ describe('crud operations', () => { writes: [ { $type: `${ids.ComAtprotoRepoApplyWrites}#create`, - action: 'create', collection: ids.AppBskyFeedPost, value: { $type: ids.AppBskyFeedPost, @@ -778,14 +779,12 @@ describe('crud operations', () => { }, { $type: `${ids.ComAtprotoRepoApplyWrites}#update`, - action: 'update', collection: 'com.example.record', rkey: new AtUri(existing1.data.uri).rkey, value: {}, }, { $type: `${ids.ComAtprotoRepoApplyWrites}#delete`, - action: 'delete', collection: 'com.example.record', rkey: new AtUri(existing2.data.uri).rkey, }, @@ -1145,7 +1144,6 @@ describe('crud operations', () => { writes: [ { $type: `${ids.ComAtprotoRepoApplyWrites}#create`, - action: 'create', collection: ids.AppBskyFeedPost, value: { $type: ids.AppBskyFeedPost, ...postRecord() }, }, @@ -1170,7 +1168,6 @@ describe('crud operations', () => { writes: [ { $type: `${ids.ComAtprotoRepoApplyWrites}#create`, - action: 'create', collection: ids.AppBskyFeedPost, value: { $type: ids.AppBskyFeedPost, ...postRecord() }, }, diff --git a/packages/pds/tests/file-uploads.test.ts b/packages/pds/tests/file-uploads.test.ts index b3cbd26d2fd..3b4051f0178 100644 --- a/packages/pds/tests/file-uploads.test.ts +++ b/packages/pds/tests/file-uploads.test.ts @@ -182,6 +182,7 @@ describe('file uploads', () => { repo: alice, rkey: 'self', }) + // @ts-expect-error "cid" is not documented as "com.atproto.repo.uploadBlob" output expect((profileA.value as any).avatar.cid).toEqual(uploadA.cid) await sc.updateProfile(bob, { displayName: 'Bob', @@ -191,6 +192,7 @@ describe('file uploads', () => { repo: bob, rkey: 'self', }) + // @ts-expect-error "cid" is not documented as "com.atproto.repo.uploadBlob" output expect((profileB.value as any).avatar.cid).toEqual(uploadA.cid) const { data: uploadAfterPermanent } = await agent.api.com.atproto.repo.uploadBlob(file, { diff --git a/packages/pds/tests/moderation.test.ts b/packages/pds/tests/moderation.test.ts index 9adda3f4a99..a20dcad000e 100644 --- a/packages/pds/tests/moderation.test.ts +++ b/packages/pds/tests/moderation.test.ts @@ -1,11 +1,18 @@ +import assert from 'node:assert' import { AtpAgent } from '@atproto/api' import { ImageRef, SeedClient, TestNetworkNoAppView } from '@atproto/dev-env' import { BlobNotFoundError } from '@atproto/repo' import { RepoBlobRef, RepoRef, + isRepoBlobRef, + isRepoRef, } from '../src/lexicon/types/com/atproto/admin/defs' -import { Main as StrongRef } from '../src/lexicon/types/com/atproto/repo/strongRef' +import { + Main as StrongRef, + isMain as isStrongRef, +} from '../src/lexicon/types/com/atproto/repo/strongRef' +import { $Typed } from '../src/lexicon/util' import basicSeed from './seeds/basic' describe('moderation', () => { @@ -13,9 +20,9 @@ describe('moderation', () => { let agent: AtpAgent let sc: SeedClient - let repoSubject: RepoRef - let recordSubject: StrongRef - let blobSubject: RepoBlobRef + let repoSubject: $Typed + let recordSubject: $Typed + let blobSubject: $Typed let blobRef: ImageRef beforeAll(async () => { @@ -66,6 +73,7 @@ describe('moderation', () => { }, { headers: network.pds.adminAuthHeaders() }, ) + assert(isRepoRef(res.data.subject)) expect(res.data.subject.did).toEqual(sc.dids.bob) expect(res.data.takedown?.applied).toBe(true) expect(res.data.takedown?.ref).toBe('test-repo') @@ -88,6 +96,7 @@ describe('moderation', () => { }, { headers: network.pds.adminAuthHeaders() }, ) + assert(isRepoRef(res.data.subject)) expect(res.data.subject.did).toEqual(sc.dids.bob) expect(res.data.takedown?.applied).toBe(false) expect(res.data.takedown?.ref).toBeUndefined() @@ -110,6 +119,7 @@ describe('moderation', () => { }, { headers: network.pds.adminAuthHeaders() }, ) + assert(isStrongRef(res.data.subject)) expect(res.data.subject.uri).toEqual(recordSubject.uri) expect(res.data.takedown?.applied).toBe(true) expect(res.data.takedown?.ref).toBe('test-record') @@ -132,6 +142,7 @@ describe('moderation', () => { }, { headers: network.pds.adminAuthHeaders() }, ) + assert(isStrongRef(res.data.subject)) expect(res.data.subject.uri).toEqual(recordSubject.uri) expect(res.data.takedown?.applied).toBe(false) expect(res.data.takedown?.ref).toBeUndefined() @@ -156,7 +167,9 @@ describe('moderation', () => { }, { headers: network.pds.adminAuthHeaders() }, ) + assert(isRepoBlobRef(res.data.subject)) expect(res.data.subject.did).toEqual(blobSubject.did) + assert(isRepoBlobRef(res.data.subject)) expect(res.data.subject.cid).toEqual(blobSubject.cid) expect(res.data.takedown?.applied).toBe(true) expect(res.data.takedown?.ref).toBe('test-blob') diff --git a/packages/pds/tests/moderator-auth.test.ts b/packages/pds/tests/moderator-auth.test.ts index a4cb7e60c2e..35ac5d57b7c 100644 --- a/packages/pds/tests/moderator-auth.test.ts +++ b/packages/pds/tests/moderator-auth.test.ts @@ -1,10 +1,12 @@ +import assert from 'node:assert' import * as plc from '@did-plc/lib' import { AtpAgent } from '@atproto/api' import { Keypair, Secp256k1Keypair } from '@atproto/crypto' import { SeedClient, TestNetworkNoAppView } from '@atproto/dev-env' import { createServiceAuthHeaders } from '@atproto/xrpc-server' import { ids } from '../src/lexicon/lexicons' -import { RepoRef } from '../src/lexicon/types/com/atproto/admin/defs' +import { RepoRef, isRepoRef } from '../src/lexicon/types/com/atproto/admin/defs' +import { $Typed } from '../src/lexicon/util' import usersSeed from './seeds/users' describe('moderator auth', () => { @@ -12,7 +14,7 @@ describe('moderator auth', () => { let agent: AtpAgent let sc: SeedClient - let repoSubject: RepoRef + let repoSubject: $Typed let modServiceDid: string let altModDid: string @@ -103,6 +105,7 @@ describe('moderator auth', () => { }, getHeaders, ) + assert(isRepoRef(res.data.subject)) expect(res.data.subject.did).toBe(repoSubject.did) expect(res.data.takedown?.applied).toBe(true) }) diff --git a/packages/pds/tests/preferences.test.ts b/packages/pds/tests/preferences.test.ts index eedd3fe1e7d..ef46674f125 100644 --- a/packages/pds/tests/preferences.test.ts +++ b/packages/pds/tests/preferences.test.ts @@ -163,6 +163,7 @@ describe('user preferences', () => { { $type: 'app.bsky.actor.defs#adultContentPref', enabled: false }, { $type: 'com.atproto.server.defs#unknown', + // @ts-expect-error un-spec'ed prop hello: 'world', }, ], @@ -179,6 +180,7 @@ describe('user preferences', () => { { preferences: [ { $type: 'app.bsky.actor.defs#adultContentPref', enabled: false }, + // @ts-expect-error this is what we are testing ! { label: 'dogs', visibility: 'warn', diff --git a/packages/pds/tests/proxied/__snapshots__/admin.test.ts.snap b/packages/pds/tests/proxied/__snapshots__/admin.test.ts.snap index 9edd80cd413..67049be05b1 100644 --- a/packages/pds/tests/proxied/__snapshots__/admin.test.ts.snap +++ b/packages/pds/tests/proxied/__snapshots__/admin.test.ts.snap @@ -136,7 +136,7 @@ Object { }, "id": 2, "subject": Object { - "$type": "com.atproto.admin.defs#repoView", + "$type": "tools.ozone.moderation.defs#repoView", "did": "user(0)", "handle": "bob.test", "indexedAt": "1970-01-01T00:00:00.000Z", diff --git a/packages/pds/tests/proxied/admin.test.ts b/packages/pds/tests/proxied/admin.test.ts index 0a725025e12..8ef69664e2b 100644 --- a/packages/pds/tests/proxied/admin.test.ts +++ b/packages/pds/tests/proxied/admin.test.ts @@ -116,6 +116,7 @@ describe('proxies admin requests', () => { cid: post.ref.cidStr, }, createdBy: 'did:example:admin', + // @ts-expect-error reason: 'Y', }, { @@ -132,6 +133,7 @@ describe('proxies admin requests', () => { did: sc.dids.bob, }, createdBy: 'did:example:admin', + // @ts-expect-error reason: 'Y', }, { @@ -216,6 +218,7 @@ describe('proxies admin requests', () => { did: sc.dids.alice, }, createdBy: 'did:example:admin', + // @ts-expect-error reason: 'Y', createLabelVals: ['dogs'], negateLabelVals: ['cats'], @@ -247,6 +250,7 @@ describe('proxies admin requests', () => { $type: 'tools.ozone.moderation.defs#modEventReverseTakedown', }, createdBy: 'did:example:admin', + // @ts-expect-error reason: 'X', }, { @@ -279,6 +283,7 @@ describe('proxies admin requests', () => { cid: post.ref.cidStr, }, createdBy: 'did:example:admin', + // @ts-expect-error reason: 'Y', createLabelVals: ['dogs'], negateLabelVals: ['cats'], @@ -311,6 +316,7 @@ describe('proxies admin requests', () => { }, event: { $type: 'tools.ozone.moderation.defs#modEventReverseTakedown' }, createdBy: 'did:example:admin', + // @ts-expect-error reason: 'X', }, { diff --git a/packages/pds/tests/proxied/read-after-write.test.ts b/packages/pds/tests/proxied/read-after-write.test.ts index f344d85f443..0bddb08b439 100644 --- a/packages/pds/tests/proxied/read-after-write.test.ts +++ b/packages/pds/tests/proxied/read-after-write.test.ts @@ -3,10 +3,13 @@ import util from 'node:util' import { request } from 'undici' import { AtpAgent } from '@atproto/api' import { RecordRef, SeedClient, TestNetwork } from '@atproto/dev-env' -import { View as ExternalEmbedView } from '../../src/lexicon/types/app/bsky/embed/external' -import { View as ImagesEmbedView } from '../../src/lexicon/types/app/bsky/embed/images' -import { View as RecordEmbedView } from '../../src/lexicon/types/app/bsky/embed/record' -import { ThreadViewPost } from '../../src/lexicon/types/app/bsky/feed/defs' +import { isView as isExternalEmbedView } from '../../src/lexicon/types/app/bsky/embed/external' +import { isView as isImagesEmbedView } from '../../src/lexicon/types/app/bsky/embed/images' +import { isView as isRecordEmbedView } from '../../src/lexicon/types/app/bsky/embed/record' +import { + ThreadViewPost, + isThreadViewPost, +} from '../../src/lexicon/types/app/bsky/feed/defs' import basicSeed from '../seeds/basic' describe('proxy read after write', () => { @@ -202,11 +205,17 @@ describe('proxy read after write', () => { { uri: sc.posts[alice][2].ref.uriStr }, { headers: { ...sc.getHeaders(alice) } }, ) - const replies = res.data.thread.replies as ThreadViewPost[] + assert(isThreadViewPost(res.data.thread)) + // @ts-ignore "pnpm verify:types" fails though VSCode doesn't complain + assert(res.data.thread.replies, 'replies is undefined') + // @ts-ignore "pnpm verify:types" fails though VSCode doesn't complain + const { replies } = res.data.thread expect(replies.length).toBe(1) + assert(isThreadViewPost(replies[0])) expect(replies[0].post.uri).toEqual(replyRes1.uri) - const imgs = replies[0].post.embed as ImagesEmbedView - expect(imgs.images[0].fullsize).toEqual( + const { embed } = replies[0].post + assert(isImagesEmbedView(embed)) + expect(embed.images[0].fullsize).toEqual( util.format( network.pds.ctx.cfg.bskyAppView.cdnUrlPattern, 'feed_fullsize', @@ -214,13 +223,14 @@ describe('proxy read after write', () => { img.image.ref.toString(), ), ) - expect(imgs.images[0].aspectRatio).toEqual({ height: 2, width: 1 }) - expect(imgs.images[0].alt).toBe('alt text') - expect(replies[0].replies?.length).toBe(1) - // @ts-ignore + expect(embed.images[0].aspectRatio).toEqual({ height: 2, width: 1 }) + expect(embed.images[0].alt).toBe('alt text') + assert(replies[0].replies, 'replies[0].replies is undefined') + expect(replies[0].replies.length).toBe(1) + assert(isThreadViewPost(replies[0].replies[0])) expect(replies[0].replies[0].post.uri).toEqual(replyRes2.uri) - // @ts-ignore - const external = replies[0].replies[0].post.embed as ExternalEmbedView + const external = replies[0].replies[0].post.embed + assert(isExternalEmbedView(external)) expect(external.external.title).toEqual('TestImage') expect(external.external.thumb).toEqual( util.format( @@ -253,10 +263,15 @@ describe('proxy read after write', () => { { uri: sc.posts[carol][0].ref.uriStr }, { headers: { ...sc.getHeaders(alice) } }, ) - const replies = res.data.thread.replies as ThreadViewPost[] + assert(isThreadViewPost(res.data.thread)) + assert(res.data.thread.replies, 'replies is undefined') + const { replies } = res.data.thread expect(replies.length).toBe(1) + assert(isThreadViewPost(replies[0])) expect(replies[0].post.uri).toEqual(replyRes.uri) - const embed = replies[0].post.embed as RecordEmbedView + const embed = replies[0].post.embed + assert(isRecordEmbedView(embed)) + assert('uri' in embed.record) // @TODO: assert based in "$type" expect(embed.record.uri).toEqual(sc.posts[alice][0].ref.uriStr) }) diff --git a/packages/pds/tests/seeds/basic.ts b/packages/pds/tests/seeds/basic.ts index 7aa6ac9942c..d5b5137c17f 100644 --- a/packages/pds/tests/seeds/basic.ts +++ b/packages/pds/tests/seeds/basic.ts @@ -61,7 +61,7 @@ export default async ( index: { byteStart: 0, byteEnd: 18 }, features: [ { - $type: `${ids.AppBskyRichtextFacet}#mention`, + $type: `${ids.AppBskyRichtextFacet}#mention` as const, did: alice, }, ], diff --git a/packages/syntax/src/tid.ts b/packages/syntax/src/tid.ts index 4a280a4e377..115d2cde5ac 100644 --- a/packages/syntax/src/tid.ts +++ b/packages/syntax/src/tid.ts @@ -1,24 +1,18 @@ +const TID_LENGTH = 13 +const TID_REGEX = /^[234567abcdefghij][234567abcdefghijklmnopqrstuvwxyz]{12}$/ + export const ensureValidTid = (tid: string): void => { - if (tid.length !== 13) { - throw new InvalidTidError('TID must be 13 characters') + if (tid.length !== TID_LENGTH) { + throw new InvalidTidError(`TID must be ${TID_LENGTH} characters`) } // simple regex to enforce most constraints via just regex and length. - if (!/^[234567abcdefghij][234567abcdefghijklmnopqrstuvwxyz]{12}$/.test(tid)) { + if (!TID_REGEX.test(tid)) { throw new InvalidTidError('TID syntax not valid (regex)') } } export const isValidTid = (tid: string): boolean => { - try { - ensureValidTid(tid) - } catch (err) { - if (err instanceof InvalidTidError) { - return false - } - throw err - } - - return true + return tid.length === TID_LENGTH && TID_REGEX.test(tid) } export class InvalidTidError extends Error {} diff --git a/tsconfig/browser.json b/tsconfig/browser.json index e60c4970688..baa8f8e16d6 100644 --- a/tsconfig/browser.json +++ b/tsconfig/browser.json @@ -2,7 +2,7 @@ "$schema": "https://json.schemastore.org/tsconfig", "extends": "./base.json", "compilerOptions": { - "lib": ["ES2022", "DOM", "DOM.Iterable", "ESNext.Disposable"], + "lib": ["ES2023", "DOM", "DOM.Iterable", "ESNext.Disposable"], "jsx": "react-jsx" } } diff --git a/tsconfig/isomorphic.json b/tsconfig/isomorphic.json index 43bff8bf979..36221a23051 100644 --- a/tsconfig/isomorphic.json +++ b/tsconfig/isomorphic.json @@ -8,7 +8,7 @@ // https://github.com/microsoft/TypeScript-DOM-lib-generator/issues/1685 // https://github.com/microsoft/TypeScript/issues/31535 // https://github.com/microsoft/TypeScript/issues/41727 - "lib": ["ES2022", "DOM", "DOM.Iterable"], + "lib": ["ES2023", "DOM", "DOM.Iterable"], "types": ["node"] } } diff --git a/tsconfig/node.json b/tsconfig/node.json index f69da239387..3e59f51fa18 100644 --- a/tsconfig/node.json +++ b/tsconfig/node.json @@ -2,7 +2,7 @@ "$schema": "https://json.schemastore.org/tsconfig", "extends": "./base.json", "compilerOptions": { - "lib": ["ES2022", "ScriptHost"], + "lib": ["ES2023", "ScriptHost"], "types": ["node"] } } diff --git a/tsconfig/nodenext.json b/tsconfig/nodenext.json index 5072557d696..c541e7be093 100644 --- a/tsconfig/nodenext.json +++ b/tsconfig/nodenext.json @@ -2,10 +2,10 @@ "$schema": "https://json.schemastore.org/tsconfig", "extends": "./base.json", "compilerOptions": { - "lib": ["ES2022", "ScriptHost"], + "lib": ["ES2023", "ScriptHost"], "types": ["node"], "module": "Node16", - "target": "ES2022", + "target": "ES2023", "moduleResolution": "Node16" } } diff --git a/tsconfig/tests.json b/tsconfig/tests.json index 07573723164..602bc4c4d76 100644 --- a/tsconfig/tests.json +++ b/tsconfig/tests.json @@ -3,7 +3,7 @@ "extends": "./node.json", "compilerOptions": { "types": ["node", "jest"], - "lib": ["ES2022", "DOM", "DOM.Iterable"], + "lib": ["ES2023", "DOM", "DOM.Iterable"], "noEmit": true } } From 8bf9a2f78999842fadb1924e2f9fdc367b502e05 Mon Sep 17 00:00:00 2001 From: Matthieu Sieben Date: Wed, 18 Dec 2024 10:39:24 +0100 Subject: [PATCH 02/20] codegen --- packages/api/src/client/index.ts | 234 +++--- packages/api/src/client/lexicons.ts | 41 +- .../src/client/types/app/bsky/actor/defs.ts | 468 +++++------ .../types/app/bsky/actor/getPreferences.ts | 11 +- .../client/types/app/bsky/actor/getProfile.ts | 10 +- .../types/app/bsky/actor/getProfiles.ts | 11 +- .../types/app/bsky/actor/getSuggestions.ts | 11 +- .../client/types/app/bsky/actor/profile.ts | 32 +- .../types/app/bsky/actor/putPreferences.ts | 11 +- .../types/app/bsky/actor/searchActors.ts | 11 +- .../app/bsky/actor/searchActorsTypeahead.ts | 11 +- .../src/client/types/app/bsky/embed/defs.ts | 24 +- .../client/types/app/bsky/embed/external.ts | 73 +- .../src/client/types/app/bsky/embed/images.ts | 71 +- .../src/client/types/app/bsky/embed/record.ts | 155 ++-- .../types/app/bsky/embed/recordWithMedia.ts | 65 +- .../src/client/types/app/bsky/embed/video.ts | 57 +- .../src/client/types/app/bsky/feed/defs.ts | 395 +++++---- .../app/bsky/feed/describeFeedGenerator.ts | 41 +- .../client/types/app/bsky/feed/generator.ts | 32 +- .../types/app/bsky/feed/getActorFeeds.ts | 11 +- .../types/app/bsky/feed/getActorLikes.ts | 11 +- .../types/app/bsky/feed/getAuthorFeed.ts | 11 +- .../src/client/types/app/bsky/feed/getFeed.ts | 11 +- .../types/app/bsky/feed/getFeedGenerator.ts | 11 +- .../types/app/bsky/feed/getFeedGenerators.ts | 11 +- .../types/app/bsky/feed/getFeedSkeleton.ts | 11 +- .../client/types/app/bsky/feed/getLikes.ts | 25 +- .../client/types/app/bsky/feed/getListFeed.ts | 11 +- .../types/app/bsky/feed/getPostThread.ts | 19 +- .../client/types/app/bsky/feed/getPosts.ts | 11 +- .../client/types/app/bsky/feed/getQuotes.ts | 11 +- .../types/app/bsky/feed/getRepostedBy.ts | 11 +- .../types/app/bsky/feed/getSuggestedFeeds.ts | 11 +- .../client/types/app/bsky/feed/getTimeline.ts | 11 +- .../src/client/types/app/bsky/feed/like.ts | 25 +- .../src/client/types/app/bsky/feed/post.ts | 99 ++- .../client/types/app/bsky/feed/postgate.ts | 42 +- .../src/client/types/app/bsky/feed/repost.ts | 26 +- .../client/types/app/bsky/feed/searchPosts.ts | 11 +- .../types/app/bsky/feed/sendInteractions.ts | 15 +- .../client/types/app/bsky/feed/threadgate.ts | 98 ++- .../src/client/types/app/bsky/graph/block.ts | 24 +- .../src/client/types/app/bsky/graph/defs.ts | 154 ++-- .../src/client/types/app/bsky/graph/follow.ts | 24 +- .../app/bsky/graph/getActorStarterPacks.ts | 11 +- .../client/types/app/bsky/graph/getBlocks.ts | 11 +- .../types/app/bsky/graph/getFollowers.ts | 11 +- .../client/types/app/bsky/graph/getFollows.ts | 11 +- .../types/app/bsky/graph/getKnownFollowers.ts | 11 +- .../client/types/app/bsky/graph/getList.ts | 11 +- .../types/app/bsky/graph/getListBlocks.ts | 11 +- .../types/app/bsky/graph/getListMutes.ts | 11 +- .../client/types/app/bsky/graph/getLists.ts | 11 +- .../client/types/app/bsky/graph/getMutes.ts | 11 +- .../types/app/bsky/graph/getRelationships.ts | 17 +- .../types/app/bsky/graph/getStarterPack.ts | 11 +- .../types/app/bsky/graph/getStarterPacks.ts | 11 +- .../bsky/graph/getSuggestedFollowsByActor.ts | 11 +- .../src/client/types/app/bsky/graph/list.ts | 34 +- .../client/types/app/bsky/graph/listblock.ts | 24 +- .../client/types/app/bsky/graph/listitem.ts | 24 +- .../client/types/app/bsky/graph/muteActor.ts | 9 +- .../types/app/bsky/graph/muteActorList.ts | 9 +- .../client/types/app/bsky/graph/muteThread.ts | 9 +- .../app/bsky/graph/searchStarterPacks.ts | 11 +- .../types/app/bsky/graph/starterpack.ts | 42 +- .../types/app/bsky/graph/unmuteActor.ts | 9 +- .../types/app/bsky/graph/unmuteActorList.ts | 9 +- .../types/app/bsky/graph/unmuteThread.ts | 9 +- .../src/client/types/app/bsky/labeler/defs.ts | 76 +- .../types/app/bsky/labeler/getServices.ts | 17 +- .../client/types/app/bsky/labeler/service.ts | 32 +- .../app/bsky/notification/getUnreadCount.ts | 9 +- .../bsky/notification/listNotifications.ts | 34 +- .../app/bsky/notification/putPreferences.ts | 9 +- .../app/bsky/notification/registerPush.ts | 9 +- .../types/app/bsky/notification/updateSeen.ts | 9 +- .../client/types/app/bsky/richtext/facet.ts | 89 +- .../client/types/app/bsky/unspecced/defs.ts | 79 +- .../types/app/bsky/unspecced/getConfig.ts | 9 +- .../unspecced/getPopularFeedGenerators.ts | 11 +- .../bsky/unspecced/getSuggestionsSkeleton.ts | 11 +- .../bsky/unspecced/getTaggedSuggestions.ts | 28 +- .../app/bsky/unspecced/getTrendingTopics.ts | 11 +- .../bsky/unspecced/searchActorsSkeleton.ts | 11 +- .../app/bsky/unspecced/searchPostsSkeleton.ts | 11 +- .../unspecced/searchStarterPacksSkeleton.ts | 11 +- .../src/client/types/app/bsky/video/defs.ts | 24 +- .../types/app/bsky/video/getJobStatus.ts | 11 +- .../types/app/bsky/video/getUploadLimits.ts | 9 +- .../types/app/bsky/video/uploadVideo.ts | 11 +- .../types/chat/bsky/actor/declaration.ts | 24 +- .../src/client/types/chat/bsky/actor/defs.ts | 28 +- .../types/chat/bsky/actor/deleteAccount.ts | 12 +- .../chat/bsky/actor/exportAccountData.ts | 8 +- .../src/client/types/chat/bsky/convo/defs.ts | 194 ++--- .../chat/bsky/convo/deleteMessageForSelf.ts | 11 +- .../client/types/chat/bsky/convo/getConvo.ts | 11 +- .../chat/bsky/convo/getConvoForMembers.ts | 11 +- .../client/types/chat/bsky/convo/getLog.ts | 21 +- .../types/chat/bsky/convo/getMessages.ts | 17 +- .../types/chat/bsky/convo/leaveConvo.ts | 10 +- .../types/chat/bsky/convo/listConvos.ts | 11 +- .../client/types/chat/bsky/convo/muteConvo.ts | 12 +- .../types/chat/bsky/convo/sendMessage.ts | 11 +- .../types/chat/bsky/convo/sendMessageBatch.ts | 28 +- .../types/chat/bsky/convo/unmuteConvo.ts | 12 +- .../types/chat/bsky/convo/updateRead.ts | 12 +- .../chat/bsky/moderation/getActorMetadata.ts | 25 +- .../chat/bsky/moderation/getMessageContext.ts | 17 +- .../chat/bsky/moderation/updateActorAccess.ts | 9 +- .../client/types/com/atproto/admin/defs.ts | 92 +-- .../types/com/atproto/admin/deleteAccount.ts | 9 +- .../atproto/admin/disableAccountInvites.ts | 9 +- .../com/atproto/admin/disableInviteCodes.ts | 9 +- .../com/atproto/admin/enableAccountInvites.ts | 9 +- .../types/com/atproto/admin/getAccountInfo.ts | 10 +- .../com/atproto/admin/getAccountInfos.ts | 11 +- .../types/com/atproto/admin/getInviteCodes.ts | 11 +- .../com/atproto/admin/getSubjectStatus.ts | 21 +- .../types/com/atproto/admin/searchAccounts.ts | 11 +- .../types/com/atproto/admin/sendEmail.ts | 10 +- .../com/atproto/admin/updateAccountEmail.ts | 9 +- .../com/atproto/admin/updateAccountHandle.ts | 9 +- .../atproto/admin/updateAccountPassword.ts | 9 +- .../com/atproto/admin/updateSubjectStatus.ts | 30 +- .../identity/getRecommendedDidCredentials.ts | 13 +- .../identity/requestPlcOperationSignature.ts | 8 +- .../com/atproto/identity/resolveHandle.ts | 9 +- .../com/atproto/identity/signPlcOperation.ts | 16 +- .../atproto/identity/submitPlcOperation.ts | 11 +- .../com/atproto/identity/updateHandle.ts | 9 +- .../client/types/com/atproto/label/defs.ts | 95 +-- .../types/com/atproto/label/queryLabels.ts | 11 +- .../com/atproto/label/subscribeLabels.ts | 42 +- .../types/com/atproto/lexicon/schema.ts | 24 +- .../com/atproto/moderation/createReport.ts | 28 +- .../types/com/atproto/moderation/defs.ts | 22 +- .../types/com/atproto/repo/applyWrites.ts | 120 ++- .../types/com/atproto/repo/createRecord.ts | 14 +- .../src/client/types/com/atproto/repo/defs.ts | 24 +- .../types/com/atproto/repo/deleteRecord.ts | 12 +- .../types/com/atproto/repo/describeRepo.ts | 11 +- .../types/com/atproto/repo/getRecord.ts | 11 +- .../types/com/atproto/repo/importRepo.ts | 8 +- .../com/atproto/repo/listMissingBlobs.ts | 25 +- .../types/com/atproto/repo/listRecords.ts | 27 +- .../types/com/atproto/repo/putRecord.ts | 14 +- .../types/com/atproto/repo/strongRef.ts | 25 +- .../types/com/atproto/repo/uploadBlob.ts | 9 +- .../com/atproto/server/activateAccount.ts | 8 +- .../com/atproto/server/checkAccountStatus.ts | 9 +- .../types/com/atproto/server/confirmEmail.ts | 9 +- .../types/com/atproto/server/createAccount.ts | 14 +- .../com/atproto/server/createAppPassword.ts | 28 +- .../com/atproto/server/createInviteCode.ts | 10 +- .../com/atproto/server/createInviteCodes.ts | 29 +- .../types/com/atproto/server/createSession.ts | 12 +- .../com/atproto/server/deactivateAccount.ts | 9 +- .../client/types/com/atproto/server/defs.ts | 40 +- .../types/com/atproto/server/deleteAccount.ts | 9 +- .../types/com/atproto/server/deleteSession.ts | 8 +- .../com/atproto/server/describeServer.ts | 41 +- .../atproto/server/getAccountInviteCodes.ts | 11 +- .../com/atproto/server/getServiceAuth.ts | 9 +- .../types/com/atproto/server/getSession.ts | 11 +- .../com/atproto/server/listAppPasswords.ts | 25 +- .../com/atproto/server/refreshSession.ts | 11 +- .../atproto/server/requestAccountDelete.ts | 8 +- .../server/requestEmailConfirmation.ts | 8 +- .../com/atproto/server/requestEmailUpdate.ts | 9 +- .../atproto/server/requestPasswordReset.ts | 9 +- .../com/atproto/server/reserveSigningKey.ts | 10 +- .../types/com/atproto/server/resetPassword.ts | 9 +- .../com/atproto/server/revokeAppPassword.ts | 9 +- .../types/com/atproto/server/updateEmail.ts | 9 +- .../client/types/com/atproto/sync/getBlob.ts | 8 +- .../types/com/atproto/sync/getBlocks.ts | 8 +- .../types/com/atproto/sync/getCheckout.ts | 8 +- .../client/types/com/atproto/sync/getHead.ts | 9 +- .../types/com/atproto/sync/getLatestCommit.ts | 9 +- .../types/com/atproto/sync/getRecord.ts | 8 +- .../client/types/com/atproto/sync/getRepo.ts | 8 +- .../types/com/atproto/sync/getRepoStatus.ts | 9 +- .../types/com/atproto/sync/listBlobs.ts | 9 +- .../types/com/atproto/sync/listRepos.ts | 25 +- .../types/com/atproto/sync/notifyOfUpdate.ts | 9 +- .../types/com/atproto/sync/requestCrawl.ts | 9 +- .../types/com/atproto/sync/subscribeRepos.ts | 136 ++-- .../com/atproto/temp/addReservedHandle.ts | 13 +- .../com/atproto/temp/checkSignupQueue.ts | 9 +- .../types/com/atproto/temp/fetchLabels.ts | 11 +- .../atproto/temp/requestPhoneVerification.ts | 9 +- .../ozone/communication/createTemplate.ts | 11 +- .../types/tools/ozone/communication/defs.ts | 24 +- .../ozone/communication/deleteTemplate.ts | 9 +- .../ozone/communication/listTemplates.ts | 11 +- .../ozone/communication/updateTemplate.ts | 11 +- .../types/tools/ozone/moderation/defs.ts | 765 ++++++++---------- .../types/tools/ozone/moderation/emitEvent.ts | 59 +- .../types/tools/ozone/moderation/getEvent.ts | 10 +- .../types/tools/ozone/moderation/getRecord.ts | 10 +- .../tools/ozone/moderation/getRecords.ts | 17 +- .../types/tools/ozone/moderation/getRepo.ts | 10 +- .../types/tools/ozone/moderation/getRepos.ts | 17 +- .../tools/ozone/moderation/queryEvents.ts | 11 +- .../tools/ozone/moderation/queryStatuses.ts | 11 +- .../tools/ozone/moderation/searchRepos.ts | 11 +- .../types/tools/ozone/server/getConfig.ts | 41 +- .../client/types/tools/ozone/set/addValues.ts | 9 +- .../src/client/types/tools/ozone/set/defs.ts | 38 +- .../client/types/tools/ozone/set/deleteSet.ts | 13 +- .../types/tools/ozone/set/deleteValues.ts | 9 +- .../client/types/tools/ozone/set/getValues.ts | 11 +- .../client/types/tools/ozone/set/querySets.ts | 11 +- .../client/types/tools/ozone/set/upsertSet.ts | 10 +- .../client/types/tools/ozone/setting/defs.ts | 26 +- .../types/tools/ozone/setting/listOptions.ts | 11 +- .../tools/ozone/setting/removeOptions.ts | 13 +- .../types/tools/ozone/setting/upsertOption.ts | 14 +- .../types/tools/ozone/signature/defs.ts | 24 +- .../tools/ozone/signature/findCorrelation.ts | 11 +- .../ozone/signature/findRelatedAccounts.ts | 32 +- .../tools/ozone/signature/searchAccounts.ts | 11 +- .../types/tools/ozone/team/addMember.ts | 11 +- .../src/client/types/tools/ozone/team/defs.ts | 32 +- .../types/tools/ozone/team/deleteMember.ts | 9 +- .../types/tools/ozone/team/listMembers.ts | 11 +- .../types/tools/ozone/team/updateMember.ts | 11 +- packages/api/src/client/util.ts | 85 +- packages/bsky/src/lexicon/lexicons.ts | 41 +- .../src/lexicon/types/app/bsky/actor/defs.ts | 468 +++++------ .../types/app/bsky/actor/getPreferences.ts | 11 +- .../types/app/bsky/actor/getProfile.ts | 10 +- .../types/app/bsky/actor/getProfiles.ts | 11 +- .../types/app/bsky/actor/getSuggestions.ts | 11 +- .../lexicon/types/app/bsky/actor/profile.ts | 32 +- .../types/app/bsky/actor/putPreferences.ts | 11 +- .../types/app/bsky/actor/searchActors.ts | 11 +- .../app/bsky/actor/searchActorsTypeahead.ts | 11 +- .../src/lexicon/types/app/bsky/embed/defs.ts | 24 +- .../lexicon/types/app/bsky/embed/external.ts | 73 +- .../lexicon/types/app/bsky/embed/images.ts | 71 +- .../lexicon/types/app/bsky/embed/record.ts | 155 ++-- .../types/app/bsky/embed/recordWithMedia.ts | 65 +- .../src/lexicon/types/app/bsky/embed/video.ts | 57 +- .../src/lexicon/types/app/bsky/feed/defs.ts | 395 +++++---- .../app/bsky/feed/describeFeedGenerator.ts | 41 +- .../lexicon/types/app/bsky/feed/generator.ts | 32 +- .../types/app/bsky/feed/getActorFeeds.ts | 11 +- .../types/app/bsky/feed/getActorLikes.ts | 11 +- .../types/app/bsky/feed/getAuthorFeed.ts | 11 +- .../lexicon/types/app/bsky/feed/getFeed.ts | 11 +- .../types/app/bsky/feed/getFeedGenerator.ts | 11 +- .../types/app/bsky/feed/getFeedGenerators.ts | 11 +- .../types/app/bsky/feed/getFeedSkeleton.ts | 11 +- .../lexicon/types/app/bsky/feed/getLikes.ts | 25 +- .../types/app/bsky/feed/getListFeed.ts | 11 +- .../types/app/bsky/feed/getPostThread.ts | 19 +- .../lexicon/types/app/bsky/feed/getPosts.ts | 11 +- .../lexicon/types/app/bsky/feed/getQuotes.ts | 11 +- .../types/app/bsky/feed/getRepostedBy.ts | 11 +- .../types/app/bsky/feed/getSuggestedFeeds.ts | 11 +- .../types/app/bsky/feed/getTimeline.ts | 11 +- .../src/lexicon/types/app/bsky/feed/like.ts | 25 +- .../src/lexicon/types/app/bsky/feed/post.ts | 99 ++- .../lexicon/types/app/bsky/feed/postgate.ts | 42 +- .../src/lexicon/types/app/bsky/feed/repost.ts | 26 +- .../types/app/bsky/feed/searchPosts.ts | 11 +- .../types/app/bsky/feed/sendInteractions.ts | 15 +- .../lexicon/types/app/bsky/feed/threadgate.ts | 98 ++- .../src/lexicon/types/app/bsky/graph/block.ts | 24 +- .../src/lexicon/types/app/bsky/graph/defs.ts | 154 ++-- .../lexicon/types/app/bsky/graph/follow.ts | 24 +- .../app/bsky/graph/getActorStarterPacks.ts | 11 +- .../lexicon/types/app/bsky/graph/getBlocks.ts | 11 +- .../types/app/bsky/graph/getFollowers.ts | 11 +- .../types/app/bsky/graph/getFollows.ts | 11 +- .../types/app/bsky/graph/getKnownFollowers.ts | 11 +- .../lexicon/types/app/bsky/graph/getList.ts | 11 +- .../types/app/bsky/graph/getListBlocks.ts | 11 +- .../types/app/bsky/graph/getListMutes.ts | 11 +- .../lexicon/types/app/bsky/graph/getLists.ts | 11 +- .../lexicon/types/app/bsky/graph/getMutes.ts | 11 +- .../types/app/bsky/graph/getRelationships.ts | 17 +- .../types/app/bsky/graph/getStarterPack.ts | 11 +- .../types/app/bsky/graph/getStarterPacks.ts | 11 +- .../bsky/graph/getSuggestedFollowsByActor.ts | 11 +- .../src/lexicon/types/app/bsky/graph/list.ts | 34 +- .../lexicon/types/app/bsky/graph/listblock.ts | 24 +- .../lexicon/types/app/bsky/graph/listitem.ts | 24 +- .../lexicon/types/app/bsky/graph/muteActor.ts | 9 +- .../types/app/bsky/graph/muteActorList.ts | 9 +- .../types/app/bsky/graph/muteThread.ts | 9 +- .../app/bsky/graph/searchStarterPacks.ts | 11 +- .../types/app/bsky/graph/starterpack.ts | 42 +- .../types/app/bsky/graph/unmuteActor.ts | 9 +- .../types/app/bsky/graph/unmuteActorList.ts | 9 +- .../types/app/bsky/graph/unmuteThread.ts | 9 +- .../lexicon/types/app/bsky/labeler/defs.ts | 76 +- .../types/app/bsky/labeler/getServices.ts | 17 +- .../lexicon/types/app/bsky/labeler/service.ts | 32 +- .../app/bsky/notification/getUnreadCount.ts | 9 +- .../bsky/notification/listNotifications.ts | 34 +- .../app/bsky/notification/putPreferences.ts | 9 +- .../app/bsky/notification/registerPush.ts | 9 +- .../types/app/bsky/notification/updateSeen.ts | 9 +- .../lexicon/types/app/bsky/richtext/facet.ts | 89 +- .../lexicon/types/app/bsky/unspecced/defs.ts | 79 +- .../types/app/bsky/unspecced/getConfig.ts | 9 +- .../unspecced/getPopularFeedGenerators.ts | 11 +- .../bsky/unspecced/getSuggestionsSkeleton.ts | 11 +- .../bsky/unspecced/getTaggedSuggestions.ts | 28 +- .../app/bsky/unspecced/getTrendingTopics.ts | 11 +- .../bsky/unspecced/searchActorsSkeleton.ts | 11 +- .../app/bsky/unspecced/searchPostsSkeleton.ts | 11 +- .../unspecced/searchStarterPacksSkeleton.ts | 11 +- .../src/lexicon/types/app/bsky/video/defs.ts | 24 +- .../types/app/bsky/video/getJobStatus.ts | 11 +- .../types/app/bsky/video/getUploadLimits.ts | 9 +- .../types/app/bsky/video/uploadVideo.ts | 11 +- .../types/chat/bsky/actor/declaration.ts | 24 +- .../src/lexicon/types/chat/bsky/actor/defs.ts | 28 +- .../types/chat/bsky/actor/deleteAccount.ts | 12 +- .../chat/bsky/actor/exportAccountData.ts | 8 +- .../src/lexicon/types/chat/bsky/convo/defs.ts | 194 ++--- .../chat/bsky/convo/deleteMessageForSelf.ts | 11 +- .../lexicon/types/chat/bsky/convo/getConvo.ts | 11 +- .../chat/bsky/convo/getConvoForMembers.ts | 11 +- .../lexicon/types/chat/bsky/convo/getLog.ts | 21 +- .../types/chat/bsky/convo/getMessages.ts | 17 +- .../types/chat/bsky/convo/leaveConvo.ts | 10 +- .../types/chat/bsky/convo/listConvos.ts | 11 +- .../types/chat/bsky/convo/muteConvo.ts | 12 +- .../types/chat/bsky/convo/sendMessage.ts | 11 +- .../types/chat/bsky/convo/sendMessageBatch.ts | 28 +- .../types/chat/bsky/convo/unmuteConvo.ts | 12 +- .../types/chat/bsky/convo/updateRead.ts | 12 +- .../chat/bsky/moderation/getActorMetadata.ts | 25 +- .../chat/bsky/moderation/getMessageContext.ts | 17 +- .../chat/bsky/moderation/updateActorAccess.ts | 9 +- .../lexicon/types/com/atproto/admin/defs.ts | 92 +-- .../types/com/atproto/admin/deleteAccount.ts | 9 +- .../atproto/admin/disableAccountInvites.ts | 9 +- .../com/atproto/admin/disableInviteCodes.ts | 9 +- .../com/atproto/admin/enableAccountInvites.ts | 9 +- .../types/com/atproto/admin/getAccountInfo.ts | 10 +- .../com/atproto/admin/getAccountInfos.ts | 11 +- .../types/com/atproto/admin/getInviteCodes.ts | 11 +- .../com/atproto/admin/getSubjectStatus.ts | 21 +- .../types/com/atproto/admin/searchAccounts.ts | 11 +- .../types/com/atproto/admin/sendEmail.ts | 10 +- .../com/atproto/admin/updateAccountEmail.ts | 9 +- .../com/atproto/admin/updateAccountHandle.ts | 9 +- .../atproto/admin/updateAccountPassword.ts | 9 +- .../com/atproto/admin/updateSubjectStatus.ts | 30 +- .../identity/getRecommendedDidCredentials.ts | 13 +- .../identity/requestPlcOperationSignature.ts | 8 +- .../com/atproto/identity/resolveHandle.ts | 9 +- .../com/atproto/identity/signPlcOperation.ts | 16 +- .../atproto/identity/submitPlcOperation.ts | 11 +- .../com/atproto/identity/updateHandle.ts | 9 +- .../lexicon/types/com/atproto/label/defs.ts | 95 +-- .../types/com/atproto/label/queryLabels.ts | 11 +- .../com/atproto/label/subscribeLabels.ts | 47 +- .../types/com/atproto/lexicon/schema.ts | 24 +- .../com/atproto/moderation/createReport.ts | 28 +- .../types/com/atproto/moderation/defs.ts | 22 +- .../types/com/atproto/repo/applyWrites.ts | 120 ++- .../types/com/atproto/repo/createRecord.ts | 14 +- .../lexicon/types/com/atproto/repo/defs.ts | 24 +- .../types/com/atproto/repo/deleteRecord.ts | 12 +- .../types/com/atproto/repo/describeRepo.ts | 11 +- .../types/com/atproto/repo/getRecord.ts | 11 +- .../types/com/atproto/repo/importRepo.ts | 8 +- .../com/atproto/repo/listMissingBlobs.ts | 25 +- .../types/com/atproto/repo/listRecords.ts | 27 +- .../types/com/atproto/repo/putRecord.ts | 14 +- .../types/com/atproto/repo/strongRef.ts | 25 +- .../types/com/atproto/repo/uploadBlob.ts | 9 +- .../com/atproto/server/activateAccount.ts | 8 +- .../com/atproto/server/checkAccountStatus.ts | 9 +- .../types/com/atproto/server/confirmEmail.ts | 9 +- .../types/com/atproto/server/createAccount.ts | 14 +- .../com/atproto/server/createAppPassword.ts | 28 +- .../com/atproto/server/createInviteCode.ts | 10 +- .../com/atproto/server/createInviteCodes.ts | 29 +- .../types/com/atproto/server/createSession.ts | 12 +- .../com/atproto/server/deactivateAccount.ts | 9 +- .../lexicon/types/com/atproto/server/defs.ts | 40 +- .../types/com/atproto/server/deleteAccount.ts | 9 +- .../types/com/atproto/server/deleteSession.ts | 8 +- .../com/atproto/server/describeServer.ts | 41 +- .../atproto/server/getAccountInviteCodes.ts | 11 +- .../com/atproto/server/getServiceAuth.ts | 9 +- .../types/com/atproto/server/getSession.ts | 11 +- .../com/atproto/server/listAppPasswords.ts | 25 +- .../com/atproto/server/refreshSession.ts | 11 +- .../atproto/server/requestAccountDelete.ts | 8 +- .../server/requestEmailConfirmation.ts | 8 +- .../com/atproto/server/requestEmailUpdate.ts | 9 +- .../atproto/server/requestPasswordReset.ts | 9 +- .../com/atproto/server/reserveSigningKey.ts | 10 +- .../types/com/atproto/server/resetPassword.ts | 9 +- .../com/atproto/server/revokeAppPassword.ts | 9 +- .../types/com/atproto/server/updateEmail.ts | 9 +- .../lexicon/types/com/atproto/sync/getBlob.ts | 8 +- .../types/com/atproto/sync/getBlocks.ts | 8 +- .../types/com/atproto/sync/getCheckout.ts | 8 +- .../lexicon/types/com/atproto/sync/getHead.ts | 9 +- .../types/com/atproto/sync/getLatestCommit.ts | 9 +- .../types/com/atproto/sync/getRecord.ts | 8 +- .../lexicon/types/com/atproto/sync/getRepo.ts | 8 +- .../types/com/atproto/sync/getRepoStatus.ts | 9 +- .../types/com/atproto/sync/listBlobs.ts | 9 +- .../types/com/atproto/sync/listRepos.ts | 25 +- .../types/com/atproto/sync/notifyOfUpdate.ts | 9 +- .../types/com/atproto/sync/requestCrawl.ts | 9 +- .../types/com/atproto/sync/subscribeRepos.ts | 152 ++-- .../com/atproto/temp/addReservedHandle.ts | 13 +- .../com/atproto/temp/checkSignupQueue.ts | 9 +- .../types/com/atproto/temp/fetchLabels.ts | 11 +- .../atproto/temp/requestPhoneVerification.ts | 9 +- packages/bsky/src/lexicon/util.ts | 85 +- packages/ozone/src/lexicon/lexicons.ts | 41 +- .../src/lexicon/types/app/bsky/actor/defs.ts | 468 +++++------ .../types/app/bsky/actor/getPreferences.ts | 11 +- .../types/app/bsky/actor/getProfile.ts | 10 +- .../types/app/bsky/actor/getProfiles.ts | 11 +- .../types/app/bsky/actor/getSuggestions.ts | 11 +- .../lexicon/types/app/bsky/actor/profile.ts | 32 +- .../types/app/bsky/actor/putPreferences.ts | 11 +- .../types/app/bsky/actor/searchActors.ts | 11 +- .../app/bsky/actor/searchActorsTypeahead.ts | 11 +- .../src/lexicon/types/app/bsky/embed/defs.ts | 24 +- .../lexicon/types/app/bsky/embed/external.ts | 73 +- .../lexicon/types/app/bsky/embed/images.ts | 71 +- .../lexicon/types/app/bsky/embed/record.ts | 155 ++-- .../types/app/bsky/embed/recordWithMedia.ts | 65 +- .../src/lexicon/types/app/bsky/embed/video.ts | 57 +- .../src/lexicon/types/app/bsky/feed/defs.ts | 395 +++++---- .../app/bsky/feed/describeFeedGenerator.ts | 41 +- .../lexicon/types/app/bsky/feed/generator.ts | 32 +- .../types/app/bsky/feed/getActorFeeds.ts | 11 +- .../types/app/bsky/feed/getActorLikes.ts | 11 +- .../types/app/bsky/feed/getAuthorFeed.ts | 11 +- .../lexicon/types/app/bsky/feed/getFeed.ts | 11 +- .../types/app/bsky/feed/getFeedGenerator.ts | 11 +- .../types/app/bsky/feed/getFeedGenerators.ts | 11 +- .../types/app/bsky/feed/getFeedSkeleton.ts | 11 +- .../lexicon/types/app/bsky/feed/getLikes.ts | 25 +- .../types/app/bsky/feed/getListFeed.ts | 11 +- .../types/app/bsky/feed/getPostThread.ts | 19 +- .../lexicon/types/app/bsky/feed/getPosts.ts | 11 +- .../lexicon/types/app/bsky/feed/getQuotes.ts | 11 +- .../types/app/bsky/feed/getRepostedBy.ts | 11 +- .../types/app/bsky/feed/getSuggestedFeeds.ts | 11 +- .../types/app/bsky/feed/getTimeline.ts | 11 +- .../src/lexicon/types/app/bsky/feed/like.ts | 25 +- .../src/lexicon/types/app/bsky/feed/post.ts | 99 ++- .../lexicon/types/app/bsky/feed/postgate.ts | 42 +- .../src/lexicon/types/app/bsky/feed/repost.ts | 26 +- .../types/app/bsky/feed/searchPosts.ts | 11 +- .../types/app/bsky/feed/sendInteractions.ts | 15 +- .../lexicon/types/app/bsky/feed/threadgate.ts | 98 ++- .../src/lexicon/types/app/bsky/graph/block.ts | 24 +- .../src/lexicon/types/app/bsky/graph/defs.ts | 154 ++-- .../lexicon/types/app/bsky/graph/follow.ts | 24 +- .../app/bsky/graph/getActorStarterPacks.ts | 11 +- .../lexicon/types/app/bsky/graph/getBlocks.ts | 11 +- .../types/app/bsky/graph/getFollowers.ts | 11 +- .../types/app/bsky/graph/getFollows.ts | 11 +- .../types/app/bsky/graph/getKnownFollowers.ts | 11 +- .../lexicon/types/app/bsky/graph/getList.ts | 11 +- .../types/app/bsky/graph/getListBlocks.ts | 11 +- .../types/app/bsky/graph/getListMutes.ts | 11 +- .../lexicon/types/app/bsky/graph/getLists.ts | 11 +- .../lexicon/types/app/bsky/graph/getMutes.ts | 11 +- .../types/app/bsky/graph/getRelationships.ts | 17 +- .../types/app/bsky/graph/getStarterPack.ts | 11 +- .../types/app/bsky/graph/getStarterPacks.ts | 11 +- .../bsky/graph/getSuggestedFollowsByActor.ts | 11 +- .../src/lexicon/types/app/bsky/graph/list.ts | 34 +- .../lexicon/types/app/bsky/graph/listblock.ts | 24 +- .../lexicon/types/app/bsky/graph/listitem.ts | 24 +- .../lexicon/types/app/bsky/graph/muteActor.ts | 9 +- .../types/app/bsky/graph/muteActorList.ts | 9 +- .../types/app/bsky/graph/muteThread.ts | 9 +- .../app/bsky/graph/searchStarterPacks.ts | 11 +- .../types/app/bsky/graph/starterpack.ts | 42 +- .../types/app/bsky/graph/unmuteActor.ts | 9 +- .../types/app/bsky/graph/unmuteActorList.ts | 9 +- .../types/app/bsky/graph/unmuteThread.ts | 9 +- .../lexicon/types/app/bsky/labeler/defs.ts | 76 +- .../types/app/bsky/labeler/getServices.ts | 17 +- .../lexicon/types/app/bsky/labeler/service.ts | 32 +- .../app/bsky/notification/getUnreadCount.ts | 9 +- .../bsky/notification/listNotifications.ts | 34 +- .../app/bsky/notification/putPreferences.ts | 9 +- .../app/bsky/notification/registerPush.ts | 9 +- .../types/app/bsky/notification/updateSeen.ts | 9 +- .../lexicon/types/app/bsky/richtext/facet.ts | 89 +- .../lexicon/types/app/bsky/unspecced/defs.ts | 79 +- .../types/app/bsky/unspecced/getConfig.ts | 9 +- .../unspecced/getPopularFeedGenerators.ts | 11 +- .../bsky/unspecced/getSuggestionsSkeleton.ts | 11 +- .../bsky/unspecced/getTaggedSuggestions.ts | 28 +- .../app/bsky/unspecced/getTrendingTopics.ts | 11 +- .../bsky/unspecced/searchActorsSkeleton.ts | 11 +- .../app/bsky/unspecced/searchPostsSkeleton.ts | 11 +- .../unspecced/searchStarterPacksSkeleton.ts | 11 +- .../src/lexicon/types/app/bsky/video/defs.ts | 24 +- .../types/app/bsky/video/getJobStatus.ts | 11 +- .../types/app/bsky/video/getUploadLimits.ts | 9 +- .../types/app/bsky/video/uploadVideo.ts | 11 +- .../types/chat/bsky/actor/declaration.ts | 24 +- .../src/lexicon/types/chat/bsky/actor/defs.ts | 28 +- .../types/chat/bsky/actor/deleteAccount.ts | 12 +- .../chat/bsky/actor/exportAccountData.ts | 8 +- .../src/lexicon/types/chat/bsky/convo/defs.ts | 194 ++--- .../chat/bsky/convo/deleteMessageForSelf.ts | 11 +- .../lexicon/types/chat/bsky/convo/getConvo.ts | 11 +- .../chat/bsky/convo/getConvoForMembers.ts | 11 +- .../lexicon/types/chat/bsky/convo/getLog.ts | 21 +- .../types/chat/bsky/convo/getMessages.ts | 17 +- .../types/chat/bsky/convo/leaveConvo.ts | 10 +- .../types/chat/bsky/convo/listConvos.ts | 11 +- .../types/chat/bsky/convo/muteConvo.ts | 12 +- .../types/chat/bsky/convo/sendMessage.ts | 11 +- .../types/chat/bsky/convo/sendMessageBatch.ts | 28 +- .../types/chat/bsky/convo/unmuteConvo.ts | 12 +- .../types/chat/bsky/convo/updateRead.ts | 12 +- .../chat/bsky/moderation/getActorMetadata.ts | 25 +- .../chat/bsky/moderation/getMessageContext.ts | 17 +- .../chat/bsky/moderation/updateActorAccess.ts | 9 +- .../lexicon/types/com/atproto/admin/defs.ts | 92 +-- .../types/com/atproto/admin/deleteAccount.ts | 9 +- .../atproto/admin/disableAccountInvites.ts | 9 +- .../com/atproto/admin/disableInviteCodes.ts | 9 +- .../com/atproto/admin/enableAccountInvites.ts | 9 +- .../types/com/atproto/admin/getAccountInfo.ts | 10 +- .../com/atproto/admin/getAccountInfos.ts | 11 +- .../types/com/atproto/admin/getInviteCodes.ts | 11 +- .../com/atproto/admin/getSubjectStatus.ts | 21 +- .../types/com/atproto/admin/searchAccounts.ts | 11 +- .../types/com/atproto/admin/sendEmail.ts | 10 +- .../com/atproto/admin/updateAccountEmail.ts | 9 +- .../com/atproto/admin/updateAccountHandle.ts | 9 +- .../atproto/admin/updateAccountPassword.ts | 9 +- .../com/atproto/admin/updateSubjectStatus.ts | 30 +- .../identity/getRecommendedDidCredentials.ts | 13 +- .../identity/requestPlcOperationSignature.ts | 8 +- .../com/atproto/identity/resolveHandle.ts | 9 +- .../com/atproto/identity/signPlcOperation.ts | 16 +- .../atproto/identity/submitPlcOperation.ts | 11 +- .../com/atproto/identity/updateHandle.ts | 9 +- .../lexicon/types/com/atproto/label/defs.ts | 95 +-- .../types/com/atproto/label/queryLabels.ts | 11 +- .../com/atproto/label/subscribeLabels.ts | 47 +- .../types/com/atproto/lexicon/schema.ts | 24 +- .../com/atproto/moderation/createReport.ts | 28 +- .../types/com/atproto/moderation/defs.ts | 22 +- .../types/com/atproto/repo/applyWrites.ts | 120 ++- .../types/com/atproto/repo/createRecord.ts | 14 +- .../lexicon/types/com/atproto/repo/defs.ts | 24 +- .../types/com/atproto/repo/deleteRecord.ts | 12 +- .../types/com/atproto/repo/describeRepo.ts | 11 +- .../types/com/atproto/repo/getRecord.ts | 11 +- .../types/com/atproto/repo/importRepo.ts | 8 +- .../com/atproto/repo/listMissingBlobs.ts | 25 +- .../types/com/atproto/repo/listRecords.ts | 27 +- .../types/com/atproto/repo/putRecord.ts | 14 +- .../types/com/atproto/repo/strongRef.ts | 25 +- .../types/com/atproto/repo/uploadBlob.ts | 9 +- .../com/atproto/server/activateAccount.ts | 8 +- .../com/atproto/server/checkAccountStatus.ts | 9 +- .../types/com/atproto/server/confirmEmail.ts | 9 +- .../types/com/atproto/server/createAccount.ts | 14 +- .../com/atproto/server/createAppPassword.ts | 28 +- .../com/atproto/server/createInviteCode.ts | 10 +- .../com/atproto/server/createInviteCodes.ts | 29 +- .../types/com/atproto/server/createSession.ts | 12 +- .../com/atproto/server/deactivateAccount.ts | 9 +- .../lexicon/types/com/atproto/server/defs.ts | 40 +- .../types/com/atproto/server/deleteAccount.ts | 9 +- .../types/com/atproto/server/deleteSession.ts | 8 +- .../com/atproto/server/describeServer.ts | 41 +- .../atproto/server/getAccountInviteCodes.ts | 11 +- .../com/atproto/server/getServiceAuth.ts | 9 +- .../types/com/atproto/server/getSession.ts | 11 +- .../com/atproto/server/listAppPasswords.ts | 25 +- .../com/atproto/server/refreshSession.ts | 11 +- .../atproto/server/requestAccountDelete.ts | 8 +- .../server/requestEmailConfirmation.ts | 8 +- .../com/atproto/server/requestEmailUpdate.ts | 9 +- .../atproto/server/requestPasswordReset.ts | 9 +- .../com/atproto/server/reserveSigningKey.ts | 10 +- .../types/com/atproto/server/resetPassword.ts | 9 +- .../com/atproto/server/revokeAppPassword.ts | 9 +- .../types/com/atproto/server/updateEmail.ts | 9 +- .../lexicon/types/com/atproto/sync/getBlob.ts | 8 +- .../types/com/atproto/sync/getBlocks.ts | 8 +- .../types/com/atproto/sync/getCheckout.ts | 8 +- .../lexicon/types/com/atproto/sync/getHead.ts | 9 +- .../types/com/atproto/sync/getLatestCommit.ts | 9 +- .../types/com/atproto/sync/getRecord.ts | 8 +- .../lexicon/types/com/atproto/sync/getRepo.ts | 8 +- .../types/com/atproto/sync/getRepoStatus.ts | 9 +- .../types/com/atproto/sync/listBlobs.ts | 9 +- .../types/com/atproto/sync/listRepos.ts | 25 +- .../types/com/atproto/sync/notifyOfUpdate.ts | 9 +- .../types/com/atproto/sync/requestCrawl.ts | 9 +- .../types/com/atproto/sync/subscribeRepos.ts | 152 ++-- .../com/atproto/temp/addReservedHandle.ts | 13 +- .../com/atproto/temp/checkSignupQueue.ts | 9 +- .../types/com/atproto/temp/fetchLabels.ts | 11 +- .../atproto/temp/requestPhoneVerification.ts | 9 +- .../ozone/communication/createTemplate.ts | 11 +- .../types/tools/ozone/communication/defs.ts | 24 +- .../ozone/communication/deleteTemplate.ts | 9 +- .../ozone/communication/listTemplates.ts | 11 +- .../ozone/communication/updateTemplate.ts | 11 +- .../types/tools/ozone/moderation/defs.ts | 765 ++++++++---------- .../types/tools/ozone/moderation/emitEvent.ts | 59 +- .../types/tools/ozone/moderation/getEvent.ts | 10 +- .../types/tools/ozone/moderation/getRecord.ts | 10 +- .../tools/ozone/moderation/getRecords.ts | 17 +- .../types/tools/ozone/moderation/getRepo.ts | 10 +- .../types/tools/ozone/moderation/getRepos.ts | 17 +- .../tools/ozone/moderation/queryEvents.ts | 11 +- .../tools/ozone/moderation/queryStatuses.ts | 11 +- .../tools/ozone/moderation/searchRepos.ts | 11 +- .../types/tools/ozone/server/getConfig.ts | 41 +- .../types/tools/ozone/set/addValues.ts | 9 +- .../src/lexicon/types/tools/ozone/set/defs.ts | 38 +- .../types/tools/ozone/set/deleteSet.ts | 13 +- .../types/tools/ozone/set/deleteValues.ts | 9 +- .../types/tools/ozone/set/getValues.ts | 11 +- .../types/tools/ozone/set/querySets.ts | 11 +- .../types/tools/ozone/set/upsertSet.ts | 10 +- .../lexicon/types/tools/ozone/setting/defs.ts | 26 +- .../types/tools/ozone/setting/listOptions.ts | 11 +- .../tools/ozone/setting/removeOptions.ts | 13 +- .../types/tools/ozone/setting/upsertOption.ts | 14 +- .../types/tools/ozone/signature/defs.ts | 24 +- .../tools/ozone/signature/findCorrelation.ts | 11 +- .../ozone/signature/findRelatedAccounts.ts | 32 +- .../tools/ozone/signature/searchAccounts.ts | 11 +- .../types/tools/ozone/team/addMember.ts | 11 +- .../lexicon/types/tools/ozone/team/defs.ts | 32 +- .../types/tools/ozone/team/deleteMember.ts | 9 +- .../types/tools/ozone/team/listMembers.ts | 11 +- .../types/tools/ozone/team/updateMember.ts | 11 +- packages/ozone/src/lexicon/util.ts | 85 +- packages/pds/src/lexicon/lexicons.ts | 41 +- .../src/lexicon/types/app/bsky/actor/defs.ts | 468 +++++------ .../types/app/bsky/actor/getPreferences.ts | 11 +- .../types/app/bsky/actor/getProfile.ts | 10 +- .../types/app/bsky/actor/getProfiles.ts | 11 +- .../types/app/bsky/actor/getSuggestions.ts | 11 +- .../lexicon/types/app/bsky/actor/profile.ts | 32 +- .../types/app/bsky/actor/putPreferences.ts | 11 +- .../types/app/bsky/actor/searchActors.ts | 11 +- .../app/bsky/actor/searchActorsTypeahead.ts | 11 +- .../src/lexicon/types/app/bsky/embed/defs.ts | 24 +- .../lexicon/types/app/bsky/embed/external.ts | 73 +- .../lexicon/types/app/bsky/embed/images.ts | 71 +- .../lexicon/types/app/bsky/embed/record.ts | 155 ++-- .../types/app/bsky/embed/recordWithMedia.ts | 65 +- .../src/lexicon/types/app/bsky/embed/video.ts | 57 +- .../src/lexicon/types/app/bsky/feed/defs.ts | 395 +++++---- .../app/bsky/feed/describeFeedGenerator.ts | 41 +- .../lexicon/types/app/bsky/feed/generator.ts | 32 +- .../types/app/bsky/feed/getActorFeeds.ts | 11 +- .../types/app/bsky/feed/getActorLikes.ts | 11 +- .../types/app/bsky/feed/getAuthorFeed.ts | 11 +- .../lexicon/types/app/bsky/feed/getFeed.ts | 11 +- .../types/app/bsky/feed/getFeedGenerator.ts | 11 +- .../types/app/bsky/feed/getFeedGenerators.ts | 11 +- .../types/app/bsky/feed/getFeedSkeleton.ts | 11 +- .../lexicon/types/app/bsky/feed/getLikes.ts | 25 +- .../types/app/bsky/feed/getListFeed.ts | 11 +- .../types/app/bsky/feed/getPostThread.ts | 19 +- .../lexicon/types/app/bsky/feed/getPosts.ts | 11 +- .../lexicon/types/app/bsky/feed/getQuotes.ts | 11 +- .../types/app/bsky/feed/getRepostedBy.ts | 11 +- .../types/app/bsky/feed/getSuggestedFeeds.ts | 11 +- .../types/app/bsky/feed/getTimeline.ts | 11 +- .../src/lexicon/types/app/bsky/feed/like.ts | 25 +- .../src/lexicon/types/app/bsky/feed/post.ts | 99 ++- .../lexicon/types/app/bsky/feed/postgate.ts | 42 +- .../src/lexicon/types/app/bsky/feed/repost.ts | 26 +- .../types/app/bsky/feed/searchPosts.ts | 11 +- .../types/app/bsky/feed/sendInteractions.ts | 15 +- .../lexicon/types/app/bsky/feed/threadgate.ts | 98 ++- .../src/lexicon/types/app/bsky/graph/block.ts | 24 +- .../src/lexicon/types/app/bsky/graph/defs.ts | 154 ++-- .../lexicon/types/app/bsky/graph/follow.ts | 24 +- .../app/bsky/graph/getActorStarterPacks.ts | 11 +- .../lexicon/types/app/bsky/graph/getBlocks.ts | 11 +- .../types/app/bsky/graph/getFollowers.ts | 11 +- .../types/app/bsky/graph/getFollows.ts | 11 +- .../types/app/bsky/graph/getKnownFollowers.ts | 11 +- .../lexicon/types/app/bsky/graph/getList.ts | 11 +- .../types/app/bsky/graph/getListBlocks.ts | 11 +- .../types/app/bsky/graph/getListMutes.ts | 11 +- .../lexicon/types/app/bsky/graph/getLists.ts | 11 +- .../lexicon/types/app/bsky/graph/getMutes.ts | 11 +- .../types/app/bsky/graph/getRelationships.ts | 17 +- .../types/app/bsky/graph/getStarterPack.ts | 11 +- .../types/app/bsky/graph/getStarterPacks.ts | 11 +- .../bsky/graph/getSuggestedFollowsByActor.ts | 11 +- .../src/lexicon/types/app/bsky/graph/list.ts | 34 +- .../lexicon/types/app/bsky/graph/listblock.ts | 24 +- .../lexicon/types/app/bsky/graph/listitem.ts | 24 +- .../lexicon/types/app/bsky/graph/muteActor.ts | 9 +- .../types/app/bsky/graph/muteActorList.ts | 9 +- .../types/app/bsky/graph/muteThread.ts | 9 +- .../app/bsky/graph/searchStarterPacks.ts | 11 +- .../types/app/bsky/graph/starterpack.ts | 42 +- .../types/app/bsky/graph/unmuteActor.ts | 9 +- .../types/app/bsky/graph/unmuteActorList.ts | 9 +- .../types/app/bsky/graph/unmuteThread.ts | 9 +- .../lexicon/types/app/bsky/labeler/defs.ts | 76 +- .../types/app/bsky/labeler/getServices.ts | 17 +- .../lexicon/types/app/bsky/labeler/service.ts | 32 +- .../app/bsky/notification/getUnreadCount.ts | 9 +- .../bsky/notification/listNotifications.ts | 34 +- .../app/bsky/notification/putPreferences.ts | 9 +- .../app/bsky/notification/registerPush.ts | 9 +- .../types/app/bsky/notification/updateSeen.ts | 9 +- .../lexicon/types/app/bsky/richtext/facet.ts | 89 +- .../lexicon/types/app/bsky/unspecced/defs.ts | 79 +- .../types/app/bsky/unspecced/getConfig.ts | 9 +- .../unspecced/getPopularFeedGenerators.ts | 11 +- .../bsky/unspecced/getSuggestionsSkeleton.ts | 11 +- .../bsky/unspecced/getTaggedSuggestions.ts | 28 +- .../app/bsky/unspecced/getTrendingTopics.ts | 11 +- .../bsky/unspecced/searchActorsSkeleton.ts | 11 +- .../app/bsky/unspecced/searchPostsSkeleton.ts | 11 +- .../unspecced/searchStarterPacksSkeleton.ts | 11 +- .../src/lexicon/types/app/bsky/video/defs.ts | 24 +- .../types/app/bsky/video/getJobStatus.ts | 11 +- .../types/app/bsky/video/getUploadLimits.ts | 9 +- .../types/app/bsky/video/uploadVideo.ts | 11 +- .../types/chat/bsky/actor/declaration.ts | 24 +- .../src/lexicon/types/chat/bsky/actor/defs.ts | 28 +- .../types/chat/bsky/actor/deleteAccount.ts | 12 +- .../chat/bsky/actor/exportAccountData.ts | 8 +- .../src/lexicon/types/chat/bsky/convo/defs.ts | 194 ++--- .../chat/bsky/convo/deleteMessageForSelf.ts | 11 +- .../lexicon/types/chat/bsky/convo/getConvo.ts | 11 +- .../chat/bsky/convo/getConvoForMembers.ts | 11 +- .../lexicon/types/chat/bsky/convo/getLog.ts | 21 +- .../types/chat/bsky/convo/getMessages.ts | 17 +- .../types/chat/bsky/convo/leaveConvo.ts | 10 +- .../types/chat/bsky/convo/listConvos.ts | 11 +- .../types/chat/bsky/convo/muteConvo.ts | 12 +- .../types/chat/bsky/convo/sendMessage.ts | 11 +- .../types/chat/bsky/convo/sendMessageBatch.ts | 28 +- .../types/chat/bsky/convo/unmuteConvo.ts | 12 +- .../types/chat/bsky/convo/updateRead.ts | 12 +- .../chat/bsky/moderation/getActorMetadata.ts | 25 +- .../chat/bsky/moderation/getMessageContext.ts | 17 +- .../chat/bsky/moderation/updateActorAccess.ts | 9 +- .../lexicon/types/com/atproto/admin/defs.ts | 92 +-- .../types/com/atproto/admin/deleteAccount.ts | 9 +- .../atproto/admin/disableAccountInvites.ts | 9 +- .../com/atproto/admin/disableInviteCodes.ts | 9 +- .../com/atproto/admin/enableAccountInvites.ts | 9 +- .../types/com/atproto/admin/getAccountInfo.ts | 10 +- .../com/atproto/admin/getAccountInfos.ts | 11 +- .../types/com/atproto/admin/getInviteCodes.ts | 11 +- .../com/atproto/admin/getSubjectStatus.ts | 21 +- .../types/com/atproto/admin/searchAccounts.ts | 11 +- .../types/com/atproto/admin/sendEmail.ts | 10 +- .../com/atproto/admin/updateAccountEmail.ts | 9 +- .../com/atproto/admin/updateAccountHandle.ts | 9 +- .../atproto/admin/updateAccountPassword.ts | 9 +- .../com/atproto/admin/updateSubjectStatus.ts | 30 +- .../identity/getRecommendedDidCredentials.ts | 13 +- .../identity/requestPlcOperationSignature.ts | 8 +- .../com/atproto/identity/resolveHandle.ts | 9 +- .../com/atproto/identity/signPlcOperation.ts | 16 +- .../atproto/identity/submitPlcOperation.ts | 11 +- .../com/atproto/identity/updateHandle.ts | 9 +- .../lexicon/types/com/atproto/label/defs.ts | 95 +-- .../types/com/atproto/label/queryLabels.ts | 11 +- .../com/atproto/label/subscribeLabels.ts | 47 +- .../types/com/atproto/lexicon/schema.ts | 24 +- .../com/atproto/moderation/createReport.ts | 28 +- .../types/com/atproto/moderation/defs.ts | 22 +- .../types/com/atproto/repo/applyWrites.ts | 120 ++- .../types/com/atproto/repo/createRecord.ts | 14 +- .../lexicon/types/com/atproto/repo/defs.ts | 24 +- .../types/com/atproto/repo/deleteRecord.ts | 12 +- .../types/com/atproto/repo/describeRepo.ts | 11 +- .../types/com/atproto/repo/getRecord.ts | 11 +- .../types/com/atproto/repo/importRepo.ts | 8 +- .../com/atproto/repo/listMissingBlobs.ts | 25 +- .../types/com/atproto/repo/listRecords.ts | 27 +- .../types/com/atproto/repo/putRecord.ts | 14 +- .../types/com/atproto/repo/strongRef.ts | 25 +- .../types/com/atproto/repo/uploadBlob.ts | 9 +- .../com/atproto/server/activateAccount.ts | 8 +- .../com/atproto/server/checkAccountStatus.ts | 9 +- .../types/com/atproto/server/confirmEmail.ts | 9 +- .../types/com/atproto/server/createAccount.ts | 14 +- .../com/atproto/server/createAppPassword.ts | 28 +- .../com/atproto/server/createInviteCode.ts | 10 +- .../com/atproto/server/createInviteCodes.ts | 29 +- .../types/com/atproto/server/createSession.ts | 12 +- .../com/atproto/server/deactivateAccount.ts | 9 +- .../lexicon/types/com/atproto/server/defs.ts | 40 +- .../types/com/atproto/server/deleteAccount.ts | 9 +- .../types/com/atproto/server/deleteSession.ts | 8 +- .../com/atproto/server/describeServer.ts | 41 +- .../atproto/server/getAccountInviteCodes.ts | 11 +- .../com/atproto/server/getServiceAuth.ts | 9 +- .../types/com/atproto/server/getSession.ts | 11 +- .../com/atproto/server/listAppPasswords.ts | 25 +- .../com/atproto/server/refreshSession.ts | 11 +- .../atproto/server/requestAccountDelete.ts | 8 +- .../server/requestEmailConfirmation.ts | 8 +- .../com/atproto/server/requestEmailUpdate.ts | 9 +- .../atproto/server/requestPasswordReset.ts | 9 +- .../com/atproto/server/reserveSigningKey.ts | 10 +- .../types/com/atproto/server/resetPassword.ts | 9 +- .../com/atproto/server/revokeAppPassword.ts | 9 +- .../types/com/atproto/server/updateEmail.ts | 9 +- .../lexicon/types/com/atproto/sync/getBlob.ts | 8 +- .../types/com/atproto/sync/getBlocks.ts | 8 +- .../types/com/atproto/sync/getCheckout.ts | 8 +- .../lexicon/types/com/atproto/sync/getHead.ts | 9 +- .../types/com/atproto/sync/getLatestCommit.ts | 9 +- .../types/com/atproto/sync/getRecord.ts | 8 +- .../lexicon/types/com/atproto/sync/getRepo.ts | 8 +- .../types/com/atproto/sync/getRepoStatus.ts | 9 +- .../types/com/atproto/sync/listBlobs.ts | 9 +- .../types/com/atproto/sync/listRepos.ts | 25 +- .../types/com/atproto/sync/notifyOfUpdate.ts | 9 +- .../types/com/atproto/sync/requestCrawl.ts | 9 +- .../types/com/atproto/sync/subscribeRepos.ts | 152 ++-- .../com/atproto/temp/addReservedHandle.ts | 13 +- .../com/atproto/temp/checkSignupQueue.ts | 9 +- .../types/com/atproto/temp/fetchLabels.ts | 11 +- .../atproto/temp/requestPhoneVerification.ts | 9 +- .../ozone/communication/createTemplate.ts | 11 +- .../types/tools/ozone/communication/defs.ts | 24 +- .../ozone/communication/deleteTemplate.ts | 9 +- .../ozone/communication/listTemplates.ts | 11 +- .../ozone/communication/updateTemplate.ts | 11 +- .../types/tools/ozone/moderation/defs.ts | 765 ++++++++---------- .../types/tools/ozone/moderation/emitEvent.ts | 59 +- .../types/tools/ozone/moderation/getEvent.ts | 10 +- .../types/tools/ozone/moderation/getRecord.ts | 10 +- .../tools/ozone/moderation/getRecords.ts | 17 +- .../types/tools/ozone/moderation/getRepo.ts | 10 +- .../types/tools/ozone/moderation/getRepos.ts | 17 +- .../tools/ozone/moderation/queryEvents.ts | 11 +- .../tools/ozone/moderation/queryStatuses.ts | 11 +- .../tools/ozone/moderation/searchRepos.ts | 11 +- .../types/tools/ozone/server/getConfig.ts | 41 +- .../types/tools/ozone/set/addValues.ts | 9 +- .../src/lexicon/types/tools/ozone/set/defs.ts | 38 +- .../types/tools/ozone/set/deleteSet.ts | 13 +- .../types/tools/ozone/set/deleteValues.ts | 9 +- .../types/tools/ozone/set/getValues.ts | 11 +- .../types/tools/ozone/set/querySets.ts | 11 +- .../types/tools/ozone/set/upsertSet.ts | 10 +- .../lexicon/types/tools/ozone/setting/defs.ts | 26 +- .../types/tools/ozone/setting/listOptions.ts | 11 +- .../tools/ozone/setting/removeOptions.ts | 13 +- .../types/tools/ozone/setting/upsertOption.ts | 14 +- .../types/tools/ozone/signature/defs.ts | 24 +- .../tools/ozone/signature/findCorrelation.ts | 11 +- .../ozone/signature/findRelatedAccounts.ts | 32 +- .../tools/ozone/signature/searchAccounts.ts | 11 +- .../types/tools/ozone/team/addMember.ts | 11 +- .../lexicon/types/tools/ozone/team/defs.ts | 32 +- .../types/tools/ozone/team/deleteMember.ts | 9 +- .../types/tools/ozone/team/listMembers.ts | 11 +- .../types/tools/ozone/team/updateMember.ts | 11 +- packages/pds/src/lexicon/util.ts | 85 +- 885 files changed, 13249 insertions(+), 11811 deletions(-) diff --git a/packages/api/src/client/index.ts b/packages/api/src/client/index.ts index 5f46b6b277c..f2a219d2c4f 100644 --- a/packages/api/src/client/index.ts +++ b/packages/api/src/client/index.ts @@ -4,6 +4,7 @@ import { XrpcClient, FetchHandler, FetchHandlerOptions } from '@atproto/xrpc' import { schemas } from './lexicons' import { CID } from 'multiformats/cid' +import { OmitKey, Un$Typed } from './util' import * as ComAtprotoAdminDefs from './types/com/atproto/admin/defs' import * as ComAtprotoAdminDeleteAccount from './types/com/atproto/admin/deleteAccount' import * as ComAtprotoAdminDisableAccountInvites from './types/com/atproto/admin/disableAccountInvites' @@ -854,7 +855,7 @@ export class SchemaRecord { } async list( - params: Omit, + params: OmitKey, ): Promise<{ cursor?: string records: { uri: string; value: ComAtprotoLexiconSchema.Record }[] @@ -867,7 +868,7 @@ export class SchemaRecord { } async get( - params: Omit, + params: OmitKey, ): Promise<{ uri: string cid: string @@ -881,25 +882,25 @@ export class SchemaRecord { } async create( - params: Omit< + params: OmitKey< ComAtprotoRepoCreateRecord.InputSchema, 'collection' | 'record' >, - record: ComAtprotoLexiconSchema.Record, + record: Un$Typed, headers?: Record, ): Promise<{ uri: string; cid: string }> { - record.$type = 'com.atproto.lexicon.schema' + const collection = 'com.atproto.lexicon.schema' const res = await this._client.call( 'com.atproto.repo.createRecord', undefined, - { collection: 'com.atproto.lexicon.schema', ...params, record }, + { collection, ...params, record: { ...record, $type: collection } }, { encoding: 'application/json', headers }, ) return res.data } async delete( - params: Omit, + params: OmitKey, headers?: Record, ): Promise { await this._client.call( @@ -1689,7 +1690,7 @@ export class ProfileRecord { } async list( - params: Omit, + params: OmitKey, ): Promise<{ cursor?: string records: { uri: string; value: AppBskyActorProfile.Record }[] @@ -1702,7 +1703,7 @@ export class ProfileRecord { } async get( - params: Omit, + params: OmitKey, ): Promise<{ uri: string; cid: string; value: AppBskyActorProfile.Record }> { const res = await this._client.call('com.atproto.repo.getRecord', { collection: 'app.bsky.actor.profile', @@ -1712,25 +1713,30 @@ export class ProfileRecord { } async create( - params: Omit< + params: OmitKey< ComAtprotoRepoCreateRecord.InputSchema, 'collection' | 'record' >, - record: AppBskyActorProfile.Record, + record: Un$Typed, headers?: Record, ): Promise<{ uri: string; cid: string }> { - record.$type = 'app.bsky.actor.profile' + const collection = 'app.bsky.actor.profile' const res = await this._client.call( 'com.atproto.repo.createRecord', undefined, - { collection: 'app.bsky.actor.profile', rkey: 'self', ...params, record }, + { + collection, + rkey: 'self', + ...params, + record: { ...record, $type: collection }, + }, { encoding: 'application/json', headers }, ) return res.data } async delete( - params: Omit, + params: OmitKey, headers?: Record, ): Promise { await this._client.call( @@ -1972,7 +1978,7 @@ export class GeneratorRecord { } async list( - params: Omit, + params: OmitKey, ): Promise<{ cursor?: string records: { uri: string; value: AppBskyFeedGenerator.Record }[] @@ -1985,7 +1991,7 @@ export class GeneratorRecord { } async get( - params: Omit, + params: OmitKey, ): Promise<{ uri: string; cid: string; value: AppBskyFeedGenerator.Record }> { const res = await this._client.call('com.atproto.repo.getRecord', { collection: 'app.bsky.feed.generator', @@ -1995,25 +2001,25 @@ export class GeneratorRecord { } async create( - params: Omit< + params: OmitKey< ComAtprotoRepoCreateRecord.InputSchema, 'collection' | 'record' >, - record: AppBskyFeedGenerator.Record, + record: Un$Typed, headers?: Record, ): Promise<{ uri: string; cid: string }> { - record.$type = 'app.bsky.feed.generator' + const collection = 'app.bsky.feed.generator' const res = await this._client.call( 'com.atproto.repo.createRecord', undefined, - { collection: 'app.bsky.feed.generator', ...params, record }, + { collection, ...params, record: { ...record, $type: collection } }, { encoding: 'application/json', headers }, ) return res.data } async delete( - params: Omit, + params: OmitKey, headers?: Record, ): Promise { await this._client.call( @@ -2033,7 +2039,7 @@ export class LikeRecord { } async list( - params: Omit, + params: OmitKey, ): Promise<{ cursor?: string records: { uri: string; value: AppBskyFeedLike.Record }[] @@ -2046,7 +2052,7 @@ export class LikeRecord { } async get( - params: Omit, + params: OmitKey, ): Promise<{ uri: string; cid: string; value: AppBskyFeedLike.Record }> { const res = await this._client.call('com.atproto.repo.getRecord', { collection: 'app.bsky.feed.like', @@ -2056,25 +2062,25 @@ export class LikeRecord { } async create( - params: Omit< + params: OmitKey< ComAtprotoRepoCreateRecord.InputSchema, 'collection' | 'record' >, - record: AppBskyFeedLike.Record, + record: Un$Typed, headers?: Record, ): Promise<{ uri: string; cid: string }> { - record.$type = 'app.bsky.feed.like' + const collection = 'app.bsky.feed.like' const res = await this._client.call( 'com.atproto.repo.createRecord', undefined, - { collection: 'app.bsky.feed.like', ...params, record }, + { collection, ...params, record: { ...record, $type: collection } }, { encoding: 'application/json', headers }, ) return res.data } async delete( - params: Omit, + params: OmitKey, headers?: Record, ): Promise { await this._client.call( @@ -2094,7 +2100,7 @@ export class PostRecord { } async list( - params: Omit, + params: OmitKey, ): Promise<{ cursor?: string records: { uri: string; value: AppBskyFeedPost.Record }[] @@ -2107,7 +2113,7 @@ export class PostRecord { } async get( - params: Omit, + params: OmitKey, ): Promise<{ uri: string; cid: string; value: AppBskyFeedPost.Record }> { const res = await this._client.call('com.atproto.repo.getRecord', { collection: 'app.bsky.feed.post', @@ -2117,25 +2123,25 @@ export class PostRecord { } async create( - params: Omit< + params: OmitKey< ComAtprotoRepoCreateRecord.InputSchema, 'collection' | 'record' >, - record: AppBskyFeedPost.Record, + record: Un$Typed, headers?: Record, ): Promise<{ uri: string; cid: string }> { - record.$type = 'app.bsky.feed.post' + const collection = 'app.bsky.feed.post' const res = await this._client.call( 'com.atproto.repo.createRecord', undefined, - { collection: 'app.bsky.feed.post', ...params, record }, + { collection, ...params, record: { ...record, $type: collection } }, { encoding: 'application/json', headers }, ) return res.data } async delete( - params: Omit, + params: OmitKey, headers?: Record, ): Promise { await this._client.call( @@ -2155,7 +2161,7 @@ export class PostgateRecord { } async list( - params: Omit, + params: OmitKey, ): Promise<{ cursor?: string records: { uri: string; value: AppBskyFeedPostgate.Record }[] @@ -2168,7 +2174,7 @@ export class PostgateRecord { } async get( - params: Omit, + params: OmitKey, ): Promise<{ uri: string; cid: string; value: AppBskyFeedPostgate.Record }> { const res = await this._client.call('com.atproto.repo.getRecord', { collection: 'app.bsky.feed.postgate', @@ -2178,25 +2184,25 @@ export class PostgateRecord { } async create( - params: Omit< + params: OmitKey< ComAtprotoRepoCreateRecord.InputSchema, 'collection' | 'record' >, - record: AppBskyFeedPostgate.Record, + record: Un$Typed, headers?: Record, ): Promise<{ uri: string; cid: string }> { - record.$type = 'app.bsky.feed.postgate' + const collection = 'app.bsky.feed.postgate' const res = await this._client.call( 'com.atproto.repo.createRecord', undefined, - { collection: 'app.bsky.feed.postgate', ...params, record }, + { collection, ...params, record: { ...record, $type: collection } }, { encoding: 'application/json', headers }, ) return res.data } async delete( - params: Omit, + params: OmitKey, headers?: Record, ): Promise { await this._client.call( @@ -2216,7 +2222,7 @@ export class RepostRecord { } async list( - params: Omit, + params: OmitKey, ): Promise<{ cursor?: string records: { uri: string; value: AppBskyFeedRepost.Record }[] @@ -2229,7 +2235,7 @@ export class RepostRecord { } async get( - params: Omit, + params: OmitKey, ): Promise<{ uri: string; cid: string; value: AppBskyFeedRepost.Record }> { const res = await this._client.call('com.atproto.repo.getRecord', { collection: 'app.bsky.feed.repost', @@ -2239,25 +2245,25 @@ export class RepostRecord { } async create( - params: Omit< + params: OmitKey< ComAtprotoRepoCreateRecord.InputSchema, 'collection' | 'record' >, - record: AppBskyFeedRepost.Record, + record: Un$Typed, headers?: Record, ): Promise<{ uri: string; cid: string }> { - record.$type = 'app.bsky.feed.repost' + const collection = 'app.bsky.feed.repost' const res = await this._client.call( 'com.atproto.repo.createRecord', undefined, - { collection: 'app.bsky.feed.repost', ...params, record }, + { collection, ...params, record: { ...record, $type: collection } }, { encoding: 'application/json', headers }, ) return res.data } async delete( - params: Omit, + params: OmitKey, headers?: Record, ): Promise { await this._client.call( @@ -2277,7 +2283,7 @@ export class ThreadgateRecord { } async list( - params: Omit, + params: OmitKey, ): Promise<{ cursor?: string records: { uri: string; value: AppBskyFeedThreadgate.Record }[] @@ -2290,7 +2296,7 @@ export class ThreadgateRecord { } async get( - params: Omit, + params: OmitKey, ): Promise<{ uri: string cid: string @@ -2304,25 +2310,25 @@ export class ThreadgateRecord { } async create( - params: Omit< + params: OmitKey< ComAtprotoRepoCreateRecord.InputSchema, 'collection' | 'record' >, - record: AppBskyFeedThreadgate.Record, + record: Un$Typed, headers?: Record, ): Promise<{ uri: string; cid: string }> { - record.$type = 'app.bsky.feed.threadgate' + const collection = 'app.bsky.feed.threadgate' const res = await this._client.call( 'com.atproto.repo.createRecord', undefined, - { collection: 'app.bsky.feed.threadgate', ...params, record }, + { collection, ...params, record: { ...record, $type: collection } }, { encoding: 'application/json', headers }, ) return res.data } async delete( - params: Omit, + params: OmitKey, headers?: Record, ): Promise { await this._client.call( @@ -2583,7 +2589,7 @@ export class BlockRecord { } async list( - params: Omit, + params: OmitKey, ): Promise<{ cursor?: string records: { uri: string; value: AppBskyGraphBlock.Record }[] @@ -2596,7 +2602,7 @@ export class BlockRecord { } async get( - params: Omit, + params: OmitKey, ): Promise<{ uri: string; cid: string; value: AppBskyGraphBlock.Record }> { const res = await this._client.call('com.atproto.repo.getRecord', { collection: 'app.bsky.graph.block', @@ -2606,25 +2612,25 @@ export class BlockRecord { } async create( - params: Omit< + params: OmitKey< ComAtprotoRepoCreateRecord.InputSchema, 'collection' | 'record' >, - record: AppBskyGraphBlock.Record, + record: Un$Typed, headers?: Record, ): Promise<{ uri: string; cid: string }> { - record.$type = 'app.bsky.graph.block' + const collection = 'app.bsky.graph.block' const res = await this._client.call( 'com.atproto.repo.createRecord', undefined, - { collection: 'app.bsky.graph.block', ...params, record }, + { collection, ...params, record: { ...record, $type: collection } }, { encoding: 'application/json', headers }, ) return res.data } async delete( - params: Omit, + params: OmitKey, headers?: Record, ): Promise { await this._client.call( @@ -2644,7 +2650,7 @@ export class FollowRecord { } async list( - params: Omit, + params: OmitKey, ): Promise<{ cursor?: string records: { uri: string; value: AppBskyGraphFollow.Record }[] @@ -2657,7 +2663,7 @@ export class FollowRecord { } async get( - params: Omit, + params: OmitKey, ): Promise<{ uri: string; cid: string; value: AppBskyGraphFollow.Record }> { const res = await this._client.call('com.atproto.repo.getRecord', { collection: 'app.bsky.graph.follow', @@ -2667,25 +2673,25 @@ export class FollowRecord { } async create( - params: Omit< + params: OmitKey< ComAtprotoRepoCreateRecord.InputSchema, 'collection' | 'record' >, - record: AppBskyGraphFollow.Record, + record: Un$Typed, headers?: Record, ): Promise<{ uri: string; cid: string }> { - record.$type = 'app.bsky.graph.follow' + const collection = 'app.bsky.graph.follow' const res = await this._client.call( 'com.atproto.repo.createRecord', undefined, - { collection: 'app.bsky.graph.follow', ...params, record }, + { collection, ...params, record: { ...record, $type: collection } }, { encoding: 'application/json', headers }, ) return res.data } async delete( - params: Omit, + params: OmitKey, headers?: Record, ): Promise { await this._client.call( @@ -2705,7 +2711,7 @@ export class ListRecord { } async list( - params: Omit, + params: OmitKey, ): Promise<{ cursor?: string records: { uri: string; value: AppBskyGraphList.Record }[] @@ -2718,7 +2724,7 @@ export class ListRecord { } async get( - params: Omit, + params: OmitKey, ): Promise<{ uri: string; cid: string; value: AppBskyGraphList.Record }> { const res = await this._client.call('com.atproto.repo.getRecord', { collection: 'app.bsky.graph.list', @@ -2728,25 +2734,25 @@ export class ListRecord { } async create( - params: Omit< + params: OmitKey< ComAtprotoRepoCreateRecord.InputSchema, 'collection' | 'record' >, - record: AppBskyGraphList.Record, + record: Un$Typed, headers?: Record, ): Promise<{ uri: string; cid: string }> { - record.$type = 'app.bsky.graph.list' + const collection = 'app.bsky.graph.list' const res = await this._client.call( 'com.atproto.repo.createRecord', undefined, - { collection: 'app.bsky.graph.list', ...params, record }, + { collection, ...params, record: { ...record, $type: collection } }, { encoding: 'application/json', headers }, ) return res.data } async delete( - params: Omit, + params: OmitKey, headers?: Record, ): Promise { await this._client.call( @@ -2766,7 +2772,7 @@ export class ListblockRecord { } async list( - params: Omit, + params: OmitKey, ): Promise<{ cursor?: string records: { uri: string; value: AppBskyGraphListblock.Record }[] @@ -2779,7 +2785,7 @@ export class ListblockRecord { } async get( - params: Omit, + params: OmitKey, ): Promise<{ uri: string cid: string @@ -2793,25 +2799,25 @@ export class ListblockRecord { } async create( - params: Omit< + params: OmitKey< ComAtprotoRepoCreateRecord.InputSchema, 'collection' | 'record' >, - record: AppBskyGraphListblock.Record, + record: Un$Typed, headers?: Record, ): Promise<{ uri: string; cid: string }> { - record.$type = 'app.bsky.graph.listblock' + const collection = 'app.bsky.graph.listblock' const res = await this._client.call( 'com.atproto.repo.createRecord', undefined, - { collection: 'app.bsky.graph.listblock', ...params, record }, + { collection, ...params, record: { ...record, $type: collection } }, { encoding: 'application/json', headers }, ) return res.data } async delete( - params: Omit, + params: OmitKey, headers?: Record, ): Promise { await this._client.call( @@ -2831,7 +2837,7 @@ export class ListitemRecord { } async list( - params: Omit, + params: OmitKey, ): Promise<{ cursor?: string records: { uri: string; value: AppBskyGraphListitem.Record }[] @@ -2844,7 +2850,7 @@ export class ListitemRecord { } async get( - params: Omit, + params: OmitKey, ): Promise<{ uri: string; cid: string; value: AppBskyGraphListitem.Record }> { const res = await this._client.call('com.atproto.repo.getRecord', { collection: 'app.bsky.graph.listitem', @@ -2854,25 +2860,25 @@ export class ListitemRecord { } async create( - params: Omit< + params: OmitKey< ComAtprotoRepoCreateRecord.InputSchema, 'collection' | 'record' >, - record: AppBskyGraphListitem.Record, + record: Un$Typed, headers?: Record, ): Promise<{ uri: string; cid: string }> { - record.$type = 'app.bsky.graph.listitem' + const collection = 'app.bsky.graph.listitem' const res = await this._client.call( 'com.atproto.repo.createRecord', undefined, - { collection: 'app.bsky.graph.listitem', ...params, record }, + { collection, ...params, record: { ...record, $type: collection } }, { encoding: 'application/json', headers }, ) return res.data } async delete( - params: Omit, + params: OmitKey, headers?: Record, ): Promise { await this._client.call( @@ -2892,7 +2898,7 @@ export class StarterpackRecord { } async list( - params: Omit, + params: OmitKey, ): Promise<{ cursor?: string records: { uri: string; value: AppBskyGraphStarterpack.Record }[] @@ -2905,7 +2911,7 @@ export class StarterpackRecord { } async get( - params: Omit, + params: OmitKey, ): Promise<{ uri: string cid: string @@ -2919,25 +2925,25 @@ export class StarterpackRecord { } async create( - params: Omit< + params: OmitKey< ComAtprotoRepoCreateRecord.InputSchema, 'collection' | 'record' >, - record: AppBskyGraphStarterpack.Record, + record: Un$Typed, headers?: Record, ): Promise<{ uri: string; cid: string }> { - record.$type = 'app.bsky.graph.starterpack' + const collection = 'app.bsky.graph.starterpack' const res = await this._client.call( 'com.atproto.repo.createRecord', undefined, - { collection: 'app.bsky.graph.starterpack', ...params, record }, + { collection, ...params, record: { ...record, $type: collection } }, { encoding: 'application/json', headers }, ) return res.data } async delete( - params: Omit, + params: OmitKey, headers?: Record, ): Promise { await this._client.call( @@ -2979,7 +2985,7 @@ export class ServiceRecord { } async list( - params: Omit, + params: OmitKey, ): Promise<{ cursor?: string records: { uri: string; value: AppBskyLabelerService.Record }[] @@ -2992,7 +2998,7 @@ export class ServiceRecord { } async get( - params: Omit, + params: OmitKey, ): Promise<{ uri: string cid: string @@ -3006,22 +3012,22 @@ export class ServiceRecord { } async create( - params: Omit< + params: OmitKey< ComAtprotoRepoCreateRecord.InputSchema, 'collection' | 'record' >, - record: AppBskyLabelerService.Record, + record: Un$Typed, headers?: Record, ): Promise<{ uri: string; cid: string }> { - record.$type = 'app.bsky.labeler.service' + const collection = 'app.bsky.labeler.service' const res = await this._client.call( 'com.atproto.repo.createRecord', undefined, { - collection: 'app.bsky.labeler.service', + collection, rkey: 'self', ...params, - record, + record: { ...record, $type: collection }, }, { encoding: 'application/json', headers }, ) @@ -3029,7 +3035,7 @@ export class ServiceRecord { } async delete( - params: Omit, + params: OmitKey, headers?: Record, ): Promise { await this._client.call( @@ -3328,7 +3334,7 @@ export class DeclarationRecord { } async list( - params: Omit, + params: OmitKey, ): Promise<{ cursor?: string records: { uri: string; value: ChatBskyActorDeclaration.Record }[] @@ -3341,7 +3347,7 @@ export class DeclarationRecord { } async get( - params: Omit, + params: OmitKey, ): Promise<{ uri: string cid: string @@ -3355,22 +3361,22 @@ export class DeclarationRecord { } async create( - params: Omit< + params: OmitKey< ComAtprotoRepoCreateRecord.InputSchema, 'collection' | 'record' >, - record: ChatBskyActorDeclaration.Record, + record: Un$Typed, headers?: Record, ): Promise<{ uri: string; cid: string }> { - record.$type = 'chat.bsky.actor.declaration' + const collection = 'chat.bsky.actor.declaration' const res = await this._client.call( 'com.atproto.repo.createRecord', undefined, { - collection: 'chat.bsky.actor.declaration', + collection, rkey: 'self', ...params, - record, + record: { ...record, $type: collection }, }, { encoding: 'application/json', headers }, ) @@ -3378,7 +3384,7 @@ export class DeclarationRecord { } async delete( - params: Omit, + params: OmitKey, headers?: Record, ): Promise { await this._client.call( diff --git a/packages/api/src/client/lexicons.ts b/packages/api/src/client/lexicons.ts index 6e709774761..b91bcc292bd 100644 --- a/packages/api/src/client/lexicons.ts +++ b/packages/api/src/client/lexicons.ts @@ -1,7 +1,13 @@ /** * GENERATED CODE - DO NOT MODIFY */ -import { LexiconDoc, Lexicons } from '@atproto/lexicon' +import { + LexiconDoc, + Lexicons, + ValidationError, + ValidationResult, +} from '@atproto/lexicon' +import { $Typed, is$typed, maybe$typed } from './util' export const schemaDict = { ComAtprotoAdminDefs: { @@ -13871,8 +13877,37 @@ export const schemaDict = { }, } as const satisfies Record -export const schemas = Object.values(schemaDict) +export const schemas = Object.values(schemaDict) satisfies LexiconDoc[] export const lexicons: Lexicons = new Lexicons(schemas) + +export function validate( + v: unknown, + id: string, + hash: string, + requiredType: true, +): ValidationResult +export function validate( + v: unknown, + id: string, + hash: string, + requiredType?: false, +): ValidationResult +export function validate( + v: unknown, + id: string, + hash: string, + requiredType?: boolean, +): ValidationResult { + return (requiredType ? is$typed : maybe$typed)(v, id, hash) + ? lexicons.validate(`${id}#${hash}`, v) + : { + success: false, + error: new ValidationError( + `Must be an object with "${hash === 'main' ? id : `${id}#${hash}`}" $type property`, + ), + } +} + export const ids = { ComAtprotoAdminDefs: 'com.atproto.admin.defs', ComAtprotoAdminDeleteAccount: 'com.atproto.admin.deleteAccount', @@ -14124,4 +14159,4 @@ export const ids = { ToolsOzoneTeamDeleteMember: 'tools.ozone.team.deleteMember', ToolsOzoneTeamListMembers: 'tools.ozone.team.listMembers', ToolsOzoneTeamUpdateMember: 'tools.ozone.team.updateMember', -} +} as const diff --git a/packages/api/src/client/types/app/bsky/actor/defs.ts b/packages/api/src/client/types/app/bsky/actor/defs.ts index 84234ed2d08..74e0a4d6dcf 100644 --- a/packages/api/src/client/types/app/bsky/actor/defs.ts +++ b/packages/api/src/client/types/app/bsky/actor/defs.ts @@ -2,16 +2,21 @@ * GENERATED CODE - DO NOT MODIFY */ import { ValidationResult, BlobRef } from '@atproto/lexicon' -import { isObj, hasProp } from '../../../../util' -import { lexicons } from '../../../../lexicons' import { CID } from 'multiformats/cid' -import * as ComAtprotoLabelDefs from '../../../com/atproto/label/defs' -import * as AppBskyGraphDefs from '../graph/defs' -import * as ComAtprotoRepoStrongRef from '../../../com/atproto/repo/strongRef' -import * as AppBskyFeedThreadgate from '../feed/threadgate' -import * as AppBskyFeedPostgate from '../feed/postgate' +import { validate as _validate } from '../../../../lexicons' +import { $Type, $Typed, is$typed as _is$typed, OmitKey } from '../../../../util' +import type * as ComAtprotoLabelDefs from '../../../com/atproto/label/defs' +import type * as AppBskyGraphDefs from '../graph/defs' +import type * as ComAtprotoRepoStrongRef from '../../../com/atproto/repo/strongRef' +import type * as AppBskyFeedThreadgate from '../feed/threadgate' +import type * as AppBskyFeedPostgate from '../feed/postgate' + +const is$typed = _is$typed, + validate = _validate +const id = 'app.bsky.actor.defs' export interface ProfileViewBasic { + $type?: $Type<'app.bsky.actor.defs', 'profileViewBasic'> did: string handle: string displayName?: string @@ -20,22 +25,20 @@ export interface ProfileViewBasic { viewer?: ViewerState labels?: ComAtprotoLabelDefs.Label[] createdAt?: string - [k: string]: unknown } -export function isProfileViewBasic(v: unknown): v is ProfileViewBasic { - return ( - isObj(v) && - hasProp(v, '$type') && - v.$type === 'app.bsky.actor.defs#profileViewBasic' - ) +const hashProfileViewBasic = 'profileViewBasic' + +export function isProfileViewBasic(v: V) { + return is$typed(v, id, hashProfileViewBasic) } -export function validateProfileViewBasic(v: unknown): ValidationResult { - return lexicons.validate('app.bsky.actor.defs#profileViewBasic', v) +export function validateProfileViewBasic(v: V) { + return validate(v, id, hashProfileViewBasic) } export interface ProfileView { + $type?: $Type<'app.bsky.actor.defs', 'profileView'> did: string handle: string displayName?: string @@ -46,22 +49,20 @@ export interface ProfileView { createdAt?: string viewer?: ViewerState labels?: ComAtprotoLabelDefs.Label[] - [k: string]: unknown } -export function isProfileView(v: unknown): v is ProfileView { - return ( - isObj(v) && - hasProp(v, '$type') && - v.$type === 'app.bsky.actor.defs#profileView' - ) +const hashProfileView = 'profileView' + +export function isProfileView(v: V) { + return is$typed(v, id, hashProfileView) } -export function validateProfileView(v: unknown): ValidationResult { - return lexicons.validate('app.bsky.actor.defs#profileView', v) +export function validateProfileView(v: V) { + return validate(v, id, hashProfileView) } export interface ProfileViewDetailed { + $type?: $Type<'app.bsky.actor.defs', 'profileViewDetailed'> did: string handle: string displayName?: string @@ -78,63 +79,55 @@ export interface ProfileViewDetailed { viewer?: ViewerState labels?: ComAtprotoLabelDefs.Label[] pinnedPost?: ComAtprotoRepoStrongRef.Main - [k: string]: unknown } -export function isProfileViewDetailed(v: unknown): v is ProfileViewDetailed { - return ( - isObj(v) && - hasProp(v, '$type') && - v.$type === 'app.bsky.actor.defs#profileViewDetailed' - ) +const hashProfileViewDetailed = 'profileViewDetailed' + +export function isProfileViewDetailed(v: V) { + return is$typed(v, id, hashProfileViewDetailed) } -export function validateProfileViewDetailed(v: unknown): ValidationResult { - return lexicons.validate('app.bsky.actor.defs#profileViewDetailed', v) +export function validateProfileViewDetailed(v: V) { + return validate(v, id, hashProfileViewDetailed) } export interface ProfileAssociated { + $type?: $Type<'app.bsky.actor.defs', 'profileAssociated'> lists?: number feedgens?: number starterPacks?: number labeler?: boolean chat?: ProfileAssociatedChat - [k: string]: unknown } -export function isProfileAssociated(v: unknown): v is ProfileAssociated { - return ( - isObj(v) && - hasProp(v, '$type') && - v.$type === 'app.bsky.actor.defs#profileAssociated' - ) +const hashProfileAssociated = 'profileAssociated' + +export function isProfileAssociated(v: V) { + return is$typed(v, id, hashProfileAssociated) } -export function validateProfileAssociated(v: unknown): ValidationResult { - return lexicons.validate('app.bsky.actor.defs#profileAssociated', v) +export function validateProfileAssociated(v: V) { + return validate(v, id, hashProfileAssociated) } export interface ProfileAssociatedChat { + $type?: $Type<'app.bsky.actor.defs', 'profileAssociatedChat'> allowIncoming: 'all' | 'none' | 'following' | (string & {}) - [k: string]: unknown } -export function isProfileAssociatedChat( - v: unknown, -): v is ProfileAssociatedChat { - return ( - isObj(v) && - hasProp(v, '$type') && - v.$type === 'app.bsky.actor.defs#profileAssociatedChat' - ) +const hashProfileAssociatedChat = 'profileAssociatedChat' + +export function isProfileAssociatedChat(v: V) { + return is$typed(v, id, hashProfileAssociatedChat) } -export function validateProfileAssociatedChat(v: unknown): ValidationResult { - return lexicons.validate('app.bsky.actor.defs#profileAssociatedChat', v) +export function validateProfileAssociatedChat(v: V) { + return validate(v, id, hashProfileAssociatedChat) } /** Metadata about the requesting account's relationship with the subject account. Only has meaningful content for authed requests. */ export interface ViewerState { + $type?: $Type<'app.bsky.actor.defs', 'viewerState'> muted?: boolean mutedByList?: AppBskyGraphDefs.ListViewBasic blockedBy?: boolean @@ -143,169 +136,153 @@ export interface ViewerState { following?: string followedBy?: string knownFollowers?: KnownFollowers - [k: string]: unknown } -export function isViewerState(v: unknown): v is ViewerState { - return ( - isObj(v) && - hasProp(v, '$type') && - v.$type === 'app.bsky.actor.defs#viewerState' - ) +const hashViewerState = 'viewerState' + +export function isViewerState(v: V) { + return is$typed(v, id, hashViewerState) } -export function validateViewerState(v: unknown): ValidationResult { - return lexicons.validate('app.bsky.actor.defs#viewerState', v) +export function validateViewerState(v: V) { + return validate(v, id, hashViewerState) } /** The subject's followers whom you also follow */ export interface KnownFollowers { + $type?: $Type<'app.bsky.actor.defs', 'knownFollowers'> count: number followers: ProfileViewBasic[] - [k: string]: unknown } -export function isKnownFollowers(v: unknown): v is KnownFollowers { - return ( - isObj(v) && - hasProp(v, '$type') && - v.$type === 'app.bsky.actor.defs#knownFollowers' - ) +const hashKnownFollowers = 'knownFollowers' + +export function isKnownFollowers(v: V) { + return is$typed(v, id, hashKnownFollowers) } -export function validateKnownFollowers(v: unknown): ValidationResult { - return lexicons.validate('app.bsky.actor.defs#knownFollowers', v) +export function validateKnownFollowers(v: V) { + return validate(v, id, hashKnownFollowers) } export type Preferences = ( - | AdultContentPref - | ContentLabelPref - | SavedFeedsPref - | SavedFeedsPrefV2 - | PersonalDetailsPref - | FeedViewPref - | ThreadViewPref - | InterestsPref - | MutedWordsPref - | HiddenPostsPref - | BskyAppStatePref - | LabelersPref - | PostInteractionSettingsPref - | { $type: string; [k: string]: unknown } + | $Typed + | $Typed + | $Typed + | $Typed + | $Typed + | $Typed + | $Typed + | $Typed + | $Typed + | $Typed + | $Typed + | $Typed + | $Typed + | { $type: string } )[] export interface AdultContentPref { + $type?: $Type<'app.bsky.actor.defs', 'adultContentPref'> enabled: boolean - [k: string]: unknown } -export function isAdultContentPref(v: unknown): v is AdultContentPref { - return ( - isObj(v) && - hasProp(v, '$type') && - v.$type === 'app.bsky.actor.defs#adultContentPref' - ) +const hashAdultContentPref = 'adultContentPref' + +export function isAdultContentPref(v: V) { + return is$typed(v, id, hashAdultContentPref) } -export function validateAdultContentPref(v: unknown): ValidationResult { - return lexicons.validate('app.bsky.actor.defs#adultContentPref', v) +export function validateAdultContentPref(v: V) { + return validate(v, id, hashAdultContentPref) } export interface ContentLabelPref { + $type?: $Type<'app.bsky.actor.defs', 'contentLabelPref'> /** Which labeler does this preference apply to? If undefined, applies globally. */ labelerDid?: string label: string visibility: 'ignore' | 'show' | 'warn' | 'hide' | (string & {}) - [k: string]: unknown } -export function isContentLabelPref(v: unknown): v is ContentLabelPref { - return ( - isObj(v) && - hasProp(v, '$type') && - v.$type === 'app.bsky.actor.defs#contentLabelPref' - ) +const hashContentLabelPref = 'contentLabelPref' + +export function isContentLabelPref(v: V) { + return is$typed(v, id, hashContentLabelPref) } -export function validateContentLabelPref(v: unknown): ValidationResult { - return lexicons.validate('app.bsky.actor.defs#contentLabelPref', v) +export function validateContentLabelPref(v: V) { + return validate(v, id, hashContentLabelPref) } export interface SavedFeed { + $type?: $Type<'app.bsky.actor.defs', 'savedFeed'> id: string type: 'feed' | 'list' | 'timeline' | (string & {}) value: string pinned: boolean - [k: string]: unknown } -export function isSavedFeed(v: unknown): v is SavedFeed { - return ( - isObj(v) && - hasProp(v, '$type') && - v.$type === 'app.bsky.actor.defs#savedFeed' - ) +const hashSavedFeed = 'savedFeed' + +export function isSavedFeed(v: V) { + return is$typed(v, id, hashSavedFeed) } -export function validateSavedFeed(v: unknown): ValidationResult { - return lexicons.validate('app.bsky.actor.defs#savedFeed', v) +export function validateSavedFeed(v: V) { + return validate(v, id, hashSavedFeed) } export interface SavedFeedsPrefV2 { + $type?: $Type<'app.bsky.actor.defs', 'savedFeedsPrefV2'> items: SavedFeed[] - [k: string]: unknown } -export function isSavedFeedsPrefV2(v: unknown): v is SavedFeedsPrefV2 { - return ( - isObj(v) && - hasProp(v, '$type') && - v.$type === 'app.bsky.actor.defs#savedFeedsPrefV2' - ) +const hashSavedFeedsPrefV2 = 'savedFeedsPrefV2' + +export function isSavedFeedsPrefV2(v: V) { + return is$typed(v, id, hashSavedFeedsPrefV2) } -export function validateSavedFeedsPrefV2(v: unknown): ValidationResult { - return lexicons.validate('app.bsky.actor.defs#savedFeedsPrefV2', v) +export function validateSavedFeedsPrefV2(v: V) { + return validate(v, id, hashSavedFeedsPrefV2) } export interface SavedFeedsPref { + $type?: $Type<'app.bsky.actor.defs', 'savedFeedsPref'> pinned: string[] saved: string[] timelineIndex?: number - [k: string]: unknown } -export function isSavedFeedsPref(v: unknown): v is SavedFeedsPref { - return ( - isObj(v) && - hasProp(v, '$type') && - v.$type === 'app.bsky.actor.defs#savedFeedsPref' - ) +const hashSavedFeedsPref = 'savedFeedsPref' + +export function isSavedFeedsPref(v: V) { + return is$typed(v, id, hashSavedFeedsPref) } -export function validateSavedFeedsPref(v: unknown): ValidationResult { - return lexicons.validate('app.bsky.actor.defs#savedFeedsPref', v) +export function validateSavedFeedsPref(v: V) { + return validate(v, id, hashSavedFeedsPref) } export interface PersonalDetailsPref { + $type?: $Type<'app.bsky.actor.defs', 'personalDetailsPref'> /** The birth date of account owner. */ birthDate?: string - [k: string]: unknown } -export function isPersonalDetailsPref(v: unknown): v is PersonalDetailsPref { - return ( - isObj(v) && - hasProp(v, '$type') && - v.$type === 'app.bsky.actor.defs#personalDetailsPref' - ) +const hashPersonalDetailsPref = 'personalDetailsPref' + +export function isPersonalDetailsPref(v: V) { + return is$typed(v, id, hashPersonalDetailsPref) } -export function validatePersonalDetailsPref(v: unknown): ValidationResult { - return lexicons.validate('app.bsky.actor.defs#personalDetailsPref', v) +export function validatePersonalDetailsPref(v: V) { + return validate(v, id, hashPersonalDetailsPref) } export interface FeedViewPref { + $type?: $Type<'app.bsky.actor.defs', 'feedViewPref'> /** The URI of the feed, or an identifier which describes the feed. */ feed: string /** Hide replies in the feed. */ @@ -318,22 +295,20 @@ export interface FeedViewPref { hideReposts?: boolean /** Hide quote posts in the feed. */ hideQuotePosts?: boolean - [k: string]: unknown } -export function isFeedViewPref(v: unknown): v is FeedViewPref { - return ( - isObj(v) && - hasProp(v, '$type') && - v.$type === 'app.bsky.actor.defs#feedViewPref' - ) +const hashFeedViewPref = 'feedViewPref' + +export function isFeedViewPref(v: V) { + return is$typed(v, id, hashFeedViewPref) } -export function validateFeedViewPref(v: unknown): ValidationResult { - return lexicons.validate('app.bsky.actor.defs#feedViewPref', v) +export function validateFeedViewPref(v: V) { + return validate(v, id, hashFeedViewPref) } export interface ThreadViewPref { + $type?: $Type<'app.bsky.actor.defs', 'threadViewPref'> /** Sorting mode for threads. */ sort?: | 'oldest' @@ -344,43 +319,39 @@ export interface ThreadViewPref { | (string & {}) /** Show followed users at the top of all replies. */ prioritizeFollowedUsers?: boolean - [k: string]: unknown } -export function isThreadViewPref(v: unknown): v is ThreadViewPref { - return ( - isObj(v) && - hasProp(v, '$type') && - v.$type === 'app.bsky.actor.defs#threadViewPref' - ) +const hashThreadViewPref = 'threadViewPref' + +export function isThreadViewPref(v: V) { + return is$typed(v, id, hashThreadViewPref) } -export function validateThreadViewPref(v: unknown): ValidationResult { - return lexicons.validate('app.bsky.actor.defs#threadViewPref', v) +export function validateThreadViewPref(v: V) { + return validate(v, id, hashThreadViewPref) } export interface InterestsPref { + $type?: $Type<'app.bsky.actor.defs', 'interestsPref'> /** A list of tags which describe the account owner's interests gathered during onboarding. */ tags: string[] - [k: string]: unknown } -export function isInterestsPref(v: unknown): v is InterestsPref { - return ( - isObj(v) && - hasProp(v, '$type') && - v.$type === 'app.bsky.actor.defs#interestsPref' - ) +const hashInterestsPref = 'interestsPref' + +export function isInterestsPref(v: V) { + return is$typed(v, id, hashInterestsPref) } -export function validateInterestsPref(v: unknown): ValidationResult { - return lexicons.validate('app.bsky.actor.defs#interestsPref', v) +export function validateInterestsPref(v: V) { + return validate(v, id, hashInterestsPref) } export type MutedWordTarget = 'content' | 'tag' | (string & {}) /** A word that the account owner has muted. */ export interface MutedWord { + $type?: $Type<'app.bsky.actor.defs', 'mutedWord'> id?: string /** The muted word itself. */ value: string @@ -390,182 +361,165 @@ export interface MutedWord { actorTarget: 'all' | 'exclude-following' | (string & {}) /** The date and time at which the muted word will expire and no longer be applied. */ expiresAt?: string - [k: string]: unknown } -export function isMutedWord(v: unknown): v is MutedWord { - return ( - isObj(v) && - hasProp(v, '$type') && - v.$type === 'app.bsky.actor.defs#mutedWord' - ) +const hashMutedWord = 'mutedWord' + +export function isMutedWord(v: V) { + return is$typed(v, id, hashMutedWord) } -export function validateMutedWord(v: unknown): ValidationResult { - return lexicons.validate('app.bsky.actor.defs#mutedWord', v) +export function validateMutedWord(v: V) { + return validate(v, id, hashMutedWord) } export interface MutedWordsPref { + $type?: $Type<'app.bsky.actor.defs', 'mutedWordsPref'> /** A list of words the account owner has muted. */ items: MutedWord[] - [k: string]: unknown } -export function isMutedWordsPref(v: unknown): v is MutedWordsPref { - return ( - isObj(v) && - hasProp(v, '$type') && - v.$type === 'app.bsky.actor.defs#mutedWordsPref' - ) +const hashMutedWordsPref = 'mutedWordsPref' + +export function isMutedWordsPref(v: V) { + return is$typed(v, id, hashMutedWordsPref) } -export function validateMutedWordsPref(v: unknown): ValidationResult { - return lexicons.validate('app.bsky.actor.defs#mutedWordsPref', v) +export function validateMutedWordsPref(v: V) { + return validate(v, id, hashMutedWordsPref) } export interface HiddenPostsPref { + $type?: $Type<'app.bsky.actor.defs', 'hiddenPostsPref'> /** A list of URIs of posts the account owner has hidden. */ items: string[] - [k: string]: unknown } -export function isHiddenPostsPref(v: unknown): v is HiddenPostsPref { - return ( - isObj(v) && - hasProp(v, '$type') && - v.$type === 'app.bsky.actor.defs#hiddenPostsPref' - ) +const hashHiddenPostsPref = 'hiddenPostsPref' + +export function isHiddenPostsPref(v: V) { + return is$typed(v, id, hashHiddenPostsPref) } -export function validateHiddenPostsPref(v: unknown): ValidationResult { - return lexicons.validate('app.bsky.actor.defs#hiddenPostsPref', v) +export function validateHiddenPostsPref(v: V) { + return validate(v, id, hashHiddenPostsPref) } export interface LabelersPref { + $type?: $Type<'app.bsky.actor.defs', 'labelersPref'> labelers: LabelerPrefItem[] - [k: string]: unknown } -export function isLabelersPref(v: unknown): v is LabelersPref { - return ( - isObj(v) && - hasProp(v, '$type') && - v.$type === 'app.bsky.actor.defs#labelersPref' - ) +const hashLabelersPref = 'labelersPref' + +export function isLabelersPref(v: V) { + return is$typed(v, id, hashLabelersPref) } -export function validateLabelersPref(v: unknown): ValidationResult { - return lexicons.validate('app.bsky.actor.defs#labelersPref', v) +export function validateLabelersPref(v: V) { + return validate(v, id, hashLabelersPref) } export interface LabelerPrefItem { + $type?: $Type<'app.bsky.actor.defs', 'labelerPrefItem'> did: string - [k: string]: unknown } -export function isLabelerPrefItem(v: unknown): v is LabelerPrefItem { - return ( - isObj(v) && - hasProp(v, '$type') && - v.$type === 'app.bsky.actor.defs#labelerPrefItem' - ) +const hashLabelerPrefItem = 'labelerPrefItem' + +export function isLabelerPrefItem(v: V) { + return is$typed(v, id, hashLabelerPrefItem) } -export function validateLabelerPrefItem(v: unknown): ValidationResult { - return lexicons.validate('app.bsky.actor.defs#labelerPrefItem', v) +export function validateLabelerPrefItem(v: V) { + return validate(v, id, hashLabelerPrefItem) } /** A grab bag of state that's specific to the bsky.app program. Third-party apps shouldn't use this. */ export interface BskyAppStatePref { + $type?: $Type<'app.bsky.actor.defs', 'bskyAppStatePref'> activeProgressGuide?: BskyAppProgressGuide /** An array of tokens which identify nudges (modals, popups, tours, highlight dots) that should be shown to the user. */ queuedNudges?: string[] /** Storage for NUXs the user has encountered. */ nuxs?: Nux[] - [k: string]: unknown } -export function isBskyAppStatePref(v: unknown): v is BskyAppStatePref { - return ( - isObj(v) && - hasProp(v, '$type') && - v.$type === 'app.bsky.actor.defs#bskyAppStatePref' - ) +const hashBskyAppStatePref = 'bskyAppStatePref' + +export function isBskyAppStatePref(v: V) { + return is$typed(v, id, hashBskyAppStatePref) } -export function validateBskyAppStatePref(v: unknown): ValidationResult { - return lexicons.validate('app.bsky.actor.defs#bskyAppStatePref', v) +export function validateBskyAppStatePref(v: V) { + return validate(v, id, hashBskyAppStatePref) } /** If set, an active progress guide. Once completed, can be set to undefined. Should have unspecced fields tracking progress. */ export interface BskyAppProgressGuide { + $type?: $Type<'app.bsky.actor.defs', 'bskyAppProgressGuide'> guide: string - [k: string]: unknown } -export function isBskyAppProgressGuide(v: unknown): v is BskyAppProgressGuide { - return ( - isObj(v) && - hasProp(v, '$type') && - v.$type === 'app.bsky.actor.defs#bskyAppProgressGuide' - ) +const hashBskyAppProgressGuide = 'bskyAppProgressGuide' + +export function isBskyAppProgressGuide(v: V) { + return is$typed(v, id, hashBskyAppProgressGuide) } -export function validateBskyAppProgressGuide(v: unknown): ValidationResult { - return lexicons.validate('app.bsky.actor.defs#bskyAppProgressGuide', v) +export function validateBskyAppProgressGuide(v: V) { + return validate(v, id, hashBskyAppProgressGuide) } /** A new user experiences (NUX) storage object */ export interface Nux { + $type?: $Type<'app.bsky.actor.defs', 'nux'> id: string completed: boolean /** Arbitrary data for the NUX. The structure is defined by the NUX itself. Limited to 300 characters. */ data?: string /** The date and time at which the NUX will expire and should be considered completed. */ expiresAt?: string - [k: string]: unknown } -export function isNux(v: unknown): v is Nux { - return ( - isObj(v) && hasProp(v, '$type') && v.$type === 'app.bsky.actor.defs#nux' - ) +const hashNux = 'nux' + +export function isNux(v: V) { + return is$typed(v, id, hashNux) } -export function validateNux(v: unknown): ValidationResult { - return lexicons.validate('app.bsky.actor.defs#nux', v) +export function validateNux(v: V) { + return validate(v, id, hashNux) } /** Default post interaction settings for the account. These values should be applied as default values when creating new posts. These refs should mirror the threadgate and postgate records exactly. */ export interface PostInteractionSettingsPref { + $type?: $Type<'app.bsky.actor.defs', 'postInteractionSettingsPref'> /** Matches threadgate record. List of rules defining who can reply to this users posts. If value is an empty array, no one can reply. If value is undefined, anyone can reply. */ threadgateAllowRules?: ( - | AppBskyFeedThreadgate.MentionRule - | AppBskyFeedThreadgate.FollowerRule - | AppBskyFeedThreadgate.FollowingRule - | AppBskyFeedThreadgate.ListRule - | { $type: string; [k: string]: unknown } + | $Typed + | $Typed + | $Typed + | $Typed + | { $type: string } )[] /** Matches postgate record. List of rules defining who can embed this users posts. If value is an empty array or is undefined, no particular rules apply and anyone can embed. */ postgateEmbeddingRules?: ( - | AppBskyFeedPostgate.DisableRule - | { $type: string; [k: string]: unknown } + | $Typed + | { $type: string } )[] - [k: string]: unknown } -export function isPostInteractionSettingsPref( - v: unknown, -): v is PostInteractionSettingsPref { - return ( - isObj(v) && - hasProp(v, '$type') && - v.$type === 'app.bsky.actor.defs#postInteractionSettingsPref' - ) +const hashPostInteractionSettingsPref = 'postInteractionSettingsPref' + +export function isPostInteractionSettingsPref(v: V) { + return is$typed(v, id, hashPostInteractionSettingsPref) } -export function validatePostInteractionSettingsPref( - v: unknown, -): ValidationResult { - return lexicons.validate('app.bsky.actor.defs#postInteractionSettingsPref', v) +export function validatePostInteractionSettingsPref(v: V) { + return validate( + v, + id, + hashPostInteractionSettingsPref, + ) } diff --git a/packages/api/src/client/types/app/bsky/actor/getPreferences.ts b/packages/api/src/client/types/app/bsky/actor/getPreferences.ts index 6cc35cc3c62..e2115d80433 100644 --- a/packages/api/src/client/types/app/bsky/actor/getPreferences.ts +++ b/packages/api/src/client/types/app/bsky/actor/getPreferences.ts @@ -3,10 +3,14 @@ */ import { HeadersMap, XRPCError } from '@atproto/xrpc' import { ValidationResult, BlobRef } from '@atproto/lexicon' -import { isObj, hasProp } from '../../../../util' -import { lexicons } from '../../../../lexicons' import { CID } from 'multiformats/cid' -import * as AppBskyActorDefs from './defs' +import { validate as _validate } from '../../../../lexicons' +import { $Type, $Typed, is$typed as _is$typed, OmitKey } from '../../../../util' +import type * as AppBskyActorDefs from './defs' + +const is$typed = _is$typed, + validate = _validate +const id = 'app.bsky.actor.getPreferences' export interface QueryParams {} @@ -14,7 +18,6 @@ export type InputSchema = undefined export interface OutputSchema { preferences: AppBskyActorDefs.Preferences - [k: string]: unknown } export interface CallOptions { diff --git a/packages/api/src/client/types/app/bsky/actor/getProfile.ts b/packages/api/src/client/types/app/bsky/actor/getProfile.ts index 13618b6c2f7..250599c1121 100644 --- a/packages/api/src/client/types/app/bsky/actor/getProfile.ts +++ b/packages/api/src/client/types/app/bsky/actor/getProfile.ts @@ -3,10 +3,14 @@ */ import { HeadersMap, XRPCError } from '@atproto/xrpc' import { ValidationResult, BlobRef } from '@atproto/lexicon' -import { isObj, hasProp } from '../../../../util' -import { lexicons } from '../../../../lexicons' import { CID } from 'multiformats/cid' -import * as AppBskyActorDefs from './defs' +import { validate as _validate } from '../../../../lexicons' +import { $Type, $Typed, is$typed as _is$typed, OmitKey } from '../../../../util' +import type * as AppBskyActorDefs from './defs' + +const is$typed = _is$typed, + validate = _validate +const id = 'app.bsky.actor.getProfile' export interface QueryParams { /** Handle or DID of account to fetch profile of. */ diff --git a/packages/api/src/client/types/app/bsky/actor/getProfiles.ts b/packages/api/src/client/types/app/bsky/actor/getProfiles.ts index 2207218ac69..51752db6057 100644 --- a/packages/api/src/client/types/app/bsky/actor/getProfiles.ts +++ b/packages/api/src/client/types/app/bsky/actor/getProfiles.ts @@ -3,10 +3,14 @@ */ import { HeadersMap, XRPCError } from '@atproto/xrpc' import { ValidationResult, BlobRef } from '@atproto/lexicon' -import { isObj, hasProp } from '../../../../util' -import { lexicons } from '../../../../lexicons' import { CID } from 'multiformats/cid' -import * as AppBskyActorDefs from './defs' +import { validate as _validate } from '../../../../lexicons' +import { $Type, $Typed, is$typed as _is$typed, OmitKey } from '../../../../util' +import type * as AppBskyActorDefs from './defs' + +const is$typed = _is$typed, + validate = _validate +const id = 'app.bsky.actor.getProfiles' export interface QueryParams { actors: string[] @@ -16,7 +20,6 @@ export type InputSchema = undefined export interface OutputSchema { profiles: AppBskyActorDefs.ProfileViewDetailed[] - [k: string]: unknown } export interface CallOptions { diff --git a/packages/api/src/client/types/app/bsky/actor/getSuggestions.ts b/packages/api/src/client/types/app/bsky/actor/getSuggestions.ts index 4bce936a4dd..5923c6b595f 100644 --- a/packages/api/src/client/types/app/bsky/actor/getSuggestions.ts +++ b/packages/api/src/client/types/app/bsky/actor/getSuggestions.ts @@ -3,10 +3,14 @@ */ import { HeadersMap, XRPCError } from '@atproto/xrpc' import { ValidationResult, BlobRef } from '@atproto/lexicon' -import { isObj, hasProp } from '../../../../util' -import { lexicons } from '../../../../lexicons' import { CID } from 'multiformats/cid' -import * as AppBskyActorDefs from './defs' +import { validate as _validate } from '../../../../lexicons' +import { $Type, $Typed, is$typed as _is$typed, OmitKey } from '../../../../util' +import type * as AppBskyActorDefs from './defs' + +const is$typed = _is$typed, + validate = _validate +const id = 'app.bsky.actor.getSuggestions' export interface QueryParams { limit?: number @@ -20,7 +24,6 @@ export interface OutputSchema { actors: AppBskyActorDefs.ProfileView[] /** Snowflake for this recommendation, use when submitting recommendation events. */ recId?: number - [k: string]: unknown } export interface CallOptions { diff --git a/packages/api/src/client/types/app/bsky/actor/profile.ts b/packages/api/src/client/types/app/bsky/actor/profile.ts index c109e93f9ab..cbca66378c3 100644 --- a/packages/api/src/client/types/app/bsky/actor/profile.ts +++ b/packages/api/src/client/types/app/bsky/actor/profile.ts @@ -2,13 +2,18 @@ * GENERATED CODE - DO NOT MODIFY */ import { ValidationResult, BlobRef } from '@atproto/lexicon' -import { isObj, hasProp } from '../../../../util' -import { lexicons } from '../../../../lexicons' import { CID } from 'multiformats/cid' -import * as ComAtprotoLabelDefs from '../../../com/atproto/label/defs' -import * as ComAtprotoRepoStrongRef from '../../../com/atproto/repo/strongRef' +import { validate as _validate } from '../../../../lexicons' +import { $Type, $Typed, is$typed as _is$typed, OmitKey } from '../../../../util' +import type * as ComAtprotoLabelDefs from '../../../com/atproto/label/defs' +import type * as ComAtprotoRepoStrongRef from '../../../com/atproto/repo/strongRef' + +const is$typed = _is$typed, + validate = _validate +const id = 'app.bsky.actor.profile' export interface Record { + $type: $Type<'app.bsky.actor.profile', 'main'> displayName?: string /** Free-form profile description text. */ description?: string @@ -16,24 +21,19 @@ export interface Record { avatar?: BlobRef /** Larger horizontal image to display behind profile view. */ banner?: BlobRef - labels?: - | ComAtprotoLabelDefs.SelfLabels - | { $type: string; [k: string]: unknown } + labels?: $Typed | { $type: string } joinedViaStarterPack?: ComAtprotoRepoStrongRef.Main pinnedPost?: ComAtprotoRepoStrongRef.Main createdAt?: string [k: string]: unknown } -export function isRecord(v: unknown): v is Record { - return ( - isObj(v) && - hasProp(v, '$type') && - (v.$type === 'app.bsky.actor.profile#main' || - v.$type === 'app.bsky.actor.profile') - ) +const hashRecord = 'main' + +export function isRecord(v: V) { + return is$typed(v, id, hashRecord) } -export function validateRecord(v: unknown): ValidationResult { - return lexicons.validate('app.bsky.actor.profile#main', v) +export function validateRecord(v: V) { + return validate(v, id, hashRecord, true) } diff --git a/packages/api/src/client/types/app/bsky/actor/putPreferences.ts b/packages/api/src/client/types/app/bsky/actor/putPreferences.ts index 834b1bd247c..d22571c891f 100644 --- a/packages/api/src/client/types/app/bsky/actor/putPreferences.ts +++ b/packages/api/src/client/types/app/bsky/actor/putPreferences.ts @@ -3,16 +3,19 @@ */ import { HeadersMap, XRPCError } from '@atproto/xrpc' import { ValidationResult, BlobRef } from '@atproto/lexicon' -import { isObj, hasProp } from '../../../../util' -import { lexicons } from '../../../../lexicons' import { CID } from 'multiformats/cid' -import * as AppBskyActorDefs from './defs' +import { validate as _validate } from '../../../../lexicons' +import { $Type, $Typed, is$typed as _is$typed, OmitKey } from '../../../../util' +import type * as AppBskyActorDefs from './defs' + +const is$typed = _is$typed, + validate = _validate +const id = 'app.bsky.actor.putPreferences' export interface QueryParams {} export interface InputSchema { preferences: AppBskyActorDefs.Preferences - [k: string]: unknown } export interface CallOptions { diff --git a/packages/api/src/client/types/app/bsky/actor/searchActors.ts b/packages/api/src/client/types/app/bsky/actor/searchActors.ts index bcbde2b7bfb..dcc4ad472d7 100644 --- a/packages/api/src/client/types/app/bsky/actor/searchActors.ts +++ b/packages/api/src/client/types/app/bsky/actor/searchActors.ts @@ -3,10 +3,14 @@ */ import { HeadersMap, XRPCError } from '@atproto/xrpc' import { ValidationResult, BlobRef } from '@atproto/lexicon' -import { isObj, hasProp } from '../../../../util' -import { lexicons } from '../../../../lexicons' import { CID } from 'multiformats/cid' -import * as AppBskyActorDefs from './defs' +import { validate as _validate } from '../../../../lexicons' +import { $Type, $Typed, is$typed as _is$typed, OmitKey } from '../../../../util' +import type * as AppBskyActorDefs from './defs' + +const is$typed = _is$typed, + validate = _validate +const id = 'app.bsky.actor.searchActors' export interface QueryParams { /** DEPRECATED: use 'q' instead. */ @@ -22,7 +26,6 @@ export type InputSchema = undefined export interface OutputSchema { cursor?: string actors: AppBskyActorDefs.ProfileView[] - [k: string]: unknown } export interface CallOptions { diff --git a/packages/api/src/client/types/app/bsky/actor/searchActorsTypeahead.ts b/packages/api/src/client/types/app/bsky/actor/searchActorsTypeahead.ts index abed0289f27..74de7034d8d 100644 --- a/packages/api/src/client/types/app/bsky/actor/searchActorsTypeahead.ts +++ b/packages/api/src/client/types/app/bsky/actor/searchActorsTypeahead.ts @@ -3,10 +3,14 @@ */ import { HeadersMap, XRPCError } from '@atproto/xrpc' import { ValidationResult, BlobRef } from '@atproto/lexicon' -import { isObj, hasProp } from '../../../../util' -import { lexicons } from '../../../../lexicons' import { CID } from 'multiformats/cid' -import * as AppBskyActorDefs from './defs' +import { validate as _validate } from '../../../../lexicons' +import { $Type, $Typed, is$typed as _is$typed, OmitKey } from '../../../../util' +import type * as AppBskyActorDefs from './defs' + +const is$typed = _is$typed, + validate = _validate +const id = 'app.bsky.actor.searchActorsTypeahead' export interface QueryParams { /** DEPRECATED: use 'q' instead. */ @@ -20,7 +24,6 @@ export type InputSchema = undefined export interface OutputSchema { actors: AppBskyActorDefs.ProfileViewBasic[] - [k: string]: unknown } export interface CallOptions { diff --git a/packages/api/src/client/types/app/bsky/embed/defs.ts b/packages/api/src/client/types/app/bsky/embed/defs.ts index b7b753d65f6..9bc6e863a98 100644 --- a/packages/api/src/client/types/app/bsky/embed/defs.ts +++ b/packages/api/src/client/types/app/bsky/embed/defs.ts @@ -2,25 +2,27 @@ * GENERATED CODE - DO NOT MODIFY */ import { ValidationResult, BlobRef } from '@atproto/lexicon' -import { isObj, hasProp } from '../../../../util' -import { lexicons } from '../../../../lexicons' import { CID } from 'multiformats/cid' +import { validate as _validate } from '../../../../lexicons' +import { $Type, $Typed, is$typed as _is$typed, OmitKey } from '../../../../util' + +const is$typed = _is$typed, + validate = _validate +const id = 'app.bsky.embed.defs' /** width:height represents an aspect ratio. It may be approximate, and may not correspond to absolute dimensions in any given unit. */ export interface AspectRatio { + $type?: $Type<'app.bsky.embed.defs', 'aspectRatio'> width: number height: number - [k: string]: unknown } -export function isAspectRatio(v: unknown): v is AspectRatio { - return ( - isObj(v) && - hasProp(v, '$type') && - v.$type === 'app.bsky.embed.defs#aspectRatio' - ) +const hashAspectRatio = 'aspectRatio' + +export function isAspectRatio(v: V) { + return is$typed(v, id, hashAspectRatio) } -export function validateAspectRatio(v: unknown): ValidationResult { - return lexicons.validate('app.bsky.embed.defs#aspectRatio', v) +export function validateAspectRatio(v: V) { + return validate(v, id, hashAspectRatio) } diff --git a/packages/api/src/client/types/app/bsky/embed/external.ts b/packages/api/src/client/types/app/bsky/embed/external.ts index 5832cbb3987..d1113226d0c 100644 --- a/packages/api/src/client/types/app/bsky/embed/external.ts +++ b/packages/api/src/client/types/app/bsky/embed/external.ts @@ -2,82 +2,77 @@ * GENERATED CODE - DO NOT MODIFY */ import { ValidationResult, BlobRef } from '@atproto/lexicon' -import { isObj, hasProp } from '../../../../util' -import { lexicons } from '../../../../lexicons' import { CID } from 'multiformats/cid' +import { validate as _validate } from '../../../../lexicons' +import { $Type, $Typed, is$typed as _is$typed, OmitKey } from '../../../../util' + +const is$typed = _is$typed, + validate = _validate +const id = 'app.bsky.embed.external' /** A representation of some externally linked content (eg, a URL and 'card'), embedded in a Bluesky record (eg, a post). */ export interface Main { + $type?: $Type<'app.bsky.embed.external', 'main'> external: External - [k: string]: unknown } -export function isMain(v: unknown): v is Main { - return ( - isObj(v) && - hasProp(v, '$type') && - (v.$type === 'app.bsky.embed.external#main' || - v.$type === 'app.bsky.embed.external') - ) +const hashMain = 'main' + +export function isMain(v: V) { + return is$typed(v, id, hashMain) } -export function validateMain(v: unknown): ValidationResult { - return lexicons.validate('app.bsky.embed.external#main', v) +export function validateMain(v: V) { + return validate
(v, id, hashMain) } export interface External { + $type?: $Type<'app.bsky.embed.external', 'external'> uri: string title: string description: string thumb?: BlobRef - [k: string]: unknown } -export function isExternal(v: unknown): v is External { - return ( - isObj(v) && - hasProp(v, '$type') && - v.$type === 'app.bsky.embed.external#external' - ) +const hashExternal = 'external' + +export function isExternal(v: V) { + return is$typed(v, id, hashExternal) } -export function validateExternal(v: unknown): ValidationResult { - return lexicons.validate('app.bsky.embed.external#external', v) +export function validateExternal(v: V) { + return validate(v, id, hashExternal) } export interface View { + $type?: $Type<'app.bsky.embed.external', 'view'> external: ViewExternal - [k: string]: unknown } -export function isView(v: unknown): v is View { - return ( - isObj(v) && - hasProp(v, '$type') && - v.$type === 'app.bsky.embed.external#view' - ) +const hashView = 'view' + +export function isView(v: V) { + return is$typed(v, id, hashView) } -export function validateView(v: unknown): ValidationResult { - return lexicons.validate('app.bsky.embed.external#view', v) +export function validateView(v: V) { + return validate(v, id, hashView) } export interface ViewExternal { + $type?: $Type<'app.bsky.embed.external', 'viewExternal'> uri: string title: string description: string thumb?: string - [k: string]: unknown } -export function isViewExternal(v: unknown): v is ViewExternal { - return ( - isObj(v) && - hasProp(v, '$type') && - v.$type === 'app.bsky.embed.external#viewExternal' - ) +const hashViewExternal = 'viewExternal' + +export function isViewExternal(v: V) { + return is$typed(v, id, hashViewExternal) } -export function validateViewExternal(v: unknown): ValidationResult { - return lexicons.validate('app.bsky.embed.external#viewExternal', v) +export function validateViewExternal(v: V) { + return validate(v, id, hashViewExternal) } diff --git a/packages/api/src/client/types/app/bsky/embed/images.ts b/packages/api/src/client/types/app/bsky/embed/images.ts index 886ad7c5c5b..2347123a0d0 100644 --- a/packages/api/src/client/types/app/bsky/embed/images.ts +++ b/packages/api/src/client/types/app/bsky/embed/images.ts @@ -2,63 +2,65 @@ * GENERATED CODE - DO NOT MODIFY */ import { ValidationResult, BlobRef } from '@atproto/lexicon' -import { isObj, hasProp } from '../../../../util' -import { lexicons } from '../../../../lexicons' import { CID } from 'multiformats/cid' -import * as AppBskyEmbedDefs from './defs' +import { validate as _validate } from '../../../../lexicons' +import { $Type, $Typed, is$typed as _is$typed, OmitKey } from '../../../../util' +import type * as AppBskyEmbedDefs from './defs' + +const is$typed = _is$typed, + validate = _validate +const id = 'app.bsky.embed.images' export interface Main { + $type?: $Type<'app.bsky.embed.images', 'main'> images: Image[] - [k: string]: unknown } -export function isMain(v: unknown): v is Main { - return ( - isObj(v) && - hasProp(v, '$type') && - (v.$type === 'app.bsky.embed.images#main' || - v.$type === 'app.bsky.embed.images') - ) +const hashMain = 'main' + +export function isMain(v: V) { + return is$typed(v, id, hashMain) } -export function validateMain(v: unknown): ValidationResult { - return lexicons.validate('app.bsky.embed.images#main', v) +export function validateMain(v: V) { + return validate
(v, id, hashMain) } export interface Image { + $type?: $Type<'app.bsky.embed.images', 'image'> image: BlobRef /** Alt text description of the image, for accessibility. */ alt: string aspectRatio?: AppBskyEmbedDefs.AspectRatio - [k: string]: unknown } -export function isImage(v: unknown): v is Image { - return ( - isObj(v) && hasProp(v, '$type') && v.$type === 'app.bsky.embed.images#image' - ) +const hashImage = 'image' + +export function isImage(v: V) { + return is$typed(v, id, hashImage) } -export function validateImage(v: unknown): ValidationResult { - return lexicons.validate('app.bsky.embed.images#image', v) +export function validateImage(v: V) { + return validate(v, id, hashImage) } export interface View { + $type?: $Type<'app.bsky.embed.images', 'view'> images: ViewImage[] - [k: string]: unknown } -export function isView(v: unknown): v is View { - return ( - isObj(v) && hasProp(v, '$type') && v.$type === 'app.bsky.embed.images#view' - ) +const hashView = 'view' + +export function isView(v: V) { + return is$typed(v, id, hashView) } -export function validateView(v: unknown): ValidationResult { - return lexicons.validate('app.bsky.embed.images#view', v) +export function validateView(v: V) { + return validate(v, id, hashView) } export interface ViewImage { + $type?: $Type<'app.bsky.embed.images', 'viewImage'> /** Fully-qualified URL where a thumbnail of the image can be fetched. For example, CDN location provided by the App View. */ thumb: string /** Fully-qualified URL where a large version of the image can be fetched. May or may not be the exact original blob. For example, CDN location provided by the App View. */ @@ -66,17 +68,14 @@ export interface ViewImage { /** Alt text description of the image, for accessibility. */ alt: string aspectRatio?: AppBskyEmbedDefs.AspectRatio - [k: string]: unknown } -export function isViewImage(v: unknown): v is ViewImage { - return ( - isObj(v) && - hasProp(v, '$type') && - v.$type === 'app.bsky.embed.images#viewImage' - ) +const hashViewImage = 'viewImage' + +export function isViewImage(v: V) { + return is$typed(v, id, hashViewImage) } -export function validateViewImage(v: unknown): ValidationResult { - return lexicons.validate('app.bsky.embed.images#viewImage', v) +export function validateViewImage(v: V) { + return validate(v, id, hashViewImage) } diff --git a/packages/api/src/client/types/app/bsky/embed/record.ts b/packages/api/src/client/types/app/bsky/embed/record.ts index a3744c29246..cecabddd8b6 100644 --- a/packages/api/src/client/types/app/bsky/embed/record.ts +++ b/packages/api/src/client/types/app/bsky/embed/record.ts @@ -2,148 +2,141 @@ * GENERATED CODE - DO NOT MODIFY */ import { ValidationResult, BlobRef } from '@atproto/lexicon' -import { isObj, hasProp } from '../../../../util' -import { lexicons } from '../../../../lexicons' import { CID } from 'multiformats/cid' -import * as ComAtprotoRepoStrongRef from '../../../com/atproto/repo/strongRef' -import * as AppBskyFeedDefs from '../feed/defs' -import * as AppBskyGraphDefs from '../graph/defs' -import * as AppBskyLabelerDefs from '../labeler/defs' -import * as AppBskyActorDefs from '../actor/defs' -import * as ComAtprotoLabelDefs from '../../../com/atproto/label/defs' -import * as AppBskyEmbedImages from './images' -import * as AppBskyEmbedVideo from './video' -import * as AppBskyEmbedExternal from './external' -import * as AppBskyEmbedRecordWithMedia from './recordWithMedia' +import { validate as _validate } from '../../../../lexicons' +import { $Type, $Typed, is$typed as _is$typed, OmitKey } from '../../../../util' +import type * as ComAtprotoRepoStrongRef from '../../../com/atproto/repo/strongRef' +import type * as AppBskyFeedDefs from '../feed/defs' +import type * as AppBskyGraphDefs from '../graph/defs' +import type * as AppBskyLabelerDefs from '../labeler/defs' +import type * as AppBskyActorDefs from '../actor/defs' +import type * as ComAtprotoLabelDefs from '../../../com/atproto/label/defs' +import type * as AppBskyEmbedImages from './images' +import type * as AppBskyEmbedVideo from './video' +import type * as AppBskyEmbedExternal from './external' +import type * as AppBskyEmbedRecordWithMedia from './recordWithMedia' + +const is$typed = _is$typed, + validate = _validate +const id = 'app.bsky.embed.record' export interface Main { + $type?: $Type<'app.bsky.embed.record', 'main'> record: ComAtprotoRepoStrongRef.Main - [k: string]: unknown } -export function isMain(v: unknown): v is Main { - return ( - isObj(v) && - hasProp(v, '$type') && - (v.$type === 'app.bsky.embed.record#main' || - v.$type === 'app.bsky.embed.record') - ) +const hashMain = 'main' + +export function isMain(v: V) { + return is$typed(v, id, hashMain) } -export function validateMain(v: unknown): ValidationResult { - return lexicons.validate('app.bsky.embed.record#main', v) +export function validateMain(v: V) { + return validate
(v, id, hashMain) } export interface View { + $type?: $Type<'app.bsky.embed.record', 'view'> record: - | ViewRecord - | ViewNotFound - | ViewBlocked - | ViewDetached - | AppBskyFeedDefs.GeneratorView - | AppBskyGraphDefs.ListView - | AppBskyLabelerDefs.LabelerView - | AppBskyGraphDefs.StarterPackViewBasic - | { $type: string; [k: string]: unknown } - [k: string]: unknown + | $Typed + | $Typed + | $Typed + | $Typed + | $Typed + | $Typed + | $Typed + | $Typed + | { $type: string } } -export function isView(v: unknown): v is View { - return ( - isObj(v) && hasProp(v, '$type') && v.$type === 'app.bsky.embed.record#view' - ) +const hashView = 'view' + +export function isView(v: V) { + return is$typed(v, id, hashView) } -export function validateView(v: unknown): ValidationResult { - return lexicons.validate('app.bsky.embed.record#view', v) +export function validateView(v: V) { + return validate(v, id, hashView) } export interface ViewRecord { + $type?: $Type<'app.bsky.embed.record', 'viewRecord'> uri: string cid: string author: AppBskyActorDefs.ProfileViewBasic /** The record data itself. */ - value: {} + value: { [_ in string]: unknown } labels?: ComAtprotoLabelDefs.Label[] replyCount?: number repostCount?: number likeCount?: number quoteCount?: number embeds?: ( - | AppBskyEmbedImages.View - | AppBskyEmbedVideo.View - | AppBskyEmbedExternal.View - | View - | AppBskyEmbedRecordWithMedia.View - | { $type: string; [k: string]: unknown } + | $Typed + | $Typed + | $Typed + | $Typed + | $Typed + | { $type: string } )[] indexedAt: string - [k: string]: unknown } -export function isViewRecord(v: unknown): v is ViewRecord { - return ( - isObj(v) && - hasProp(v, '$type') && - v.$type === 'app.bsky.embed.record#viewRecord' - ) +const hashViewRecord = 'viewRecord' + +export function isViewRecord(v: V) { + return is$typed(v, id, hashViewRecord) } -export function validateViewRecord(v: unknown): ValidationResult { - return lexicons.validate('app.bsky.embed.record#viewRecord', v) +export function validateViewRecord(v: V) { + return validate(v, id, hashViewRecord) } export interface ViewNotFound { + $type?: $Type<'app.bsky.embed.record', 'viewNotFound'> uri: string notFound: true - [k: string]: unknown } -export function isViewNotFound(v: unknown): v is ViewNotFound { - return ( - isObj(v) && - hasProp(v, '$type') && - v.$type === 'app.bsky.embed.record#viewNotFound' - ) +const hashViewNotFound = 'viewNotFound' + +export function isViewNotFound(v: V) { + return is$typed(v, id, hashViewNotFound) } -export function validateViewNotFound(v: unknown): ValidationResult { - return lexicons.validate('app.bsky.embed.record#viewNotFound', v) +export function validateViewNotFound(v: V) { + return validate(v, id, hashViewNotFound) } export interface ViewBlocked { + $type?: $Type<'app.bsky.embed.record', 'viewBlocked'> uri: string blocked: true author: AppBskyFeedDefs.BlockedAuthor - [k: string]: unknown } -export function isViewBlocked(v: unknown): v is ViewBlocked { - return ( - isObj(v) && - hasProp(v, '$type') && - v.$type === 'app.bsky.embed.record#viewBlocked' - ) +const hashViewBlocked = 'viewBlocked' + +export function isViewBlocked(v: V) { + return is$typed(v, id, hashViewBlocked) } -export function validateViewBlocked(v: unknown): ValidationResult { - return lexicons.validate('app.bsky.embed.record#viewBlocked', v) +export function validateViewBlocked(v: V) { + return validate(v, id, hashViewBlocked) } export interface ViewDetached { + $type?: $Type<'app.bsky.embed.record', 'viewDetached'> uri: string detached: true - [k: string]: unknown } -export function isViewDetached(v: unknown): v is ViewDetached { - return ( - isObj(v) && - hasProp(v, '$type') && - v.$type === 'app.bsky.embed.record#viewDetached' - ) +const hashViewDetached = 'viewDetached' + +export function isViewDetached(v: V) { + return is$typed(v, id, hashViewDetached) } -export function validateViewDetached(v: unknown): ValidationResult { - return lexicons.validate('app.bsky.embed.record#viewDetached', v) +export function validateViewDetached(v: V) { + return validate(v, id, hashViewDetached) } diff --git a/packages/api/src/client/types/app/bsky/embed/recordWithMedia.ts b/packages/api/src/client/types/app/bsky/embed/recordWithMedia.ts index 2b2b3ae6251..ea1e15a8e42 100644 --- a/packages/api/src/client/types/app/bsky/embed/recordWithMedia.ts +++ b/packages/api/src/client/types/app/bsky/embed/recordWithMedia.ts @@ -2,55 +2,54 @@ * GENERATED CODE - DO NOT MODIFY */ import { ValidationResult, BlobRef } from '@atproto/lexicon' -import { isObj, hasProp } from '../../../../util' -import { lexicons } from '../../../../lexicons' import { CID } from 'multiformats/cid' -import * as AppBskyEmbedRecord from './record' -import * as AppBskyEmbedImages from './images' -import * as AppBskyEmbedVideo from './video' -import * as AppBskyEmbedExternal from './external' +import { validate as _validate } from '../../../../lexicons' +import { $Type, $Typed, is$typed as _is$typed, OmitKey } from '../../../../util' +import type * as AppBskyEmbedRecord from './record' +import type * as AppBskyEmbedImages from './images' +import type * as AppBskyEmbedVideo from './video' +import type * as AppBskyEmbedExternal from './external' + +const is$typed = _is$typed, + validate = _validate +const id = 'app.bsky.embed.recordWithMedia' export interface Main { + $type?: $Type<'app.bsky.embed.recordWithMedia', 'main'> record: AppBskyEmbedRecord.Main media: - | AppBskyEmbedImages.Main - | AppBskyEmbedVideo.Main - | AppBskyEmbedExternal.Main - | { $type: string; [k: string]: unknown } - [k: string]: unknown + | $Typed + | $Typed + | $Typed + | { $type: string } } -export function isMain(v: unknown): v is Main { - return ( - isObj(v) && - hasProp(v, '$type') && - (v.$type === 'app.bsky.embed.recordWithMedia#main' || - v.$type === 'app.bsky.embed.recordWithMedia') - ) +const hashMain = 'main' + +export function isMain(v: V) { + return is$typed(v, id, hashMain) } -export function validateMain(v: unknown): ValidationResult { - return lexicons.validate('app.bsky.embed.recordWithMedia#main', v) +export function validateMain(v: V) { + return validate
(v, id, hashMain) } export interface View { + $type?: $Type<'app.bsky.embed.recordWithMedia', 'view'> record: AppBskyEmbedRecord.View media: - | AppBskyEmbedImages.View - | AppBskyEmbedVideo.View - | AppBskyEmbedExternal.View - | { $type: string; [k: string]: unknown } - [k: string]: unknown + | $Typed + | $Typed + | $Typed + | { $type: string } } -export function isView(v: unknown): v is View { - return ( - isObj(v) && - hasProp(v, '$type') && - v.$type === 'app.bsky.embed.recordWithMedia#view' - ) +const hashView = 'view' + +export function isView(v: V) { + return is$typed(v, id, hashView) } -export function validateView(v: unknown): ValidationResult { - return lexicons.validate('app.bsky.embed.recordWithMedia#view', v) +export function validateView(v: V) { + return validate(v, id, hashView) } diff --git a/packages/api/src/client/types/app/bsky/embed/video.ts b/packages/api/src/client/types/app/bsky/embed/video.ts index 2be451b85ed..366d2735d99 100644 --- a/packages/api/src/client/types/app/bsky/embed/video.ts +++ b/packages/api/src/client/types/app/bsky/embed/video.ts @@ -2,66 +2,65 @@ * GENERATED CODE - DO NOT MODIFY */ import { ValidationResult, BlobRef } from '@atproto/lexicon' -import { isObj, hasProp } from '../../../../util' -import { lexicons } from '../../../../lexicons' import { CID } from 'multiformats/cid' -import * as AppBskyEmbedDefs from './defs' +import { validate as _validate } from '../../../../lexicons' +import { $Type, $Typed, is$typed as _is$typed, OmitKey } from '../../../../util' +import type * as AppBskyEmbedDefs from './defs' + +const is$typed = _is$typed, + validate = _validate +const id = 'app.bsky.embed.video' export interface Main { + $type?: $Type<'app.bsky.embed.video', 'main'> video: BlobRef captions?: Caption[] /** Alt text description of the video, for accessibility. */ alt?: string aspectRatio?: AppBskyEmbedDefs.AspectRatio - [k: string]: unknown } -export function isMain(v: unknown): v is Main { - return ( - isObj(v) && - hasProp(v, '$type') && - (v.$type === 'app.bsky.embed.video#main' || - v.$type === 'app.bsky.embed.video') - ) +const hashMain = 'main' + +export function isMain(v: V) { + return is$typed(v, id, hashMain) } -export function validateMain(v: unknown): ValidationResult { - return lexicons.validate('app.bsky.embed.video#main', v) +export function validateMain(v: V) { + return validate
(v, id, hashMain) } export interface Caption { + $type?: $Type<'app.bsky.embed.video', 'caption'> lang: string file: BlobRef - [k: string]: unknown } -export function isCaption(v: unknown): v is Caption { - return ( - isObj(v) && - hasProp(v, '$type') && - v.$type === 'app.bsky.embed.video#caption' - ) +const hashCaption = 'caption' + +export function isCaption(v: V) { + return is$typed(v, id, hashCaption) } -export function validateCaption(v: unknown): ValidationResult { - return lexicons.validate('app.bsky.embed.video#caption', v) +export function validateCaption(v: V) { + return validate(v, id, hashCaption) } export interface View { + $type?: $Type<'app.bsky.embed.video', 'view'> cid: string playlist: string thumbnail?: string alt?: string aspectRatio?: AppBskyEmbedDefs.AspectRatio - [k: string]: unknown } -export function isView(v: unknown): v is View { - return ( - isObj(v) && hasProp(v, '$type') && v.$type === 'app.bsky.embed.video#view' - ) +const hashView = 'view' + +export function isView(v: V) { + return is$typed(v, id, hashView) } -export function validateView(v: unknown): ValidationResult { - return lexicons.validate('app.bsky.embed.video#view', v) +export function validateView(v: V) { + return validate(v, id, hashView) } diff --git a/packages/api/src/client/types/app/bsky/feed/defs.ts b/packages/api/src/client/types/app/bsky/feed/defs.ts index 760c634d35f..4d309746110 100644 --- a/packages/api/src/client/types/app/bsky/feed/defs.ts +++ b/packages/api/src/client/types/app/bsky/feed/defs.ts @@ -2,31 +2,36 @@ * GENERATED CODE - DO NOT MODIFY */ import { ValidationResult, BlobRef } from '@atproto/lexicon' -import { isObj, hasProp } from '../../../../util' -import { lexicons } from '../../../../lexicons' import { CID } from 'multiformats/cid' -import * as AppBskyActorDefs from '../actor/defs' -import * as AppBskyEmbedImages from '../embed/images' -import * as AppBskyEmbedVideo from '../embed/video' -import * as AppBskyEmbedExternal from '../embed/external' -import * as AppBskyEmbedRecord from '../embed/record' -import * as AppBskyEmbedRecordWithMedia from '../embed/recordWithMedia' -import * as ComAtprotoLabelDefs from '../../../com/atproto/label/defs' -import * as AppBskyRichtextFacet from '../richtext/facet' -import * as AppBskyGraphDefs from '../graph/defs' +import { validate as _validate } from '../../../../lexicons' +import { $Type, $Typed, is$typed as _is$typed, OmitKey } from '../../../../util' +import type * as AppBskyActorDefs from '../actor/defs' +import type * as AppBskyEmbedImages from '../embed/images' +import type * as AppBskyEmbedVideo from '../embed/video' +import type * as AppBskyEmbedExternal from '../embed/external' +import type * as AppBskyEmbedRecord from '../embed/record' +import type * as AppBskyEmbedRecordWithMedia from '../embed/recordWithMedia' +import type * as ComAtprotoLabelDefs from '../../../com/atproto/label/defs' +import type * as AppBskyRichtextFacet from '../richtext/facet' +import type * as AppBskyGraphDefs from '../graph/defs' + +const is$typed = _is$typed, + validate = _validate +const id = 'app.bsky.feed.defs' export interface PostView { + $type?: $Type<'app.bsky.feed.defs', 'postView'> uri: string cid: string author: AppBskyActorDefs.ProfileViewBasic - record: {} + record: { [_ in string]: unknown } embed?: - | AppBskyEmbedImages.View - | AppBskyEmbedVideo.View - | AppBskyEmbedExternal.View - | AppBskyEmbedRecord.View - | AppBskyEmbedRecordWithMedia.View - | { $type: string; [k: string]: unknown } + | $Typed + | $Typed + | $Typed + | $Typed + | $Typed + | { $type: string } replyCount?: number repostCount?: number likeCount?: number @@ -35,225 +40,207 @@ export interface PostView { viewer?: ViewerState labels?: ComAtprotoLabelDefs.Label[] threadgate?: ThreadgateView - [k: string]: unknown } -export function isPostView(v: unknown): v is PostView { - return ( - isObj(v) && hasProp(v, '$type') && v.$type === 'app.bsky.feed.defs#postView' - ) +const hashPostView = 'postView' + +export function isPostView(v: V) { + return is$typed(v, id, hashPostView) } -export function validatePostView(v: unknown): ValidationResult { - return lexicons.validate('app.bsky.feed.defs#postView', v) +export function validatePostView(v: V) { + return validate(v, id, hashPostView) } /** Metadata about the requesting account's relationship with the subject content. Only has meaningful content for authed requests. */ export interface ViewerState { + $type?: $Type<'app.bsky.feed.defs', 'viewerState'> repost?: string like?: string threadMuted?: boolean replyDisabled?: boolean embeddingDisabled?: boolean pinned?: boolean - [k: string]: unknown } -export function isViewerState(v: unknown): v is ViewerState { - return ( - isObj(v) && - hasProp(v, '$type') && - v.$type === 'app.bsky.feed.defs#viewerState' - ) +const hashViewerState = 'viewerState' + +export function isViewerState(v: V) { + return is$typed(v, id, hashViewerState) } -export function validateViewerState(v: unknown): ValidationResult { - return lexicons.validate('app.bsky.feed.defs#viewerState', v) +export function validateViewerState(v: V) { + return validate(v, id, hashViewerState) } /** Metadata about this post within the context of the thread it is in. */ export interface ThreadContext { + $type?: $Type<'app.bsky.feed.defs', 'threadContext'> rootAuthorLike?: string - [k: string]: unknown } -export function isThreadContext(v: unknown): v is ThreadContext { - return ( - isObj(v) && - hasProp(v, '$type') && - v.$type === 'app.bsky.feed.defs#threadContext' - ) +const hashThreadContext = 'threadContext' + +export function isThreadContext(v: V) { + return is$typed(v, id, hashThreadContext) } -export function validateThreadContext(v: unknown): ValidationResult { - return lexicons.validate('app.bsky.feed.defs#threadContext', v) +export function validateThreadContext(v: V) { + return validate(v, id, hashThreadContext) } export interface FeedViewPost { + $type?: $Type<'app.bsky.feed.defs', 'feedViewPost'> post: PostView reply?: ReplyRef - reason?: ReasonRepost | ReasonPin | { $type: string; [k: string]: unknown } + reason?: $Typed | $Typed | { $type: string } /** Context provided by feed generator that may be passed back alongside interactions. */ feedContext?: string - [k: string]: unknown } -export function isFeedViewPost(v: unknown): v is FeedViewPost { - return ( - isObj(v) && - hasProp(v, '$type') && - v.$type === 'app.bsky.feed.defs#feedViewPost' - ) +const hashFeedViewPost = 'feedViewPost' + +export function isFeedViewPost(v: V) { + return is$typed(v, id, hashFeedViewPost) } -export function validateFeedViewPost(v: unknown): ValidationResult { - return lexicons.validate('app.bsky.feed.defs#feedViewPost', v) +export function validateFeedViewPost(v: V) { + return validate(v, id, hashFeedViewPost) } export interface ReplyRef { + $type?: $Type<'app.bsky.feed.defs', 'replyRef'> root: - | PostView - | NotFoundPost - | BlockedPost - | { $type: string; [k: string]: unknown } + | $Typed + | $Typed + | $Typed + | { $type: string } parent: - | PostView - | NotFoundPost - | BlockedPost - | { $type: string; [k: string]: unknown } + | $Typed + | $Typed + | $Typed + | { $type: string } grandparentAuthor?: AppBskyActorDefs.ProfileViewBasic - [k: string]: unknown } -export function isReplyRef(v: unknown): v is ReplyRef { - return ( - isObj(v) && hasProp(v, '$type') && v.$type === 'app.bsky.feed.defs#replyRef' - ) +const hashReplyRef = 'replyRef' + +export function isReplyRef(v: V) { + return is$typed(v, id, hashReplyRef) } -export function validateReplyRef(v: unknown): ValidationResult { - return lexicons.validate('app.bsky.feed.defs#replyRef', v) +export function validateReplyRef(v: V) { + return validate(v, id, hashReplyRef) } export interface ReasonRepost { + $type?: $Type<'app.bsky.feed.defs', 'reasonRepost'> by: AppBskyActorDefs.ProfileViewBasic indexedAt: string - [k: string]: unknown } -export function isReasonRepost(v: unknown): v is ReasonRepost { - return ( - isObj(v) && - hasProp(v, '$type') && - v.$type === 'app.bsky.feed.defs#reasonRepost' - ) +const hashReasonRepost = 'reasonRepost' + +export function isReasonRepost(v: V) { + return is$typed(v, id, hashReasonRepost) } -export function validateReasonRepost(v: unknown): ValidationResult { - return lexicons.validate('app.bsky.feed.defs#reasonRepost', v) +export function validateReasonRepost(v: V) { + return validate(v, id, hashReasonRepost) } export interface ReasonPin { - [k: string]: unknown + $type?: $Type<'app.bsky.feed.defs', 'reasonPin'> } -export function isReasonPin(v: unknown): v is ReasonPin { - return ( - isObj(v) && - hasProp(v, '$type') && - v.$type === 'app.bsky.feed.defs#reasonPin' - ) +const hashReasonPin = 'reasonPin' + +export function isReasonPin(v: V) { + return is$typed(v, id, hashReasonPin) } -export function validateReasonPin(v: unknown): ValidationResult { - return lexicons.validate('app.bsky.feed.defs#reasonPin', v) +export function validateReasonPin(v: V) { + return validate(v, id, hashReasonPin) } export interface ThreadViewPost { + $type?: $Type<'app.bsky.feed.defs', 'threadViewPost'> post: PostView parent?: - | ThreadViewPost - | NotFoundPost - | BlockedPost - | { $type: string; [k: string]: unknown } + | $Typed + | $Typed + | $Typed + | { $type: string } replies?: ( - | ThreadViewPost - | NotFoundPost - | BlockedPost - | { $type: string; [k: string]: unknown } + | $Typed + | $Typed + | $Typed + | { $type: string } )[] threadContext?: ThreadContext - [k: string]: unknown } -export function isThreadViewPost(v: unknown): v is ThreadViewPost { - return ( - isObj(v) && - hasProp(v, '$type') && - v.$type === 'app.bsky.feed.defs#threadViewPost' - ) +const hashThreadViewPost = 'threadViewPost' + +export function isThreadViewPost(v: V) { + return is$typed(v, id, hashThreadViewPost) } -export function validateThreadViewPost(v: unknown): ValidationResult { - return lexicons.validate('app.bsky.feed.defs#threadViewPost', v) +export function validateThreadViewPost(v: V) { + return validate(v, id, hashThreadViewPost) } export interface NotFoundPost { + $type?: $Type<'app.bsky.feed.defs', 'notFoundPost'> uri: string notFound: true - [k: string]: unknown } -export function isNotFoundPost(v: unknown): v is NotFoundPost { - return ( - isObj(v) && - hasProp(v, '$type') && - v.$type === 'app.bsky.feed.defs#notFoundPost' - ) +const hashNotFoundPost = 'notFoundPost' + +export function isNotFoundPost(v: V) { + return is$typed(v, id, hashNotFoundPost) } -export function validateNotFoundPost(v: unknown): ValidationResult { - return lexicons.validate('app.bsky.feed.defs#notFoundPost', v) +export function validateNotFoundPost(v: V) { + return validate(v, id, hashNotFoundPost) } export interface BlockedPost { + $type?: $Type<'app.bsky.feed.defs', 'blockedPost'> uri: string blocked: true author: BlockedAuthor - [k: string]: unknown } -export function isBlockedPost(v: unknown): v is BlockedPost { - return ( - isObj(v) && - hasProp(v, '$type') && - v.$type === 'app.bsky.feed.defs#blockedPost' - ) +const hashBlockedPost = 'blockedPost' + +export function isBlockedPost(v: V) { + return is$typed(v, id, hashBlockedPost) } -export function validateBlockedPost(v: unknown): ValidationResult { - return lexicons.validate('app.bsky.feed.defs#blockedPost', v) +export function validateBlockedPost(v: V) { + return validate(v, id, hashBlockedPost) } export interface BlockedAuthor { + $type?: $Type<'app.bsky.feed.defs', 'blockedAuthor'> did: string viewer?: AppBskyActorDefs.ViewerState - [k: string]: unknown } -export function isBlockedAuthor(v: unknown): v is BlockedAuthor { - return ( - isObj(v) && - hasProp(v, '$type') && - v.$type === 'app.bsky.feed.defs#blockedAuthor' - ) +const hashBlockedAuthor = 'blockedAuthor' + +export function isBlockedAuthor(v: V) { + return is$typed(v, id, hashBlockedAuthor) } -export function validateBlockedAuthor(v: unknown): ValidationResult { - return lexicons.validate('app.bsky.feed.defs#blockedAuthor', v) +export function validateBlockedAuthor(v: V) { + return validate(v, id, hashBlockedAuthor) } export interface GeneratorView { + $type?: $Type<'app.bsky.feed.defs', 'generatorView'> uri: string cid: string did: string @@ -271,115 +258,103 @@ export interface GeneratorView { | 'app.bsky.feed.defs#contentModeVideo' | (string & {}) indexedAt: string - [k: string]: unknown } -export function isGeneratorView(v: unknown): v is GeneratorView { - return ( - isObj(v) && - hasProp(v, '$type') && - v.$type === 'app.bsky.feed.defs#generatorView' - ) +const hashGeneratorView = 'generatorView' + +export function isGeneratorView(v: V) { + return is$typed(v, id, hashGeneratorView) } -export function validateGeneratorView(v: unknown): ValidationResult { - return lexicons.validate('app.bsky.feed.defs#generatorView', v) +export function validateGeneratorView(v: V) { + return validate(v, id, hashGeneratorView) } export interface GeneratorViewerState { + $type?: $Type<'app.bsky.feed.defs', 'generatorViewerState'> like?: string - [k: string]: unknown } -export function isGeneratorViewerState(v: unknown): v is GeneratorViewerState { - return ( - isObj(v) && - hasProp(v, '$type') && - v.$type === 'app.bsky.feed.defs#generatorViewerState' - ) +const hashGeneratorViewerState = 'generatorViewerState' + +export function isGeneratorViewerState(v: V) { + return is$typed(v, id, hashGeneratorViewerState) } -export function validateGeneratorViewerState(v: unknown): ValidationResult { - return lexicons.validate('app.bsky.feed.defs#generatorViewerState', v) +export function validateGeneratorViewerState(v: V) { + return validate(v, id, hashGeneratorViewerState) } export interface SkeletonFeedPost { + $type?: $Type<'app.bsky.feed.defs', 'skeletonFeedPost'> post: string reason?: - | SkeletonReasonRepost - | SkeletonReasonPin - | { $type: string; [k: string]: unknown } + | $Typed + | $Typed + | { $type: string } /** Context that will be passed through to client and may be passed to feed generator back alongside interactions. */ feedContext?: string - [k: string]: unknown } -export function isSkeletonFeedPost(v: unknown): v is SkeletonFeedPost { - return ( - isObj(v) && - hasProp(v, '$type') && - v.$type === 'app.bsky.feed.defs#skeletonFeedPost' - ) +const hashSkeletonFeedPost = 'skeletonFeedPost' + +export function isSkeletonFeedPost(v: V) { + return is$typed(v, id, hashSkeletonFeedPost) } -export function validateSkeletonFeedPost(v: unknown): ValidationResult { - return lexicons.validate('app.bsky.feed.defs#skeletonFeedPost', v) +export function validateSkeletonFeedPost(v: V) { + return validate(v, id, hashSkeletonFeedPost) } export interface SkeletonReasonRepost { + $type?: $Type<'app.bsky.feed.defs', 'skeletonReasonRepost'> repost: string - [k: string]: unknown } -export function isSkeletonReasonRepost(v: unknown): v is SkeletonReasonRepost { - return ( - isObj(v) && - hasProp(v, '$type') && - v.$type === 'app.bsky.feed.defs#skeletonReasonRepost' - ) +const hashSkeletonReasonRepost = 'skeletonReasonRepost' + +export function isSkeletonReasonRepost(v: V) { + return is$typed(v, id, hashSkeletonReasonRepost) } -export function validateSkeletonReasonRepost(v: unknown): ValidationResult { - return lexicons.validate('app.bsky.feed.defs#skeletonReasonRepost', v) +export function validateSkeletonReasonRepost(v: V) { + return validate(v, id, hashSkeletonReasonRepost) } export interface SkeletonReasonPin { - [k: string]: unknown + $type?: $Type<'app.bsky.feed.defs', 'skeletonReasonPin'> } -export function isSkeletonReasonPin(v: unknown): v is SkeletonReasonPin { - return ( - isObj(v) && - hasProp(v, '$type') && - v.$type === 'app.bsky.feed.defs#skeletonReasonPin' - ) +const hashSkeletonReasonPin = 'skeletonReasonPin' + +export function isSkeletonReasonPin(v: V) { + return is$typed(v, id, hashSkeletonReasonPin) } -export function validateSkeletonReasonPin(v: unknown): ValidationResult { - return lexicons.validate('app.bsky.feed.defs#skeletonReasonPin', v) +export function validateSkeletonReasonPin(v: V) { + return validate(v, id, hashSkeletonReasonPin) } export interface ThreadgateView { + $type?: $Type<'app.bsky.feed.defs', 'threadgateView'> uri?: string cid?: string - record?: {} + record?: { [_ in string]: unknown } lists?: AppBskyGraphDefs.ListViewBasic[] - [k: string]: unknown } -export function isThreadgateView(v: unknown): v is ThreadgateView { - return ( - isObj(v) && - hasProp(v, '$type') && - v.$type === 'app.bsky.feed.defs#threadgateView' - ) +const hashThreadgateView = 'threadgateView' + +export function isThreadgateView(v: V) { + return is$typed(v, id, hashThreadgateView) } -export function validateThreadgateView(v: unknown): ValidationResult { - return lexicons.validate('app.bsky.feed.defs#threadgateView', v) +export function validateThreadgateView(v: V) { + return validate(v, id, hashThreadgateView) } export interface Interaction { + $type?: $Type<'app.bsky.feed.defs', 'interaction'> item?: string event?: | 'app.bsky.feed.defs#requestLess' @@ -397,47 +372,43 @@ export interface Interaction { | (string & {}) /** Context on a feed item that was originally supplied by the feed generator on getFeedSkeleton. */ feedContext?: string - [k: string]: unknown } -export function isInteraction(v: unknown): v is Interaction { - return ( - isObj(v) && - hasProp(v, '$type') && - v.$type === 'app.bsky.feed.defs#interaction' - ) +const hashInteraction = 'interaction' + +export function isInteraction(v: V) { + return is$typed(v, id, hashInteraction) } -export function validateInteraction(v: unknown): ValidationResult { - return lexicons.validate('app.bsky.feed.defs#interaction', v) +export function validateInteraction(v: V) { + return validate(v, id, hashInteraction) } /** Request that less content like the given feed item be shown in the feed */ -export const REQUESTLESS = 'app.bsky.feed.defs#requestLess' +export const REQUESTLESS = `${id}#requestLess` /** Request that more content like the given feed item be shown in the feed */ -export const REQUESTMORE = 'app.bsky.feed.defs#requestMore' +export const REQUESTMORE = `${id}#requestMore` /** User clicked through to the feed item */ -export const CLICKTHROUGHITEM = 'app.bsky.feed.defs#clickthroughItem' +export const CLICKTHROUGHITEM = `${id}#clickthroughItem` /** User clicked through to the author of the feed item */ -export const CLICKTHROUGHAUTHOR = 'app.bsky.feed.defs#clickthroughAuthor' +export const CLICKTHROUGHAUTHOR = `${id}#clickthroughAuthor` /** User clicked through to the reposter of the feed item */ -export const CLICKTHROUGHREPOSTER = 'app.bsky.feed.defs#clickthroughReposter' +export const CLICKTHROUGHREPOSTER = `${id}#clickthroughReposter` /** User clicked through to the embedded content of the feed item */ -export const CLICKTHROUGHEMBED = 'app.bsky.feed.defs#clickthroughEmbed' +export const CLICKTHROUGHEMBED = `${id}#clickthroughEmbed` /** Declares the feed generator returns any types of posts. */ -export const CONTENTMODEUNSPECIFIED = - 'app.bsky.feed.defs#contentModeUnspecified' +export const CONTENTMODEUNSPECIFIED = `${id}#contentModeUnspecified` /** Declares the feed generator returns posts containing app.bsky.embed.video embeds. */ -export const CONTENTMODEVIDEO = 'app.bsky.feed.defs#contentModeVideo' +export const CONTENTMODEVIDEO = `${id}#contentModeVideo` /** Feed item was seen by user */ -export const INTERACTIONSEEN = 'app.bsky.feed.defs#interactionSeen' +export const INTERACTIONSEEN = `${id}#interactionSeen` /** User liked the feed item */ -export const INTERACTIONLIKE = 'app.bsky.feed.defs#interactionLike' +export const INTERACTIONLIKE = `${id}#interactionLike` /** User reposted the feed item */ -export const INTERACTIONREPOST = 'app.bsky.feed.defs#interactionRepost' +export const INTERACTIONREPOST = `${id}#interactionRepost` /** User replied to the feed item */ -export const INTERACTIONREPLY = 'app.bsky.feed.defs#interactionReply' +export const INTERACTIONREPLY = `${id}#interactionReply` /** User quoted the feed item */ -export const INTERACTIONQUOTE = 'app.bsky.feed.defs#interactionQuote' +export const INTERACTIONQUOTE = `${id}#interactionQuote` /** User shared the feed item */ -export const INTERACTIONSHARE = 'app.bsky.feed.defs#interactionShare' +export const INTERACTIONSHARE = `${id}#interactionShare` diff --git a/packages/api/src/client/types/app/bsky/feed/describeFeedGenerator.ts b/packages/api/src/client/types/app/bsky/feed/describeFeedGenerator.ts index b974f496144..a3f7874ed85 100644 --- a/packages/api/src/client/types/app/bsky/feed/describeFeedGenerator.ts +++ b/packages/api/src/client/types/app/bsky/feed/describeFeedGenerator.ts @@ -3,9 +3,13 @@ */ import { HeadersMap, XRPCError } from '@atproto/xrpc' import { ValidationResult, BlobRef } from '@atproto/lexicon' -import { isObj, hasProp } from '../../../../util' -import { lexicons } from '../../../../lexicons' import { CID } from 'multiformats/cid' +import { validate as _validate } from '../../../../lexicons' +import { $Type, $Typed, is$typed as _is$typed, OmitKey } from '../../../../util' + +const is$typed = _is$typed, + validate = _validate +const id = 'app.bsky.feed.describeFeedGenerator' export interface QueryParams {} @@ -15,7 +19,6 @@ export interface OutputSchema { did: string feeds: Feed[] links?: Links - [k: string]: unknown } export interface CallOptions { @@ -34,36 +37,32 @@ export function toKnownErr(e: any) { } export interface Feed { + $type?: $Type<'app.bsky.feed.describeFeedGenerator', 'feed'> uri: string - [k: string]: unknown } -export function isFeed(v: unknown): v is Feed { - return ( - isObj(v) && - hasProp(v, '$type') && - v.$type === 'app.bsky.feed.describeFeedGenerator#feed' - ) +const hashFeed = 'feed' + +export function isFeed(v: V) { + return is$typed(v, id, hashFeed) } -export function validateFeed(v: unknown): ValidationResult { - return lexicons.validate('app.bsky.feed.describeFeedGenerator#feed', v) +export function validateFeed(v: V) { + return validate(v, id, hashFeed) } export interface Links { + $type?: $Type<'app.bsky.feed.describeFeedGenerator', 'links'> privacyPolicy?: string termsOfService?: string - [k: string]: unknown } -export function isLinks(v: unknown): v is Links { - return ( - isObj(v) && - hasProp(v, '$type') && - v.$type === 'app.bsky.feed.describeFeedGenerator#links' - ) +const hashLinks = 'links' + +export function isLinks(v: V) { + return is$typed(v, id, hashLinks) } -export function validateLinks(v: unknown): ValidationResult { - return lexicons.validate('app.bsky.feed.describeFeedGenerator#links', v) +export function validateLinks(v: V) { + return validate(v, id, hashLinks) } diff --git a/packages/api/src/client/types/app/bsky/feed/generator.ts b/packages/api/src/client/types/app/bsky/feed/generator.ts index 8e1dd9e9c80..6b6f7113287 100644 --- a/packages/api/src/client/types/app/bsky/feed/generator.ts +++ b/packages/api/src/client/types/app/bsky/feed/generator.ts @@ -2,13 +2,18 @@ * GENERATED CODE - DO NOT MODIFY */ import { ValidationResult, BlobRef } from '@atproto/lexicon' -import { isObj, hasProp } from '../../../../util' -import { lexicons } from '../../../../lexicons' import { CID } from 'multiformats/cid' -import * as AppBskyRichtextFacet from '../richtext/facet' -import * as ComAtprotoLabelDefs from '../../../com/atproto/label/defs' +import { validate as _validate } from '../../../../lexicons' +import { $Type, $Typed, is$typed as _is$typed, OmitKey } from '../../../../util' +import type * as AppBskyRichtextFacet from '../richtext/facet' +import type * as ComAtprotoLabelDefs from '../../../com/atproto/label/defs' + +const is$typed = _is$typed, + validate = _validate +const id = 'app.bsky.feed.generator' export interface Record { + $type: $Type<'app.bsky.feed.generator', 'main'> did: string displayName: string description?: string @@ -16,9 +21,7 @@ export interface Record { avatar?: BlobRef /** Declaration that a feed accepts feedback interactions from a client through app.bsky.feed.sendInteractions */ acceptsInteractions?: boolean - labels?: - | ComAtprotoLabelDefs.SelfLabels - | { $type: string; [k: string]: unknown } + labels?: $Typed | { $type: string } contentMode?: | 'app.bsky.feed.defs#contentModeUnspecified' | 'app.bsky.feed.defs#contentModeVideo' @@ -27,15 +30,12 @@ export interface Record { [k: string]: unknown } -export function isRecord(v: unknown): v is Record { - return ( - isObj(v) && - hasProp(v, '$type') && - (v.$type === 'app.bsky.feed.generator#main' || - v.$type === 'app.bsky.feed.generator') - ) +const hashRecord = 'main' + +export function isRecord(v: V) { + return is$typed(v, id, hashRecord) } -export function validateRecord(v: unknown): ValidationResult { - return lexicons.validate('app.bsky.feed.generator#main', v) +export function validateRecord(v: V) { + return validate(v, id, hashRecord, true) } diff --git a/packages/api/src/client/types/app/bsky/feed/getActorFeeds.ts b/packages/api/src/client/types/app/bsky/feed/getActorFeeds.ts index e8a0cb94bd3..eabd755c73d 100644 --- a/packages/api/src/client/types/app/bsky/feed/getActorFeeds.ts +++ b/packages/api/src/client/types/app/bsky/feed/getActorFeeds.ts @@ -3,10 +3,14 @@ */ import { HeadersMap, XRPCError } from '@atproto/xrpc' import { ValidationResult, BlobRef } from '@atproto/lexicon' -import { isObj, hasProp } from '../../../../util' -import { lexicons } from '../../../../lexicons' import { CID } from 'multiformats/cid' -import * as AppBskyFeedDefs from './defs' +import { validate as _validate } from '../../../../lexicons' +import { $Type, $Typed, is$typed as _is$typed, OmitKey } from '../../../../util' +import type * as AppBskyFeedDefs from './defs' + +const is$typed = _is$typed, + validate = _validate +const id = 'app.bsky.feed.getActorFeeds' export interface QueryParams { actor: string @@ -19,7 +23,6 @@ export type InputSchema = undefined export interface OutputSchema { cursor?: string feeds: AppBskyFeedDefs.GeneratorView[] - [k: string]: unknown } export interface CallOptions { diff --git a/packages/api/src/client/types/app/bsky/feed/getActorLikes.ts b/packages/api/src/client/types/app/bsky/feed/getActorLikes.ts index 1da239d96f3..aa69ac0a7ec 100644 --- a/packages/api/src/client/types/app/bsky/feed/getActorLikes.ts +++ b/packages/api/src/client/types/app/bsky/feed/getActorLikes.ts @@ -3,10 +3,14 @@ */ import { HeadersMap, XRPCError } from '@atproto/xrpc' import { ValidationResult, BlobRef } from '@atproto/lexicon' -import { isObj, hasProp } from '../../../../util' -import { lexicons } from '../../../../lexicons' import { CID } from 'multiformats/cid' -import * as AppBskyFeedDefs from './defs' +import { validate as _validate } from '../../../../lexicons' +import { $Type, $Typed, is$typed as _is$typed, OmitKey } from '../../../../util' +import type * as AppBskyFeedDefs from './defs' + +const is$typed = _is$typed, + validate = _validate +const id = 'app.bsky.feed.getActorLikes' export interface QueryParams { actor: string @@ -19,7 +23,6 @@ export type InputSchema = undefined export interface OutputSchema { cursor?: string feed: AppBskyFeedDefs.FeedViewPost[] - [k: string]: unknown } export interface CallOptions { diff --git a/packages/api/src/client/types/app/bsky/feed/getAuthorFeed.ts b/packages/api/src/client/types/app/bsky/feed/getAuthorFeed.ts index 8fc58a4e46c..8ffe5a891b7 100644 --- a/packages/api/src/client/types/app/bsky/feed/getAuthorFeed.ts +++ b/packages/api/src/client/types/app/bsky/feed/getAuthorFeed.ts @@ -3,10 +3,14 @@ */ import { HeadersMap, XRPCError } from '@atproto/xrpc' import { ValidationResult, BlobRef } from '@atproto/lexicon' -import { isObj, hasProp } from '../../../../util' -import { lexicons } from '../../../../lexicons' import { CID } from 'multiformats/cid' -import * as AppBskyFeedDefs from './defs' +import { validate as _validate } from '../../../../lexicons' +import { $Type, $Typed, is$typed as _is$typed, OmitKey } from '../../../../util' +import type * as AppBskyFeedDefs from './defs' + +const is$typed = _is$typed, + validate = _validate +const id = 'app.bsky.feed.getAuthorFeed' export interface QueryParams { actor: string @@ -28,7 +32,6 @@ export type InputSchema = undefined export interface OutputSchema { cursor?: string feed: AppBskyFeedDefs.FeedViewPost[] - [k: string]: unknown } export interface CallOptions { diff --git a/packages/api/src/client/types/app/bsky/feed/getFeed.ts b/packages/api/src/client/types/app/bsky/feed/getFeed.ts index b61a1248e2a..8996d2a9f67 100644 --- a/packages/api/src/client/types/app/bsky/feed/getFeed.ts +++ b/packages/api/src/client/types/app/bsky/feed/getFeed.ts @@ -3,10 +3,14 @@ */ import { HeadersMap, XRPCError } from '@atproto/xrpc' import { ValidationResult, BlobRef } from '@atproto/lexicon' -import { isObj, hasProp } from '../../../../util' -import { lexicons } from '../../../../lexicons' import { CID } from 'multiformats/cid' -import * as AppBskyFeedDefs from './defs' +import { validate as _validate } from '../../../../lexicons' +import { $Type, $Typed, is$typed as _is$typed, OmitKey } from '../../../../util' +import type * as AppBskyFeedDefs from './defs' + +const is$typed = _is$typed, + validate = _validate +const id = 'app.bsky.feed.getFeed' export interface QueryParams { feed: string @@ -19,7 +23,6 @@ export type InputSchema = undefined export interface OutputSchema { cursor?: string feed: AppBskyFeedDefs.FeedViewPost[] - [k: string]: unknown } export interface CallOptions { diff --git a/packages/api/src/client/types/app/bsky/feed/getFeedGenerator.ts b/packages/api/src/client/types/app/bsky/feed/getFeedGenerator.ts index 8d222f8d521..326a0d934bf 100644 --- a/packages/api/src/client/types/app/bsky/feed/getFeedGenerator.ts +++ b/packages/api/src/client/types/app/bsky/feed/getFeedGenerator.ts @@ -3,10 +3,14 @@ */ import { HeadersMap, XRPCError } from '@atproto/xrpc' import { ValidationResult, BlobRef } from '@atproto/lexicon' -import { isObj, hasProp } from '../../../../util' -import { lexicons } from '../../../../lexicons' import { CID } from 'multiformats/cid' -import * as AppBskyFeedDefs from './defs' +import { validate as _validate } from '../../../../lexicons' +import { $Type, $Typed, is$typed as _is$typed, OmitKey } from '../../../../util' +import type * as AppBskyFeedDefs from './defs' + +const is$typed = _is$typed, + validate = _validate +const id = 'app.bsky.feed.getFeedGenerator' export interface QueryParams { /** AT-URI of the feed generator record. */ @@ -21,7 +25,6 @@ export interface OutputSchema { isOnline: boolean /** Indicates whether the feed generator service is compatible with the record declaration. */ isValid: boolean - [k: string]: unknown } export interface CallOptions { diff --git a/packages/api/src/client/types/app/bsky/feed/getFeedGenerators.ts b/packages/api/src/client/types/app/bsky/feed/getFeedGenerators.ts index 1d99fd6608c..5bdf801e0c7 100644 --- a/packages/api/src/client/types/app/bsky/feed/getFeedGenerators.ts +++ b/packages/api/src/client/types/app/bsky/feed/getFeedGenerators.ts @@ -3,10 +3,14 @@ */ import { HeadersMap, XRPCError } from '@atproto/xrpc' import { ValidationResult, BlobRef } from '@atproto/lexicon' -import { isObj, hasProp } from '../../../../util' -import { lexicons } from '../../../../lexicons' import { CID } from 'multiformats/cid' -import * as AppBskyFeedDefs from './defs' +import { validate as _validate } from '../../../../lexicons' +import { $Type, $Typed, is$typed as _is$typed, OmitKey } from '../../../../util' +import type * as AppBskyFeedDefs from './defs' + +const is$typed = _is$typed, + validate = _validate +const id = 'app.bsky.feed.getFeedGenerators' export interface QueryParams { feeds: string[] @@ -16,7 +20,6 @@ export type InputSchema = undefined export interface OutputSchema { feeds: AppBskyFeedDefs.GeneratorView[] - [k: string]: unknown } export interface CallOptions { diff --git a/packages/api/src/client/types/app/bsky/feed/getFeedSkeleton.ts b/packages/api/src/client/types/app/bsky/feed/getFeedSkeleton.ts index bf859cc1e87..e826c7aaf46 100644 --- a/packages/api/src/client/types/app/bsky/feed/getFeedSkeleton.ts +++ b/packages/api/src/client/types/app/bsky/feed/getFeedSkeleton.ts @@ -3,10 +3,14 @@ */ import { HeadersMap, XRPCError } from '@atproto/xrpc' import { ValidationResult, BlobRef } from '@atproto/lexicon' -import { isObj, hasProp } from '../../../../util' -import { lexicons } from '../../../../lexicons' import { CID } from 'multiformats/cid' -import * as AppBskyFeedDefs from './defs' +import { validate as _validate } from '../../../../lexicons' +import { $Type, $Typed, is$typed as _is$typed, OmitKey } from '../../../../util' +import type * as AppBskyFeedDefs from './defs' + +const is$typed = _is$typed, + validate = _validate +const id = 'app.bsky.feed.getFeedSkeleton' export interface QueryParams { /** Reference to feed generator record describing the specific feed being requested. */ @@ -20,7 +24,6 @@ export type InputSchema = undefined export interface OutputSchema { cursor?: string feed: AppBskyFeedDefs.SkeletonFeedPost[] - [k: string]: unknown } export interface CallOptions { diff --git a/packages/api/src/client/types/app/bsky/feed/getLikes.ts b/packages/api/src/client/types/app/bsky/feed/getLikes.ts index 35af1b3aae5..a02b91c54b5 100644 --- a/packages/api/src/client/types/app/bsky/feed/getLikes.ts +++ b/packages/api/src/client/types/app/bsky/feed/getLikes.ts @@ -3,10 +3,14 @@ */ import { HeadersMap, XRPCError } from '@atproto/xrpc' import { ValidationResult, BlobRef } from '@atproto/lexicon' -import { isObj, hasProp } from '../../../../util' -import { lexicons } from '../../../../lexicons' import { CID } from 'multiformats/cid' -import * as AppBskyActorDefs from '../actor/defs' +import { validate as _validate } from '../../../../lexicons' +import { $Type, $Typed, is$typed as _is$typed, OmitKey } from '../../../../util' +import type * as AppBskyActorDefs from '../actor/defs' + +const is$typed = _is$typed, + validate = _validate +const id = 'app.bsky.feed.getLikes' export interface QueryParams { /** AT-URI of the subject (eg, a post record). */ @@ -24,7 +28,6 @@ export interface OutputSchema { cid?: string cursor?: string likes: Like[] - [k: string]: unknown } export interface CallOptions { @@ -43,18 +46,18 @@ export function toKnownErr(e: any) { } export interface Like { + $type?: $Type<'app.bsky.feed.getLikes', 'like'> indexedAt: string createdAt: string actor: AppBskyActorDefs.ProfileView - [k: string]: unknown } -export function isLike(v: unknown): v is Like { - return ( - isObj(v) && hasProp(v, '$type') && v.$type === 'app.bsky.feed.getLikes#like' - ) +const hashLike = 'like' + +export function isLike(v: V) { + return is$typed(v, id, hashLike) } -export function validateLike(v: unknown): ValidationResult { - return lexicons.validate('app.bsky.feed.getLikes#like', v) +export function validateLike(v: V) { + return validate(v, id, hashLike) } diff --git a/packages/api/src/client/types/app/bsky/feed/getListFeed.ts b/packages/api/src/client/types/app/bsky/feed/getListFeed.ts index 4e47e597afd..dc3b262e231 100644 --- a/packages/api/src/client/types/app/bsky/feed/getListFeed.ts +++ b/packages/api/src/client/types/app/bsky/feed/getListFeed.ts @@ -3,10 +3,14 @@ */ import { HeadersMap, XRPCError } from '@atproto/xrpc' import { ValidationResult, BlobRef } from '@atproto/lexicon' -import { isObj, hasProp } from '../../../../util' -import { lexicons } from '../../../../lexicons' import { CID } from 'multiformats/cid' -import * as AppBskyFeedDefs from './defs' +import { validate as _validate } from '../../../../lexicons' +import { $Type, $Typed, is$typed as _is$typed, OmitKey } from '../../../../util' +import type * as AppBskyFeedDefs from './defs' + +const is$typed = _is$typed, + validate = _validate +const id = 'app.bsky.feed.getListFeed' export interface QueryParams { /** Reference (AT-URI) to the list record. */ @@ -20,7 +24,6 @@ export type InputSchema = undefined export interface OutputSchema { cursor?: string feed: AppBskyFeedDefs.FeedViewPost[] - [k: string]: unknown } export interface CallOptions { diff --git a/packages/api/src/client/types/app/bsky/feed/getPostThread.ts b/packages/api/src/client/types/app/bsky/feed/getPostThread.ts index df012607df1..724b15c7929 100644 --- a/packages/api/src/client/types/app/bsky/feed/getPostThread.ts +++ b/packages/api/src/client/types/app/bsky/feed/getPostThread.ts @@ -3,10 +3,14 @@ */ import { HeadersMap, XRPCError } from '@atproto/xrpc' import { ValidationResult, BlobRef } from '@atproto/lexicon' -import { isObj, hasProp } from '../../../../util' -import { lexicons } from '../../../../lexicons' import { CID } from 'multiformats/cid' -import * as AppBskyFeedDefs from './defs' +import { validate as _validate } from '../../../../lexicons' +import { $Type, $Typed, is$typed as _is$typed, OmitKey } from '../../../../util' +import type * as AppBskyFeedDefs from './defs' + +const is$typed = _is$typed, + validate = _validate +const id = 'app.bsky.feed.getPostThread' export interface QueryParams { /** Reference (AT-URI) to post record. */ @@ -21,12 +25,11 @@ export type InputSchema = undefined export interface OutputSchema { thread: - | AppBskyFeedDefs.ThreadViewPost - | AppBskyFeedDefs.NotFoundPost - | AppBskyFeedDefs.BlockedPost - | { $type: string; [k: string]: unknown } + | $Typed + | $Typed + | $Typed + | { $type: string } threadgate?: AppBskyFeedDefs.ThreadgateView - [k: string]: unknown } export interface CallOptions { diff --git a/packages/api/src/client/types/app/bsky/feed/getPosts.ts b/packages/api/src/client/types/app/bsky/feed/getPosts.ts index 0ae8657de94..1fed826a44b 100644 --- a/packages/api/src/client/types/app/bsky/feed/getPosts.ts +++ b/packages/api/src/client/types/app/bsky/feed/getPosts.ts @@ -3,10 +3,14 @@ */ import { HeadersMap, XRPCError } from '@atproto/xrpc' import { ValidationResult, BlobRef } from '@atproto/lexicon' -import { isObj, hasProp } from '../../../../util' -import { lexicons } from '../../../../lexicons' import { CID } from 'multiformats/cid' -import * as AppBskyFeedDefs from './defs' +import { validate as _validate } from '../../../../lexicons' +import { $Type, $Typed, is$typed as _is$typed, OmitKey } from '../../../../util' +import type * as AppBskyFeedDefs from './defs' + +const is$typed = _is$typed, + validate = _validate +const id = 'app.bsky.feed.getPosts' export interface QueryParams { /** List of post AT-URIs to return hydrated views for. */ @@ -17,7 +21,6 @@ export type InputSchema = undefined export interface OutputSchema { posts: AppBskyFeedDefs.PostView[] - [k: string]: unknown } export interface CallOptions { diff --git a/packages/api/src/client/types/app/bsky/feed/getQuotes.ts b/packages/api/src/client/types/app/bsky/feed/getQuotes.ts index aafc487cd43..42029ea1394 100644 --- a/packages/api/src/client/types/app/bsky/feed/getQuotes.ts +++ b/packages/api/src/client/types/app/bsky/feed/getQuotes.ts @@ -3,10 +3,14 @@ */ import { HeadersMap, XRPCError } from '@atproto/xrpc' import { ValidationResult, BlobRef } from '@atproto/lexicon' -import { isObj, hasProp } from '../../../../util' -import { lexicons } from '../../../../lexicons' import { CID } from 'multiformats/cid' -import * as AppBskyFeedDefs from './defs' +import { validate as _validate } from '../../../../lexicons' +import { $Type, $Typed, is$typed as _is$typed, OmitKey } from '../../../../util' +import type * as AppBskyFeedDefs from './defs' + +const is$typed = _is$typed, + validate = _validate +const id = 'app.bsky.feed.getQuotes' export interface QueryParams { /** Reference (AT-URI) of post record */ @@ -24,7 +28,6 @@ export interface OutputSchema { cid?: string cursor?: string posts: AppBskyFeedDefs.PostView[] - [k: string]: unknown } export interface CallOptions { diff --git a/packages/api/src/client/types/app/bsky/feed/getRepostedBy.ts b/packages/api/src/client/types/app/bsky/feed/getRepostedBy.ts index dfbac97453b..f643585a5a1 100644 --- a/packages/api/src/client/types/app/bsky/feed/getRepostedBy.ts +++ b/packages/api/src/client/types/app/bsky/feed/getRepostedBy.ts @@ -3,10 +3,14 @@ */ import { HeadersMap, XRPCError } from '@atproto/xrpc' import { ValidationResult, BlobRef } from '@atproto/lexicon' -import { isObj, hasProp } from '../../../../util' -import { lexicons } from '../../../../lexicons' import { CID } from 'multiformats/cid' -import * as AppBskyActorDefs from '../actor/defs' +import { validate as _validate } from '../../../../lexicons' +import { $Type, $Typed, is$typed as _is$typed, OmitKey } from '../../../../util' +import type * as AppBskyActorDefs from '../actor/defs' + +const is$typed = _is$typed, + validate = _validate +const id = 'app.bsky.feed.getRepostedBy' export interface QueryParams { /** Reference (AT-URI) of post record */ @@ -24,7 +28,6 @@ export interface OutputSchema { cid?: string cursor?: string repostedBy: AppBskyActorDefs.ProfileView[] - [k: string]: unknown } export interface CallOptions { diff --git a/packages/api/src/client/types/app/bsky/feed/getSuggestedFeeds.ts b/packages/api/src/client/types/app/bsky/feed/getSuggestedFeeds.ts index 2e9495d8c19..b8729c5ac31 100644 --- a/packages/api/src/client/types/app/bsky/feed/getSuggestedFeeds.ts +++ b/packages/api/src/client/types/app/bsky/feed/getSuggestedFeeds.ts @@ -3,10 +3,14 @@ */ import { HeadersMap, XRPCError } from '@atproto/xrpc' import { ValidationResult, BlobRef } from '@atproto/lexicon' -import { isObj, hasProp } from '../../../../util' -import { lexicons } from '../../../../lexicons' import { CID } from 'multiformats/cid' -import * as AppBskyFeedDefs from './defs' +import { validate as _validate } from '../../../../lexicons' +import { $Type, $Typed, is$typed as _is$typed, OmitKey } from '../../../../util' +import type * as AppBskyFeedDefs from './defs' + +const is$typed = _is$typed, + validate = _validate +const id = 'app.bsky.feed.getSuggestedFeeds' export interface QueryParams { limit?: number @@ -18,7 +22,6 @@ export type InputSchema = undefined export interface OutputSchema { cursor?: string feeds: AppBskyFeedDefs.GeneratorView[] - [k: string]: unknown } export interface CallOptions { diff --git a/packages/api/src/client/types/app/bsky/feed/getTimeline.ts b/packages/api/src/client/types/app/bsky/feed/getTimeline.ts index 6c5280443a9..4b429d36ac6 100644 --- a/packages/api/src/client/types/app/bsky/feed/getTimeline.ts +++ b/packages/api/src/client/types/app/bsky/feed/getTimeline.ts @@ -3,10 +3,14 @@ */ import { HeadersMap, XRPCError } from '@atproto/xrpc' import { ValidationResult, BlobRef } from '@atproto/lexicon' -import { isObj, hasProp } from '../../../../util' -import { lexicons } from '../../../../lexicons' import { CID } from 'multiformats/cid' -import * as AppBskyFeedDefs from './defs' +import { validate as _validate } from '../../../../lexicons' +import { $Type, $Typed, is$typed as _is$typed, OmitKey } from '../../../../util' +import type * as AppBskyFeedDefs from './defs' + +const is$typed = _is$typed, + validate = _validate +const id = 'app.bsky.feed.getTimeline' export interface QueryParams { /** Variant 'algorithm' for timeline. Implementation-specific. NOTE: most feed flexibility has been moved to feed generator mechanism. */ @@ -20,7 +24,6 @@ export type InputSchema = undefined export interface OutputSchema { cursor?: string feed: AppBskyFeedDefs.FeedViewPost[] - [k: string]: unknown } export interface CallOptions { diff --git a/packages/api/src/client/types/app/bsky/feed/like.ts b/packages/api/src/client/types/app/bsky/feed/like.ts index 10237e45f0d..d49f343e3c6 100644 --- a/packages/api/src/client/types/app/bsky/feed/like.ts +++ b/packages/api/src/client/types/app/bsky/feed/like.ts @@ -2,25 +2,28 @@ * GENERATED CODE - DO NOT MODIFY */ import { ValidationResult, BlobRef } from '@atproto/lexicon' -import { isObj, hasProp } from '../../../../util' -import { lexicons } from '../../../../lexicons' import { CID } from 'multiformats/cid' -import * as ComAtprotoRepoStrongRef from '../../../com/atproto/repo/strongRef' +import { validate as _validate } from '../../../../lexicons' +import { $Type, $Typed, is$typed as _is$typed, OmitKey } from '../../../../util' +import type * as ComAtprotoRepoStrongRef from '../../../com/atproto/repo/strongRef' + +const is$typed = _is$typed, + validate = _validate +const id = 'app.bsky.feed.like' export interface Record { + $type: $Type<'app.bsky.feed.like', 'main'> subject: ComAtprotoRepoStrongRef.Main createdAt: string [k: string]: unknown } -export function isRecord(v: unknown): v is Record { - return ( - isObj(v) && - hasProp(v, '$type') && - (v.$type === 'app.bsky.feed.like#main' || v.$type === 'app.bsky.feed.like') - ) +const hashRecord = 'main' + +export function isRecord(v: V) { + return is$typed(v, id, hashRecord) } -export function validateRecord(v: unknown): ValidationResult { - return lexicons.validate('app.bsky.feed.like#main', v) +export function validateRecord(v: V) { + return validate(v, id, hashRecord, true) } diff --git a/packages/api/src/client/types/app/bsky/feed/post.ts b/packages/api/src/client/types/app/bsky/feed/post.ts index e358409c0e0..fed17585388 100644 --- a/packages/api/src/client/types/app/bsky/feed/post.ts +++ b/packages/api/src/client/types/app/bsky/feed/post.ts @@ -2,19 +2,24 @@ * GENERATED CODE - DO NOT MODIFY */ import { ValidationResult, BlobRef } from '@atproto/lexicon' -import { isObj, hasProp } from '../../../../util' -import { lexicons } from '../../../../lexicons' import { CID } from 'multiformats/cid' -import * as AppBskyRichtextFacet from '../richtext/facet' -import * as AppBskyEmbedImages from '../embed/images' -import * as AppBskyEmbedVideo from '../embed/video' -import * as AppBskyEmbedExternal from '../embed/external' -import * as AppBskyEmbedRecord from '../embed/record' -import * as AppBskyEmbedRecordWithMedia from '../embed/recordWithMedia' -import * as ComAtprotoLabelDefs from '../../../com/atproto/label/defs' -import * as ComAtprotoRepoStrongRef from '../../../com/atproto/repo/strongRef' +import { validate as _validate } from '../../../../lexicons' +import { $Type, $Typed, is$typed as _is$typed, OmitKey } from '../../../../util' +import type * as AppBskyRichtextFacet from '../richtext/facet' +import type * as AppBskyEmbedImages from '../embed/images' +import type * as AppBskyEmbedVideo from '../embed/video' +import type * as AppBskyEmbedExternal from '../embed/external' +import type * as AppBskyEmbedRecord from '../embed/record' +import type * as AppBskyEmbedRecordWithMedia from '../embed/recordWithMedia' +import type * as ComAtprotoLabelDefs from '../../../com/atproto/label/defs' +import type * as ComAtprotoRepoStrongRef from '../../../com/atproto/repo/strongRef' + +const is$typed = _is$typed, + validate = _validate +const id = 'app.bsky.feed.post' export interface Record { + $type: $Type<'app.bsky.feed.post', 'main'> /** The primary post content. May be an empty string, if there are embeds. */ text: string /** DEPRECATED: replaced by app.bsky.richtext.facet. */ @@ -23,17 +28,15 @@ export interface Record { facets?: AppBskyRichtextFacet.Main[] reply?: ReplyRef embed?: - | AppBskyEmbedImages.Main - | AppBskyEmbedVideo.Main - | AppBskyEmbedExternal.Main - | AppBskyEmbedRecord.Main - | AppBskyEmbedRecordWithMedia.Main - | { $type: string; [k: string]: unknown } + | $Typed + | $Typed + | $Typed + | $Typed + | $Typed + | { $type: string } /** Indicates human language of post primary text content. */ langs?: string[] - labels?: - | ComAtprotoLabelDefs.SelfLabels - | { $type: string; [k: string]: unknown } + labels?: $Typed | { $type: string } /** Additional hashtags, in addition to any included in post text and facets. */ tags?: string[] /** Client-declared timestamp when this post was originally created. */ @@ -41,68 +44,64 @@ export interface Record { [k: string]: unknown } -export function isRecord(v: unknown): v is Record { - return ( - isObj(v) && - hasProp(v, '$type') && - (v.$type === 'app.bsky.feed.post#main' || v.$type === 'app.bsky.feed.post') - ) +const hashRecord = 'main' + +export function isRecord(v: V) { + return is$typed(v, id, hashRecord) } -export function validateRecord(v: unknown): ValidationResult { - return lexicons.validate('app.bsky.feed.post#main', v) +export function validateRecord(v: V) { + return validate(v, id, hashRecord, true) } export interface ReplyRef { + $type?: $Type<'app.bsky.feed.post', 'replyRef'> root: ComAtprotoRepoStrongRef.Main parent: ComAtprotoRepoStrongRef.Main - [k: string]: unknown } -export function isReplyRef(v: unknown): v is ReplyRef { - return ( - isObj(v) && hasProp(v, '$type') && v.$type === 'app.bsky.feed.post#replyRef' - ) +const hashReplyRef = 'replyRef' + +export function isReplyRef(v: V) { + return is$typed(v, id, hashReplyRef) } -export function validateReplyRef(v: unknown): ValidationResult { - return lexicons.validate('app.bsky.feed.post#replyRef', v) +export function validateReplyRef(v: V) { + return validate(v, id, hashReplyRef) } /** Deprecated: use facets instead. */ export interface Entity { + $type?: $Type<'app.bsky.feed.post', 'entity'> index: TextSlice /** Expected values are 'mention' and 'link'. */ type: string value: string - [k: string]: unknown } -export function isEntity(v: unknown): v is Entity { - return ( - isObj(v) && hasProp(v, '$type') && v.$type === 'app.bsky.feed.post#entity' - ) +const hashEntity = 'entity' + +export function isEntity(v: V) { + return is$typed(v, id, hashEntity) } -export function validateEntity(v: unknown): ValidationResult { - return lexicons.validate('app.bsky.feed.post#entity', v) +export function validateEntity(v: V) { + return validate(v, id, hashEntity) } /** Deprecated. Use app.bsky.richtext instead -- A text segment. Start is inclusive, end is exclusive. Indices are for utf16-encoded strings. */ export interface TextSlice { + $type?: $Type<'app.bsky.feed.post', 'textSlice'> start: number end: number - [k: string]: unknown } -export function isTextSlice(v: unknown): v is TextSlice { - return ( - isObj(v) && - hasProp(v, '$type') && - v.$type === 'app.bsky.feed.post#textSlice' - ) +const hashTextSlice = 'textSlice' + +export function isTextSlice(v: V) { + return is$typed(v, id, hashTextSlice) } -export function validateTextSlice(v: unknown): ValidationResult { - return lexicons.validate('app.bsky.feed.post#textSlice', v) +export function validateTextSlice(v: V) { + return validate(v, id, hashTextSlice) } diff --git a/packages/api/src/client/types/app/bsky/feed/postgate.ts b/packages/api/src/client/types/app/bsky/feed/postgate.ts index e3028385873..560b4e3f5c6 100644 --- a/packages/api/src/client/types/app/bsky/feed/postgate.ts +++ b/packages/api/src/client/types/app/bsky/feed/postgate.ts @@ -2,47 +2,47 @@ * GENERATED CODE - DO NOT MODIFY */ import { ValidationResult, BlobRef } from '@atproto/lexicon' -import { isObj, hasProp } from '../../../../util' -import { lexicons } from '../../../../lexicons' import { CID } from 'multiformats/cid' +import { validate as _validate } from '../../../../lexicons' +import { $Type, $Typed, is$typed as _is$typed, OmitKey } from '../../../../util' + +const is$typed = _is$typed, + validate = _validate +const id = 'app.bsky.feed.postgate' export interface Record { + $type: $Type<'app.bsky.feed.postgate', 'main'> createdAt: string /** Reference (AT-URI) to the post record. */ post: string /** List of AT-URIs embedding this post that the author has detached from. */ detachedEmbeddingUris?: string[] /** List of rules defining who can embed this post. If value is an empty array or is undefined, no particular rules apply and anyone can embed. */ - embeddingRules?: (DisableRule | { $type: string; [k: string]: unknown })[] + embeddingRules?: ($Typed | { $type: string })[] [k: string]: unknown } -export function isRecord(v: unknown): v is Record { - return ( - isObj(v) && - hasProp(v, '$type') && - (v.$type === 'app.bsky.feed.postgate#main' || - v.$type === 'app.bsky.feed.postgate') - ) +const hashRecord = 'main' + +export function isRecord(v: V) { + return is$typed(v, id, hashRecord) } -export function validateRecord(v: unknown): ValidationResult { - return lexicons.validate('app.bsky.feed.postgate#main', v) +export function validateRecord(v: V) { + return validate(v, id, hashRecord, true) } /** Disables embedding of this post. */ export interface DisableRule { - [k: string]: unknown + $type?: $Type<'app.bsky.feed.postgate', 'disableRule'> } -export function isDisableRule(v: unknown): v is DisableRule { - return ( - isObj(v) && - hasProp(v, '$type') && - v.$type === 'app.bsky.feed.postgate#disableRule' - ) +const hashDisableRule = 'disableRule' + +export function isDisableRule(v: V) { + return is$typed(v, id, hashDisableRule) } -export function validateDisableRule(v: unknown): ValidationResult { - return lexicons.validate('app.bsky.feed.postgate#disableRule', v) +export function validateDisableRule(v: V) { + return validate(v, id, hashDisableRule) } diff --git a/packages/api/src/client/types/app/bsky/feed/repost.ts b/packages/api/src/client/types/app/bsky/feed/repost.ts index 2e8f6e75548..3b81cb1560d 100644 --- a/packages/api/src/client/types/app/bsky/feed/repost.ts +++ b/packages/api/src/client/types/app/bsky/feed/repost.ts @@ -2,26 +2,28 @@ * GENERATED CODE - DO NOT MODIFY */ import { ValidationResult, BlobRef } from '@atproto/lexicon' -import { isObj, hasProp } from '../../../../util' -import { lexicons } from '../../../../lexicons' import { CID } from 'multiformats/cid' -import * as ComAtprotoRepoStrongRef from '../../../com/atproto/repo/strongRef' +import { validate as _validate } from '../../../../lexicons' +import { $Type, $Typed, is$typed as _is$typed, OmitKey } from '../../../../util' +import type * as ComAtprotoRepoStrongRef from '../../../com/atproto/repo/strongRef' + +const is$typed = _is$typed, + validate = _validate +const id = 'app.bsky.feed.repost' export interface Record { + $type: $Type<'app.bsky.feed.repost', 'main'> subject: ComAtprotoRepoStrongRef.Main createdAt: string [k: string]: unknown } -export function isRecord(v: unknown): v is Record { - return ( - isObj(v) && - hasProp(v, '$type') && - (v.$type === 'app.bsky.feed.repost#main' || - v.$type === 'app.bsky.feed.repost') - ) +const hashRecord = 'main' + +export function isRecord(v: V) { + return is$typed(v, id, hashRecord) } -export function validateRecord(v: unknown): ValidationResult { - return lexicons.validate('app.bsky.feed.repost#main', v) +export function validateRecord(v: V) { + return validate(v, id, hashRecord, true) } diff --git a/packages/api/src/client/types/app/bsky/feed/searchPosts.ts b/packages/api/src/client/types/app/bsky/feed/searchPosts.ts index 1a2ec64dcd2..c47e4b1c907 100644 --- a/packages/api/src/client/types/app/bsky/feed/searchPosts.ts +++ b/packages/api/src/client/types/app/bsky/feed/searchPosts.ts @@ -3,10 +3,14 @@ */ import { HeadersMap, XRPCError } from '@atproto/xrpc' import { ValidationResult, BlobRef } from '@atproto/lexicon' -import { isObj, hasProp } from '../../../../util' -import { lexicons } from '../../../../lexicons' import { CID } from 'multiformats/cid' -import * as AppBskyFeedDefs from './defs' +import { validate as _validate } from '../../../../lexicons' +import { $Type, $Typed, is$typed as _is$typed, OmitKey } from '../../../../util' +import type * as AppBskyFeedDefs from './defs' + +const is$typed = _is$typed, + validate = _validate +const id = 'app.bsky.feed.searchPosts' export interface QueryParams { /** Search query string; syntax, phrase, boolean, and faceting is unspecified, but Lucene query syntax is recommended. */ @@ -41,7 +45,6 @@ export interface OutputSchema { /** Count of search hits. Optional, may be rounded/truncated, and may not be possible to paginate through all hits. */ hitsTotal?: number posts: AppBskyFeedDefs.PostView[] - [k: string]: unknown } export interface CallOptions { diff --git a/packages/api/src/client/types/app/bsky/feed/sendInteractions.ts b/packages/api/src/client/types/app/bsky/feed/sendInteractions.ts index 4d43e26f3dd..278bf27456a 100644 --- a/packages/api/src/client/types/app/bsky/feed/sendInteractions.ts +++ b/packages/api/src/client/types/app/bsky/feed/sendInteractions.ts @@ -3,21 +3,22 @@ */ import { HeadersMap, XRPCError } from '@atproto/xrpc' import { ValidationResult, BlobRef } from '@atproto/lexicon' -import { isObj, hasProp } from '../../../../util' -import { lexicons } from '../../../../lexicons' import { CID } from 'multiformats/cid' -import * as AppBskyFeedDefs from './defs' +import { validate as _validate } from '../../../../lexicons' +import { $Type, $Typed, is$typed as _is$typed, OmitKey } from '../../../../util' +import type * as AppBskyFeedDefs from './defs' + +const is$typed = _is$typed, + validate = _validate +const id = 'app.bsky.feed.sendInteractions' export interface QueryParams {} export interface InputSchema { interactions: AppBskyFeedDefs.Interaction[] - [k: string]: unknown } -export interface OutputSchema { - [k: string]: unknown -} +export interface OutputSchema {} export interface CallOptions { signal?: AbortSignal diff --git a/packages/api/src/client/types/app/bsky/feed/threadgate.ts b/packages/api/src/client/types/app/bsky/feed/threadgate.ts index 1fe87016dce..571d34b7837 100644 --- a/packages/api/src/client/types/app/bsky/feed/threadgate.ts +++ b/packages/api/src/client/types/app/bsky/feed/threadgate.ts @@ -2,20 +2,25 @@ * GENERATED CODE - DO NOT MODIFY */ import { ValidationResult, BlobRef } from '@atproto/lexicon' -import { isObj, hasProp } from '../../../../util' -import { lexicons } from '../../../../lexicons' import { CID } from 'multiformats/cid' +import { validate as _validate } from '../../../../lexicons' +import { $Type, $Typed, is$typed as _is$typed, OmitKey } from '../../../../util' + +const is$typed = _is$typed, + validate = _validate +const id = 'app.bsky.feed.threadgate' export interface Record { + $type: $Type<'app.bsky.feed.threadgate', 'main'> /** Reference (AT-URI) to the post record. */ post: string /** List of rules defining who can reply to this post. If value is an empty array, no one can reply. If value is undefined, anyone can reply. */ allow?: ( - | MentionRule - | FollowerRule - | FollowingRule - | ListRule - | { $type: string; [k: string]: unknown } + | $Typed + | $Typed + | $Typed + | $Typed + | { $type: string } )[] createdAt: string /** List of hidden reply URIs. */ @@ -23,84 +28,73 @@ export interface Record { [k: string]: unknown } -export function isRecord(v: unknown): v is Record { - return ( - isObj(v) && - hasProp(v, '$type') && - (v.$type === 'app.bsky.feed.threadgate#main' || - v.$type === 'app.bsky.feed.threadgate') - ) +const hashRecord = 'main' + +export function isRecord(v: V) { + return is$typed(v, id, hashRecord) } -export function validateRecord(v: unknown): ValidationResult { - return lexicons.validate('app.bsky.feed.threadgate#main', v) +export function validateRecord(v: V) { + return validate(v, id, hashRecord, true) } /** Allow replies from actors mentioned in your post. */ export interface MentionRule { - [k: string]: unknown + $type?: $Type<'app.bsky.feed.threadgate', 'mentionRule'> } -export function isMentionRule(v: unknown): v is MentionRule { - return ( - isObj(v) && - hasProp(v, '$type') && - v.$type === 'app.bsky.feed.threadgate#mentionRule' - ) +const hashMentionRule = 'mentionRule' + +export function isMentionRule(v: V) { + return is$typed(v, id, hashMentionRule) } -export function validateMentionRule(v: unknown): ValidationResult { - return lexicons.validate('app.bsky.feed.threadgate#mentionRule', v) +export function validateMentionRule(v: V) { + return validate(v, id, hashMentionRule) } /** Allow replies from actors who follow you. */ export interface FollowerRule { - [k: string]: unknown + $type?: $Type<'app.bsky.feed.threadgate', 'followerRule'> } -export function isFollowerRule(v: unknown): v is FollowerRule { - return ( - isObj(v) && - hasProp(v, '$type') && - v.$type === 'app.bsky.feed.threadgate#followerRule' - ) +const hashFollowerRule = 'followerRule' + +export function isFollowerRule(v: V) { + return is$typed(v, id, hashFollowerRule) } -export function validateFollowerRule(v: unknown): ValidationResult { - return lexicons.validate('app.bsky.feed.threadgate#followerRule', v) +export function validateFollowerRule(v: V) { + return validate(v, id, hashFollowerRule) } /** Allow replies from actors you follow. */ export interface FollowingRule { - [k: string]: unknown + $type?: $Type<'app.bsky.feed.threadgate', 'followingRule'> } -export function isFollowingRule(v: unknown): v is FollowingRule { - return ( - isObj(v) && - hasProp(v, '$type') && - v.$type === 'app.bsky.feed.threadgate#followingRule' - ) +const hashFollowingRule = 'followingRule' + +export function isFollowingRule(v: V) { + return is$typed(v, id, hashFollowingRule) } -export function validateFollowingRule(v: unknown): ValidationResult { - return lexicons.validate('app.bsky.feed.threadgate#followingRule', v) +export function validateFollowingRule(v: V) { + return validate(v, id, hashFollowingRule) } /** Allow replies from actors on a list. */ export interface ListRule { + $type?: $Type<'app.bsky.feed.threadgate', 'listRule'> list: string - [k: string]: unknown } -export function isListRule(v: unknown): v is ListRule { - return ( - isObj(v) && - hasProp(v, '$type') && - v.$type === 'app.bsky.feed.threadgate#listRule' - ) +const hashListRule = 'listRule' + +export function isListRule(v: V) { + return is$typed(v, id, hashListRule) } -export function validateListRule(v: unknown): ValidationResult { - return lexicons.validate('app.bsky.feed.threadgate#listRule', v) +export function validateListRule(v: V) { + return validate(v, id, hashListRule) } diff --git a/packages/api/src/client/types/app/bsky/graph/block.ts b/packages/api/src/client/types/app/bsky/graph/block.ts index f2455fc08a2..a1e60d9eaa8 100644 --- a/packages/api/src/client/types/app/bsky/graph/block.ts +++ b/packages/api/src/client/types/app/bsky/graph/block.ts @@ -2,26 +2,28 @@ * GENERATED CODE - DO NOT MODIFY */ import { ValidationResult, BlobRef } from '@atproto/lexicon' -import { isObj, hasProp } from '../../../../util' -import { lexicons } from '../../../../lexicons' import { CID } from 'multiformats/cid' +import { validate as _validate } from '../../../../lexicons' +import { $Type, $Typed, is$typed as _is$typed, OmitKey } from '../../../../util' + +const is$typed = _is$typed, + validate = _validate +const id = 'app.bsky.graph.block' export interface Record { + $type: $Type<'app.bsky.graph.block', 'main'> /** DID of the account to be blocked. */ subject: string createdAt: string [k: string]: unknown } -export function isRecord(v: unknown): v is Record { - return ( - isObj(v) && - hasProp(v, '$type') && - (v.$type === 'app.bsky.graph.block#main' || - v.$type === 'app.bsky.graph.block') - ) +const hashRecord = 'main' + +export function isRecord(v: V) { + return is$typed(v, id, hashRecord) } -export function validateRecord(v: unknown): ValidationResult { - return lexicons.validate('app.bsky.graph.block#main', v) +export function validateRecord(v: V) { + return validate(v, id, hashRecord, true) } diff --git a/packages/api/src/client/types/app/bsky/graph/defs.ts b/packages/api/src/client/types/app/bsky/graph/defs.ts index aa7e2c068b5..0a10e5bbd7e 100644 --- a/packages/api/src/client/types/app/bsky/graph/defs.ts +++ b/packages/api/src/client/types/app/bsky/graph/defs.ts @@ -2,15 +2,20 @@ * GENERATED CODE - DO NOT MODIFY */ import { ValidationResult, BlobRef } from '@atproto/lexicon' -import { isObj, hasProp } from '../../../../util' -import { lexicons } from '../../../../lexicons' import { CID } from 'multiformats/cid' -import * as ComAtprotoLabelDefs from '../../../com/atproto/label/defs' -import * as AppBskyActorDefs from '../actor/defs' -import * as AppBskyRichtextFacet from '../richtext/facet' -import * as AppBskyFeedDefs from '../feed/defs' +import { validate as _validate } from '../../../../lexicons' +import { $Type, $Typed, is$typed as _is$typed, OmitKey } from '../../../../util' +import type * as ComAtprotoLabelDefs from '../../../com/atproto/label/defs' +import type * as AppBskyActorDefs from '../actor/defs' +import type * as AppBskyRichtextFacet from '../richtext/facet' +import type * as AppBskyFeedDefs from '../feed/defs' + +const is$typed = _is$typed, + validate = _validate +const id = 'app.bsky.graph.defs' export interface ListViewBasic { + $type?: $Type<'app.bsky.graph.defs', 'listViewBasic'> uri: string cid: string name: string @@ -20,22 +25,20 @@ export interface ListViewBasic { labels?: ComAtprotoLabelDefs.Label[] viewer?: ListViewerState indexedAt?: string - [k: string]: unknown } -export function isListViewBasic(v: unknown): v is ListViewBasic { - return ( - isObj(v) && - hasProp(v, '$type') && - v.$type === 'app.bsky.graph.defs#listViewBasic' - ) +const hashListViewBasic = 'listViewBasic' + +export function isListViewBasic(v: V) { + return is$typed(v, id, hashListViewBasic) } -export function validateListViewBasic(v: unknown): ValidationResult { - return lexicons.validate('app.bsky.graph.defs#listViewBasic', v) +export function validateListViewBasic(v: V) { + return validate(v, id, hashListViewBasic) } export interface ListView { + $type?: $Type<'app.bsky.graph.defs', 'listView'> uri: string cid: string creator: AppBskyActorDefs.ProfileView @@ -48,43 +51,39 @@ export interface ListView { labels?: ComAtprotoLabelDefs.Label[] viewer?: ListViewerState indexedAt: string - [k: string]: unknown } -export function isListView(v: unknown): v is ListView { - return ( - isObj(v) && - hasProp(v, '$type') && - v.$type === 'app.bsky.graph.defs#listView' - ) +const hashListView = 'listView' + +export function isListView(v: V) { + return is$typed(v, id, hashListView) } -export function validateListView(v: unknown): ValidationResult { - return lexicons.validate('app.bsky.graph.defs#listView', v) +export function validateListView(v: V) { + return validate(v, id, hashListView) } export interface ListItemView { + $type?: $Type<'app.bsky.graph.defs', 'listItemView'> uri: string subject: AppBskyActorDefs.ProfileView - [k: string]: unknown } -export function isListItemView(v: unknown): v is ListItemView { - return ( - isObj(v) && - hasProp(v, '$type') && - v.$type === 'app.bsky.graph.defs#listItemView' - ) +const hashListItemView = 'listItemView' + +export function isListItemView(v: V) { + return is$typed(v, id, hashListItemView) } -export function validateListItemView(v: unknown): ValidationResult { - return lexicons.validate('app.bsky.graph.defs#listItemView', v) +export function validateListItemView(v: V) { + return validate(v, id, hashListItemView) } export interface StarterPackView { + $type?: $Type<'app.bsky.graph.defs', 'starterPackView'> uri: string cid: string - record: {} + record: { [_ in string]: unknown } creator: AppBskyActorDefs.ProfileViewBasic list?: ListViewBasic listItemsSample?: ListItemView[] @@ -93,44 +92,39 @@ export interface StarterPackView { joinedAllTimeCount?: number labels?: ComAtprotoLabelDefs.Label[] indexedAt: string - [k: string]: unknown } -export function isStarterPackView(v: unknown): v is StarterPackView { - return ( - isObj(v) && - hasProp(v, '$type') && - v.$type === 'app.bsky.graph.defs#starterPackView' - ) +const hashStarterPackView = 'starterPackView' + +export function isStarterPackView(v: V) { + return is$typed(v, id, hashStarterPackView) } -export function validateStarterPackView(v: unknown): ValidationResult { - return lexicons.validate('app.bsky.graph.defs#starterPackView', v) +export function validateStarterPackView(v: V) { + return validate(v, id, hashStarterPackView) } export interface StarterPackViewBasic { + $type?: $Type<'app.bsky.graph.defs', 'starterPackViewBasic'> uri: string cid: string - record: {} + record: { [_ in string]: unknown } creator: AppBskyActorDefs.ProfileViewBasic listItemCount?: number joinedWeekCount?: number joinedAllTimeCount?: number labels?: ComAtprotoLabelDefs.Label[] indexedAt: string - [k: string]: unknown } -export function isStarterPackViewBasic(v: unknown): v is StarterPackViewBasic { - return ( - isObj(v) && - hasProp(v, '$type') && - v.$type === 'app.bsky.graph.defs#starterPackViewBasic' - ) +const hashStarterPackViewBasic = 'starterPackViewBasic' + +export function isStarterPackViewBasic(v: V) { + return is$typed(v, id, hashStarterPackViewBasic) } -export function validateStarterPackViewBasic(v: unknown): ValidationResult { - return lexicons.validate('app.bsky.graph.defs#starterPackViewBasic', v) +export function validateStarterPackViewBasic(v: V) { + return validate(v, id, hashStarterPackViewBasic) } export type ListPurpose = @@ -140,67 +134,61 @@ export type ListPurpose = | (string & {}) /** A list of actors to apply an aggregate moderation action (mute/block) on. */ -export const MODLIST = 'app.bsky.graph.defs#modlist' +export const MODLIST = `${id}#modlist` /** A list of actors used for curation purposes such as list feeds or interaction gating. */ -export const CURATELIST = 'app.bsky.graph.defs#curatelist' +export const CURATELIST = `${id}#curatelist` /** A list of actors used for only for reference purposes such as within a starter pack. */ -export const REFERENCELIST = 'app.bsky.graph.defs#referencelist' +export const REFERENCELIST = `${id}#referencelist` export interface ListViewerState { + $type?: $Type<'app.bsky.graph.defs', 'listViewerState'> muted?: boolean blocked?: string - [k: string]: unknown } -export function isListViewerState(v: unknown): v is ListViewerState { - return ( - isObj(v) && - hasProp(v, '$type') && - v.$type === 'app.bsky.graph.defs#listViewerState' - ) +const hashListViewerState = 'listViewerState' + +export function isListViewerState(v: V) { + return is$typed(v, id, hashListViewerState) } -export function validateListViewerState(v: unknown): ValidationResult { - return lexicons.validate('app.bsky.graph.defs#listViewerState', v) +export function validateListViewerState(v: V) { + return validate(v, id, hashListViewerState) } /** indicates that a handle or DID could not be resolved */ export interface NotFoundActor { + $type?: $Type<'app.bsky.graph.defs', 'notFoundActor'> actor: string notFound: true - [k: string]: unknown } -export function isNotFoundActor(v: unknown): v is NotFoundActor { - return ( - isObj(v) && - hasProp(v, '$type') && - v.$type === 'app.bsky.graph.defs#notFoundActor' - ) +const hashNotFoundActor = 'notFoundActor' + +export function isNotFoundActor(v: V) { + return is$typed(v, id, hashNotFoundActor) } -export function validateNotFoundActor(v: unknown): ValidationResult { - return lexicons.validate('app.bsky.graph.defs#notFoundActor', v) +export function validateNotFoundActor(v: V) { + return validate(v, id, hashNotFoundActor) } /** lists the bi-directional graph relationships between one actor (not indicated in the object), and the target actors (the DID included in the object) */ export interface Relationship { + $type?: $Type<'app.bsky.graph.defs', 'relationship'> did: string /** if the actor follows this DID, this is the AT-URI of the follow record */ following?: string /** if the actor is followed by this DID, contains the AT-URI of the follow record */ followedBy?: string - [k: string]: unknown } -export function isRelationship(v: unknown): v is Relationship { - return ( - isObj(v) && - hasProp(v, '$type') && - v.$type === 'app.bsky.graph.defs#relationship' - ) +const hashRelationship = 'relationship' + +export function isRelationship(v: V) { + return is$typed(v, id, hashRelationship) } -export function validateRelationship(v: unknown): ValidationResult { - return lexicons.validate('app.bsky.graph.defs#relationship', v) +export function validateRelationship(v: V) { + return validate(v, id, hashRelationship) } diff --git a/packages/api/src/client/types/app/bsky/graph/follow.ts b/packages/api/src/client/types/app/bsky/graph/follow.ts index a68f434f3c8..bc1a184b820 100644 --- a/packages/api/src/client/types/app/bsky/graph/follow.ts +++ b/packages/api/src/client/types/app/bsky/graph/follow.ts @@ -2,25 +2,27 @@ * GENERATED CODE - DO NOT MODIFY */ import { ValidationResult, BlobRef } from '@atproto/lexicon' -import { isObj, hasProp } from '../../../../util' -import { lexicons } from '../../../../lexicons' import { CID } from 'multiformats/cid' +import { validate as _validate } from '../../../../lexicons' +import { $Type, $Typed, is$typed as _is$typed, OmitKey } from '../../../../util' + +const is$typed = _is$typed, + validate = _validate +const id = 'app.bsky.graph.follow' export interface Record { + $type: $Type<'app.bsky.graph.follow', 'main'> subject: string createdAt: string [k: string]: unknown } -export function isRecord(v: unknown): v is Record { - return ( - isObj(v) && - hasProp(v, '$type') && - (v.$type === 'app.bsky.graph.follow#main' || - v.$type === 'app.bsky.graph.follow') - ) +const hashRecord = 'main' + +export function isRecord(v: V) { + return is$typed(v, id, hashRecord) } -export function validateRecord(v: unknown): ValidationResult { - return lexicons.validate('app.bsky.graph.follow#main', v) +export function validateRecord(v: V) { + return validate(v, id, hashRecord, true) } diff --git a/packages/api/src/client/types/app/bsky/graph/getActorStarterPacks.ts b/packages/api/src/client/types/app/bsky/graph/getActorStarterPacks.ts index bb374e2318d..c8a37c09af6 100644 --- a/packages/api/src/client/types/app/bsky/graph/getActorStarterPacks.ts +++ b/packages/api/src/client/types/app/bsky/graph/getActorStarterPacks.ts @@ -3,10 +3,14 @@ */ import { HeadersMap, XRPCError } from '@atproto/xrpc' import { ValidationResult, BlobRef } from '@atproto/lexicon' -import { isObj, hasProp } from '../../../../util' -import { lexicons } from '../../../../lexicons' import { CID } from 'multiformats/cid' -import * as AppBskyGraphDefs from './defs' +import { validate as _validate } from '../../../../lexicons' +import { $Type, $Typed, is$typed as _is$typed, OmitKey } from '../../../../util' +import type * as AppBskyGraphDefs from './defs' + +const is$typed = _is$typed, + validate = _validate +const id = 'app.bsky.graph.getActorStarterPacks' export interface QueryParams { actor: string @@ -19,7 +23,6 @@ export type InputSchema = undefined export interface OutputSchema { cursor?: string starterPacks: AppBskyGraphDefs.StarterPackViewBasic[] - [k: string]: unknown } export interface CallOptions { diff --git a/packages/api/src/client/types/app/bsky/graph/getBlocks.ts b/packages/api/src/client/types/app/bsky/graph/getBlocks.ts index f0e4bd96bc8..6d73b202db7 100644 --- a/packages/api/src/client/types/app/bsky/graph/getBlocks.ts +++ b/packages/api/src/client/types/app/bsky/graph/getBlocks.ts @@ -3,10 +3,14 @@ */ import { HeadersMap, XRPCError } from '@atproto/xrpc' import { ValidationResult, BlobRef } from '@atproto/lexicon' -import { isObj, hasProp } from '../../../../util' -import { lexicons } from '../../../../lexicons' import { CID } from 'multiformats/cid' -import * as AppBskyActorDefs from '../actor/defs' +import { validate as _validate } from '../../../../lexicons' +import { $Type, $Typed, is$typed as _is$typed, OmitKey } from '../../../../util' +import type * as AppBskyActorDefs from '../actor/defs' + +const is$typed = _is$typed, + validate = _validate +const id = 'app.bsky.graph.getBlocks' export interface QueryParams { limit?: number @@ -18,7 +22,6 @@ export type InputSchema = undefined export interface OutputSchema { cursor?: string blocks: AppBskyActorDefs.ProfileView[] - [k: string]: unknown } export interface CallOptions { diff --git a/packages/api/src/client/types/app/bsky/graph/getFollowers.ts b/packages/api/src/client/types/app/bsky/graph/getFollowers.ts index f55649dc769..11c5869d30b 100644 --- a/packages/api/src/client/types/app/bsky/graph/getFollowers.ts +++ b/packages/api/src/client/types/app/bsky/graph/getFollowers.ts @@ -3,10 +3,14 @@ */ import { HeadersMap, XRPCError } from '@atproto/xrpc' import { ValidationResult, BlobRef } from '@atproto/lexicon' -import { isObj, hasProp } from '../../../../util' -import { lexicons } from '../../../../lexicons' import { CID } from 'multiformats/cid' -import * as AppBskyActorDefs from '../actor/defs' +import { validate as _validate } from '../../../../lexicons' +import { $Type, $Typed, is$typed as _is$typed, OmitKey } from '../../../../util' +import type * as AppBskyActorDefs from '../actor/defs' + +const is$typed = _is$typed, + validate = _validate +const id = 'app.bsky.graph.getFollowers' export interface QueryParams { actor: string @@ -20,7 +24,6 @@ export interface OutputSchema { subject: AppBskyActorDefs.ProfileView cursor?: string followers: AppBskyActorDefs.ProfileView[] - [k: string]: unknown } export interface CallOptions { diff --git a/packages/api/src/client/types/app/bsky/graph/getFollows.ts b/packages/api/src/client/types/app/bsky/graph/getFollows.ts index 8570c4a14c0..281c170a65f 100644 --- a/packages/api/src/client/types/app/bsky/graph/getFollows.ts +++ b/packages/api/src/client/types/app/bsky/graph/getFollows.ts @@ -3,10 +3,14 @@ */ import { HeadersMap, XRPCError } from '@atproto/xrpc' import { ValidationResult, BlobRef } from '@atproto/lexicon' -import { isObj, hasProp } from '../../../../util' -import { lexicons } from '../../../../lexicons' import { CID } from 'multiformats/cid' -import * as AppBskyActorDefs from '../actor/defs' +import { validate as _validate } from '../../../../lexicons' +import { $Type, $Typed, is$typed as _is$typed, OmitKey } from '../../../../util' +import type * as AppBskyActorDefs from '../actor/defs' + +const is$typed = _is$typed, + validate = _validate +const id = 'app.bsky.graph.getFollows' export interface QueryParams { actor: string @@ -20,7 +24,6 @@ export interface OutputSchema { subject: AppBskyActorDefs.ProfileView cursor?: string follows: AppBskyActorDefs.ProfileView[] - [k: string]: unknown } export interface CallOptions { diff --git a/packages/api/src/client/types/app/bsky/graph/getKnownFollowers.ts b/packages/api/src/client/types/app/bsky/graph/getKnownFollowers.ts index f55649dc769..032dbf1d824 100644 --- a/packages/api/src/client/types/app/bsky/graph/getKnownFollowers.ts +++ b/packages/api/src/client/types/app/bsky/graph/getKnownFollowers.ts @@ -3,10 +3,14 @@ */ import { HeadersMap, XRPCError } from '@atproto/xrpc' import { ValidationResult, BlobRef } from '@atproto/lexicon' -import { isObj, hasProp } from '../../../../util' -import { lexicons } from '../../../../lexicons' import { CID } from 'multiformats/cid' -import * as AppBskyActorDefs from '../actor/defs' +import { validate as _validate } from '../../../../lexicons' +import { $Type, $Typed, is$typed as _is$typed, OmitKey } from '../../../../util' +import type * as AppBskyActorDefs from '../actor/defs' + +const is$typed = _is$typed, + validate = _validate +const id = 'app.bsky.graph.getKnownFollowers' export interface QueryParams { actor: string @@ -20,7 +24,6 @@ export interface OutputSchema { subject: AppBskyActorDefs.ProfileView cursor?: string followers: AppBskyActorDefs.ProfileView[] - [k: string]: unknown } export interface CallOptions { diff --git a/packages/api/src/client/types/app/bsky/graph/getList.ts b/packages/api/src/client/types/app/bsky/graph/getList.ts index bebde2b49c3..b999932f096 100644 --- a/packages/api/src/client/types/app/bsky/graph/getList.ts +++ b/packages/api/src/client/types/app/bsky/graph/getList.ts @@ -3,10 +3,14 @@ */ import { HeadersMap, XRPCError } from '@atproto/xrpc' import { ValidationResult, BlobRef } from '@atproto/lexicon' -import { isObj, hasProp } from '../../../../util' -import { lexicons } from '../../../../lexicons' import { CID } from 'multiformats/cid' -import * as AppBskyGraphDefs from './defs' +import { validate as _validate } from '../../../../lexicons' +import { $Type, $Typed, is$typed as _is$typed, OmitKey } from '../../../../util' +import type * as AppBskyGraphDefs from './defs' + +const is$typed = _is$typed, + validate = _validate +const id = 'app.bsky.graph.getList' export interface QueryParams { /** Reference (AT-URI) of the list record to hydrate. */ @@ -21,7 +25,6 @@ export interface OutputSchema { cursor?: string list: AppBskyGraphDefs.ListView items: AppBskyGraphDefs.ListItemView[] - [k: string]: unknown } export interface CallOptions { diff --git a/packages/api/src/client/types/app/bsky/graph/getListBlocks.ts b/packages/api/src/client/types/app/bsky/graph/getListBlocks.ts index d3c7ad5ed7b..b93c63c4ae6 100644 --- a/packages/api/src/client/types/app/bsky/graph/getListBlocks.ts +++ b/packages/api/src/client/types/app/bsky/graph/getListBlocks.ts @@ -3,10 +3,14 @@ */ import { HeadersMap, XRPCError } from '@atproto/xrpc' import { ValidationResult, BlobRef } from '@atproto/lexicon' -import { isObj, hasProp } from '../../../../util' -import { lexicons } from '../../../../lexicons' import { CID } from 'multiformats/cid' -import * as AppBskyGraphDefs from './defs' +import { validate as _validate } from '../../../../lexicons' +import { $Type, $Typed, is$typed as _is$typed, OmitKey } from '../../../../util' +import type * as AppBskyGraphDefs from './defs' + +const is$typed = _is$typed, + validate = _validate +const id = 'app.bsky.graph.getListBlocks' export interface QueryParams { limit?: number @@ -18,7 +22,6 @@ export type InputSchema = undefined export interface OutputSchema { cursor?: string lists: AppBskyGraphDefs.ListView[] - [k: string]: unknown } export interface CallOptions { diff --git a/packages/api/src/client/types/app/bsky/graph/getListMutes.ts b/packages/api/src/client/types/app/bsky/graph/getListMutes.ts index d3c7ad5ed7b..b0448e29ec6 100644 --- a/packages/api/src/client/types/app/bsky/graph/getListMutes.ts +++ b/packages/api/src/client/types/app/bsky/graph/getListMutes.ts @@ -3,10 +3,14 @@ */ import { HeadersMap, XRPCError } from '@atproto/xrpc' import { ValidationResult, BlobRef } from '@atproto/lexicon' -import { isObj, hasProp } from '../../../../util' -import { lexicons } from '../../../../lexicons' import { CID } from 'multiformats/cid' -import * as AppBskyGraphDefs from './defs' +import { validate as _validate } from '../../../../lexicons' +import { $Type, $Typed, is$typed as _is$typed, OmitKey } from '../../../../util' +import type * as AppBskyGraphDefs from './defs' + +const is$typed = _is$typed, + validate = _validate +const id = 'app.bsky.graph.getListMutes' export interface QueryParams { limit?: number @@ -18,7 +22,6 @@ export type InputSchema = undefined export interface OutputSchema { cursor?: string lists: AppBskyGraphDefs.ListView[] - [k: string]: unknown } export interface CallOptions { diff --git a/packages/api/src/client/types/app/bsky/graph/getLists.ts b/packages/api/src/client/types/app/bsky/graph/getLists.ts index 89a4bfd58df..46f054e1fe5 100644 --- a/packages/api/src/client/types/app/bsky/graph/getLists.ts +++ b/packages/api/src/client/types/app/bsky/graph/getLists.ts @@ -3,10 +3,14 @@ */ import { HeadersMap, XRPCError } from '@atproto/xrpc' import { ValidationResult, BlobRef } from '@atproto/lexicon' -import { isObj, hasProp } from '../../../../util' -import { lexicons } from '../../../../lexicons' import { CID } from 'multiformats/cid' -import * as AppBskyGraphDefs from './defs' +import { validate as _validate } from '../../../../lexicons' +import { $Type, $Typed, is$typed as _is$typed, OmitKey } from '../../../../util' +import type * as AppBskyGraphDefs from './defs' + +const is$typed = _is$typed, + validate = _validate +const id = 'app.bsky.graph.getLists' export interface QueryParams { /** The account (actor) to enumerate lists from. */ @@ -20,7 +24,6 @@ export type InputSchema = undefined export interface OutputSchema { cursor?: string lists: AppBskyGraphDefs.ListView[] - [k: string]: unknown } export interface CallOptions { diff --git a/packages/api/src/client/types/app/bsky/graph/getMutes.ts b/packages/api/src/client/types/app/bsky/graph/getMutes.ts index 0ee441cf9eb..ede28a39491 100644 --- a/packages/api/src/client/types/app/bsky/graph/getMutes.ts +++ b/packages/api/src/client/types/app/bsky/graph/getMutes.ts @@ -3,10 +3,14 @@ */ import { HeadersMap, XRPCError } from '@atproto/xrpc' import { ValidationResult, BlobRef } from '@atproto/lexicon' -import { isObj, hasProp } from '../../../../util' -import { lexicons } from '../../../../lexicons' import { CID } from 'multiformats/cid' -import * as AppBskyActorDefs from '../actor/defs' +import { validate as _validate } from '../../../../lexicons' +import { $Type, $Typed, is$typed as _is$typed, OmitKey } from '../../../../util' +import type * as AppBskyActorDefs from '../actor/defs' + +const is$typed = _is$typed, + validate = _validate +const id = 'app.bsky.graph.getMutes' export interface QueryParams { limit?: number @@ -18,7 +22,6 @@ export type InputSchema = undefined export interface OutputSchema { cursor?: string mutes: AppBskyActorDefs.ProfileView[] - [k: string]: unknown } export interface CallOptions { diff --git a/packages/api/src/client/types/app/bsky/graph/getRelationships.ts b/packages/api/src/client/types/app/bsky/graph/getRelationships.ts index 20194662b71..6c1e521ca8f 100644 --- a/packages/api/src/client/types/app/bsky/graph/getRelationships.ts +++ b/packages/api/src/client/types/app/bsky/graph/getRelationships.ts @@ -3,10 +3,14 @@ */ import { HeadersMap, XRPCError } from '@atproto/xrpc' import { ValidationResult, BlobRef } from '@atproto/lexicon' -import { isObj, hasProp } from '../../../../util' -import { lexicons } from '../../../../lexicons' import { CID } from 'multiformats/cid' -import * as AppBskyGraphDefs from './defs' +import { validate as _validate } from '../../../../lexicons' +import { $Type, $Typed, is$typed as _is$typed, OmitKey } from '../../../../util' +import type * as AppBskyGraphDefs from './defs' + +const is$typed = _is$typed, + validate = _validate +const id = 'app.bsky.graph.getRelationships' export interface QueryParams { /** Primary account requesting relationships for. */ @@ -20,11 +24,10 @@ export type InputSchema = undefined export interface OutputSchema { actor?: string relationships: ( - | AppBskyGraphDefs.Relationship - | AppBskyGraphDefs.NotFoundActor - | { $type: string; [k: string]: unknown } + | $Typed + | $Typed + | { $type: string } )[] - [k: string]: unknown } export interface CallOptions { diff --git a/packages/api/src/client/types/app/bsky/graph/getStarterPack.ts b/packages/api/src/client/types/app/bsky/graph/getStarterPack.ts index 7eafb2d228d..de3fb97be31 100644 --- a/packages/api/src/client/types/app/bsky/graph/getStarterPack.ts +++ b/packages/api/src/client/types/app/bsky/graph/getStarterPack.ts @@ -3,10 +3,14 @@ */ import { HeadersMap, XRPCError } from '@atproto/xrpc' import { ValidationResult, BlobRef } from '@atproto/lexicon' -import { isObj, hasProp } from '../../../../util' -import { lexicons } from '../../../../lexicons' import { CID } from 'multiformats/cid' -import * as AppBskyGraphDefs from './defs' +import { validate as _validate } from '../../../../lexicons' +import { $Type, $Typed, is$typed as _is$typed, OmitKey } from '../../../../util' +import type * as AppBskyGraphDefs from './defs' + +const is$typed = _is$typed, + validate = _validate +const id = 'app.bsky.graph.getStarterPack' export interface QueryParams { /** Reference (AT-URI) of the starter pack record. */ @@ -17,7 +21,6 @@ export type InputSchema = undefined export interface OutputSchema { starterPack: AppBskyGraphDefs.StarterPackView - [k: string]: unknown } export interface CallOptions { diff --git a/packages/api/src/client/types/app/bsky/graph/getStarterPacks.ts b/packages/api/src/client/types/app/bsky/graph/getStarterPacks.ts index 50dbfdbaeda..fdce17fd8bf 100644 --- a/packages/api/src/client/types/app/bsky/graph/getStarterPacks.ts +++ b/packages/api/src/client/types/app/bsky/graph/getStarterPacks.ts @@ -3,10 +3,14 @@ */ import { HeadersMap, XRPCError } from '@atproto/xrpc' import { ValidationResult, BlobRef } from '@atproto/lexicon' -import { isObj, hasProp } from '../../../../util' -import { lexicons } from '../../../../lexicons' import { CID } from 'multiformats/cid' -import * as AppBskyGraphDefs from './defs' +import { validate as _validate } from '../../../../lexicons' +import { $Type, $Typed, is$typed as _is$typed, OmitKey } from '../../../../util' +import type * as AppBskyGraphDefs from './defs' + +const is$typed = _is$typed, + validate = _validate +const id = 'app.bsky.graph.getStarterPacks' export interface QueryParams { uris: string[] @@ -16,7 +20,6 @@ export type InputSchema = undefined export interface OutputSchema { starterPacks: AppBskyGraphDefs.StarterPackViewBasic[] - [k: string]: unknown } export interface CallOptions { diff --git a/packages/api/src/client/types/app/bsky/graph/getSuggestedFollowsByActor.ts b/packages/api/src/client/types/app/bsky/graph/getSuggestedFollowsByActor.ts index fb27392d1b7..c5c7c1cf433 100644 --- a/packages/api/src/client/types/app/bsky/graph/getSuggestedFollowsByActor.ts +++ b/packages/api/src/client/types/app/bsky/graph/getSuggestedFollowsByActor.ts @@ -3,10 +3,14 @@ */ import { HeadersMap, XRPCError } from '@atproto/xrpc' import { ValidationResult, BlobRef } from '@atproto/lexicon' -import { isObj, hasProp } from '../../../../util' -import { lexicons } from '../../../../lexicons' import { CID } from 'multiformats/cid' -import * as AppBskyActorDefs from '../actor/defs' +import { validate as _validate } from '../../../../lexicons' +import { $Type, $Typed, is$typed as _is$typed, OmitKey } from '../../../../util' +import type * as AppBskyActorDefs from '../actor/defs' + +const is$typed = _is$typed, + validate = _validate +const id = 'app.bsky.graph.getSuggestedFollowsByActor' export interface QueryParams { actor: string @@ -20,7 +24,6 @@ export interface OutputSchema { isFallback: boolean /** Snowflake for this recommendation, use when submitting recommendation events. */ recId?: number - [k: string]: unknown } export interface CallOptions { diff --git a/packages/api/src/client/types/app/bsky/graph/list.ts b/packages/api/src/client/types/app/bsky/graph/list.ts index fec652ccb12..9964f4a6f7f 100644 --- a/packages/api/src/client/types/app/bsky/graph/list.ts +++ b/packages/api/src/client/types/app/bsky/graph/list.ts @@ -2,36 +2,36 @@ * GENERATED CODE - DO NOT MODIFY */ import { ValidationResult, BlobRef } from '@atproto/lexicon' -import { isObj, hasProp } from '../../../../util' -import { lexicons } from '../../../../lexicons' import { CID } from 'multiformats/cid' -import * as AppBskyGraphDefs from './defs' -import * as AppBskyRichtextFacet from '../richtext/facet' -import * as ComAtprotoLabelDefs from '../../../com/atproto/label/defs' +import { validate as _validate } from '../../../../lexicons' +import { $Type, $Typed, is$typed as _is$typed, OmitKey } from '../../../../util' +import type * as AppBskyGraphDefs from './defs' +import type * as AppBskyRichtextFacet from '../richtext/facet' +import type * as ComAtprotoLabelDefs from '../../../com/atproto/label/defs' + +const is$typed = _is$typed, + validate = _validate +const id = 'app.bsky.graph.list' export interface Record { + $type: $Type<'app.bsky.graph.list', 'main'> purpose: AppBskyGraphDefs.ListPurpose /** Display name for list; can not be empty. */ name: string description?: string descriptionFacets?: AppBskyRichtextFacet.Main[] avatar?: BlobRef - labels?: - | ComAtprotoLabelDefs.SelfLabels - | { $type: string; [k: string]: unknown } + labels?: $Typed | { $type: string } createdAt: string [k: string]: unknown } -export function isRecord(v: unknown): v is Record { - return ( - isObj(v) && - hasProp(v, '$type') && - (v.$type === 'app.bsky.graph.list#main' || - v.$type === 'app.bsky.graph.list') - ) +const hashRecord = 'main' + +export function isRecord(v: V) { + return is$typed(v, id, hashRecord) } -export function validateRecord(v: unknown): ValidationResult { - return lexicons.validate('app.bsky.graph.list#main', v) +export function validateRecord(v: V) { + return validate(v, id, hashRecord, true) } diff --git a/packages/api/src/client/types/app/bsky/graph/listblock.ts b/packages/api/src/client/types/app/bsky/graph/listblock.ts index e0f02be268f..149617d599e 100644 --- a/packages/api/src/client/types/app/bsky/graph/listblock.ts +++ b/packages/api/src/client/types/app/bsky/graph/listblock.ts @@ -2,26 +2,28 @@ * GENERATED CODE - DO NOT MODIFY */ import { ValidationResult, BlobRef } from '@atproto/lexicon' -import { isObj, hasProp } from '../../../../util' -import { lexicons } from '../../../../lexicons' import { CID } from 'multiformats/cid' +import { validate as _validate } from '../../../../lexicons' +import { $Type, $Typed, is$typed as _is$typed, OmitKey } from '../../../../util' + +const is$typed = _is$typed, + validate = _validate +const id = 'app.bsky.graph.listblock' export interface Record { + $type: $Type<'app.bsky.graph.listblock', 'main'> /** Reference (AT-URI) to the mod list record. */ subject: string createdAt: string [k: string]: unknown } -export function isRecord(v: unknown): v is Record { - return ( - isObj(v) && - hasProp(v, '$type') && - (v.$type === 'app.bsky.graph.listblock#main' || - v.$type === 'app.bsky.graph.listblock') - ) +const hashRecord = 'main' + +export function isRecord(v: V) { + return is$typed(v, id, hashRecord) } -export function validateRecord(v: unknown): ValidationResult { - return lexicons.validate('app.bsky.graph.listblock#main', v) +export function validateRecord(v: V) { + return validate(v, id, hashRecord, true) } diff --git a/packages/api/src/client/types/app/bsky/graph/listitem.ts b/packages/api/src/client/types/app/bsky/graph/listitem.ts index d4fb5631e84..f5bfd1e6daf 100644 --- a/packages/api/src/client/types/app/bsky/graph/listitem.ts +++ b/packages/api/src/client/types/app/bsky/graph/listitem.ts @@ -2,11 +2,16 @@ * GENERATED CODE - DO NOT MODIFY */ import { ValidationResult, BlobRef } from '@atproto/lexicon' -import { isObj, hasProp } from '../../../../util' -import { lexicons } from '../../../../lexicons' import { CID } from 'multiformats/cid' +import { validate as _validate } from '../../../../lexicons' +import { $Type, $Typed, is$typed as _is$typed, OmitKey } from '../../../../util' + +const is$typed = _is$typed, + validate = _validate +const id = 'app.bsky.graph.listitem' export interface Record { + $type: $Type<'app.bsky.graph.listitem', 'main'> /** The account which is included on the list. */ subject: string /** Reference (AT-URI) to the list record (app.bsky.graph.list). */ @@ -15,15 +20,12 @@ export interface Record { [k: string]: unknown } -export function isRecord(v: unknown): v is Record { - return ( - isObj(v) && - hasProp(v, '$type') && - (v.$type === 'app.bsky.graph.listitem#main' || - v.$type === 'app.bsky.graph.listitem') - ) +const hashRecord = 'main' + +export function isRecord(v: V) { + return is$typed(v, id, hashRecord) } -export function validateRecord(v: unknown): ValidationResult { - return lexicons.validate('app.bsky.graph.listitem#main', v) +export function validateRecord(v: V) { + return validate(v, id, hashRecord, true) } diff --git a/packages/api/src/client/types/app/bsky/graph/muteActor.ts b/packages/api/src/client/types/app/bsky/graph/muteActor.ts index c21c00b2069..76e06f70182 100644 --- a/packages/api/src/client/types/app/bsky/graph/muteActor.ts +++ b/packages/api/src/client/types/app/bsky/graph/muteActor.ts @@ -3,15 +3,18 @@ */ import { HeadersMap, XRPCError } from '@atproto/xrpc' import { ValidationResult, BlobRef } from '@atproto/lexicon' -import { isObj, hasProp } from '../../../../util' -import { lexicons } from '../../../../lexicons' import { CID } from 'multiformats/cid' +import { validate as _validate } from '../../../../lexicons' +import { $Type, $Typed, is$typed as _is$typed, OmitKey } from '../../../../util' + +const is$typed = _is$typed, + validate = _validate +const id = 'app.bsky.graph.muteActor' export interface QueryParams {} export interface InputSchema { actor: string - [k: string]: unknown } export interface CallOptions { diff --git a/packages/api/src/client/types/app/bsky/graph/muteActorList.ts b/packages/api/src/client/types/app/bsky/graph/muteActorList.ts index 8a0e8586deb..9c02cd8b080 100644 --- a/packages/api/src/client/types/app/bsky/graph/muteActorList.ts +++ b/packages/api/src/client/types/app/bsky/graph/muteActorList.ts @@ -3,15 +3,18 @@ */ import { HeadersMap, XRPCError } from '@atproto/xrpc' import { ValidationResult, BlobRef } from '@atproto/lexicon' -import { isObj, hasProp } from '../../../../util' -import { lexicons } from '../../../../lexicons' import { CID } from 'multiformats/cid' +import { validate as _validate } from '../../../../lexicons' +import { $Type, $Typed, is$typed as _is$typed, OmitKey } from '../../../../util' + +const is$typed = _is$typed, + validate = _validate +const id = 'app.bsky.graph.muteActorList' export interface QueryParams {} export interface InputSchema { list: string - [k: string]: unknown } export interface CallOptions { diff --git a/packages/api/src/client/types/app/bsky/graph/muteThread.ts b/packages/api/src/client/types/app/bsky/graph/muteThread.ts index 275ba7a0f22..b9e0c869c75 100644 --- a/packages/api/src/client/types/app/bsky/graph/muteThread.ts +++ b/packages/api/src/client/types/app/bsky/graph/muteThread.ts @@ -3,15 +3,18 @@ */ import { HeadersMap, XRPCError } from '@atproto/xrpc' import { ValidationResult, BlobRef } from '@atproto/lexicon' -import { isObj, hasProp } from '../../../../util' -import { lexicons } from '../../../../lexicons' import { CID } from 'multiformats/cid' +import { validate as _validate } from '../../../../lexicons' +import { $Type, $Typed, is$typed as _is$typed, OmitKey } from '../../../../util' + +const is$typed = _is$typed, + validate = _validate +const id = 'app.bsky.graph.muteThread' export interface QueryParams {} export interface InputSchema { root: string - [k: string]: unknown } export interface CallOptions { diff --git a/packages/api/src/client/types/app/bsky/graph/searchStarterPacks.ts b/packages/api/src/client/types/app/bsky/graph/searchStarterPacks.ts index 8291b219a62..d9bf5758112 100644 --- a/packages/api/src/client/types/app/bsky/graph/searchStarterPacks.ts +++ b/packages/api/src/client/types/app/bsky/graph/searchStarterPacks.ts @@ -3,10 +3,14 @@ */ import { HeadersMap, XRPCError } from '@atproto/xrpc' import { ValidationResult, BlobRef } from '@atproto/lexicon' -import { isObj, hasProp } from '../../../../util' -import { lexicons } from '../../../../lexicons' import { CID } from 'multiformats/cid' -import * as AppBskyGraphDefs from './defs' +import { validate as _validate } from '../../../../lexicons' +import { $Type, $Typed, is$typed as _is$typed, OmitKey } from '../../../../util' +import type * as AppBskyGraphDefs from './defs' + +const is$typed = _is$typed, + validate = _validate +const id = 'app.bsky.graph.searchStarterPacks' export interface QueryParams { /** Search query string. Syntax, phrase, boolean, and faceting is unspecified, but Lucene query syntax is recommended. */ @@ -20,7 +24,6 @@ export type InputSchema = undefined export interface OutputSchema { cursor?: string starterPacks: AppBskyGraphDefs.StarterPackViewBasic[] - [k: string]: unknown } export interface CallOptions { diff --git a/packages/api/src/client/types/app/bsky/graph/starterpack.ts b/packages/api/src/client/types/app/bsky/graph/starterpack.ts index 7cb0aa26c3f..9b17c4ac083 100644 --- a/packages/api/src/client/types/app/bsky/graph/starterpack.ts +++ b/packages/api/src/client/types/app/bsky/graph/starterpack.ts @@ -2,12 +2,17 @@ * GENERATED CODE - DO NOT MODIFY */ import { ValidationResult, BlobRef } from '@atproto/lexicon' -import { isObj, hasProp } from '../../../../util' -import { lexicons } from '../../../../lexicons' import { CID } from 'multiformats/cid' -import * as AppBskyRichtextFacet from '../richtext/facet' +import { validate as _validate } from '../../../../lexicons' +import { $Type, $Typed, is$typed as _is$typed, OmitKey } from '../../../../util' +import type * as AppBskyRichtextFacet from '../richtext/facet' + +const is$typed = _is$typed, + validate = _validate +const id = 'app.bsky.graph.starterpack' export interface Record { + $type: $Type<'app.bsky.graph.starterpack', 'main'> /** Display name for starter pack; can not be empty. */ name: string description?: string @@ -19,32 +24,27 @@ export interface Record { [k: string]: unknown } -export function isRecord(v: unknown): v is Record { - return ( - isObj(v) && - hasProp(v, '$type') && - (v.$type === 'app.bsky.graph.starterpack#main' || - v.$type === 'app.bsky.graph.starterpack') - ) +const hashRecord = 'main' + +export function isRecord(v: V) { + return is$typed(v, id, hashRecord) } -export function validateRecord(v: unknown): ValidationResult { - return lexicons.validate('app.bsky.graph.starterpack#main', v) +export function validateRecord(v: V) { + return validate(v, id, hashRecord, true) } export interface FeedItem { + $type?: $Type<'app.bsky.graph.starterpack', 'feedItem'> uri: string - [k: string]: unknown } -export function isFeedItem(v: unknown): v is FeedItem { - return ( - isObj(v) && - hasProp(v, '$type') && - v.$type === 'app.bsky.graph.starterpack#feedItem' - ) +const hashFeedItem = 'feedItem' + +export function isFeedItem(v: V) { + return is$typed(v, id, hashFeedItem) } -export function validateFeedItem(v: unknown): ValidationResult { - return lexicons.validate('app.bsky.graph.starterpack#feedItem', v) +export function validateFeedItem(v: V) { + return validate(v, id, hashFeedItem) } diff --git a/packages/api/src/client/types/app/bsky/graph/unmuteActor.ts b/packages/api/src/client/types/app/bsky/graph/unmuteActor.ts index c21c00b2069..f5c3b20f4a4 100644 --- a/packages/api/src/client/types/app/bsky/graph/unmuteActor.ts +++ b/packages/api/src/client/types/app/bsky/graph/unmuteActor.ts @@ -3,15 +3,18 @@ */ import { HeadersMap, XRPCError } from '@atproto/xrpc' import { ValidationResult, BlobRef } from '@atproto/lexicon' -import { isObj, hasProp } from '../../../../util' -import { lexicons } from '../../../../lexicons' import { CID } from 'multiformats/cid' +import { validate as _validate } from '../../../../lexicons' +import { $Type, $Typed, is$typed as _is$typed, OmitKey } from '../../../../util' + +const is$typed = _is$typed, + validate = _validate +const id = 'app.bsky.graph.unmuteActor' export interface QueryParams {} export interface InputSchema { actor: string - [k: string]: unknown } export interface CallOptions { diff --git a/packages/api/src/client/types/app/bsky/graph/unmuteActorList.ts b/packages/api/src/client/types/app/bsky/graph/unmuteActorList.ts index 8a0e8586deb..727da7b3c7e 100644 --- a/packages/api/src/client/types/app/bsky/graph/unmuteActorList.ts +++ b/packages/api/src/client/types/app/bsky/graph/unmuteActorList.ts @@ -3,15 +3,18 @@ */ import { HeadersMap, XRPCError } from '@atproto/xrpc' import { ValidationResult, BlobRef } from '@atproto/lexicon' -import { isObj, hasProp } from '../../../../util' -import { lexicons } from '../../../../lexicons' import { CID } from 'multiformats/cid' +import { validate as _validate } from '../../../../lexicons' +import { $Type, $Typed, is$typed as _is$typed, OmitKey } from '../../../../util' + +const is$typed = _is$typed, + validate = _validate +const id = 'app.bsky.graph.unmuteActorList' export interface QueryParams {} export interface InputSchema { list: string - [k: string]: unknown } export interface CallOptions { diff --git a/packages/api/src/client/types/app/bsky/graph/unmuteThread.ts b/packages/api/src/client/types/app/bsky/graph/unmuteThread.ts index 275ba7a0f22..f11450c3b58 100644 --- a/packages/api/src/client/types/app/bsky/graph/unmuteThread.ts +++ b/packages/api/src/client/types/app/bsky/graph/unmuteThread.ts @@ -3,15 +3,18 @@ */ import { HeadersMap, XRPCError } from '@atproto/xrpc' import { ValidationResult, BlobRef } from '@atproto/lexicon' -import { isObj, hasProp } from '../../../../util' -import { lexicons } from '../../../../lexicons' import { CID } from 'multiformats/cid' +import { validate as _validate } from '../../../../lexicons' +import { $Type, $Typed, is$typed as _is$typed, OmitKey } from '../../../../util' + +const is$typed = _is$typed, + validate = _validate +const id = 'app.bsky.graph.unmuteThread' export interface QueryParams {} export interface InputSchema { root: string - [k: string]: unknown } export interface CallOptions { diff --git a/packages/api/src/client/types/app/bsky/labeler/defs.ts b/packages/api/src/client/types/app/bsky/labeler/defs.ts index 3d9b1d77f8a..deb7780daa7 100644 --- a/packages/api/src/client/types/app/bsky/labeler/defs.ts +++ b/packages/api/src/client/types/app/bsky/labeler/defs.ts @@ -2,13 +2,18 @@ * GENERATED CODE - DO NOT MODIFY */ import { ValidationResult, BlobRef } from '@atproto/lexicon' -import { isObj, hasProp } from '../../../../util' -import { lexicons } from '../../../../lexicons' import { CID } from 'multiformats/cid' -import * as AppBskyActorDefs from '../actor/defs' -import * as ComAtprotoLabelDefs from '../../../com/atproto/label/defs' +import { validate as _validate } from '../../../../lexicons' +import { $Type, $Typed, is$typed as _is$typed, OmitKey } from '../../../../util' +import type * as AppBskyActorDefs from '../actor/defs' +import type * as ComAtprotoLabelDefs from '../../../com/atproto/label/defs' + +const is$typed = _is$typed, + validate = _validate +const id = 'app.bsky.labeler.defs' export interface LabelerView { + $type?: $Type<'app.bsky.labeler.defs', 'labelerView'> uri: string cid: string creator: AppBskyActorDefs.ProfileView @@ -16,22 +21,20 @@ export interface LabelerView { viewer?: LabelerViewerState indexedAt: string labels?: ComAtprotoLabelDefs.Label[] - [k: string]: unknown } -export function isLabelerView(v: unknown): v is LabelerView { - return ( - isObj(v) && - hasProp(v, '$type') && - v.$type === 'app.bsky.labeler.defs#labelerView' - ) +const hashLabelerView = 'labelerView' + +export function isLabelerView(v: V) { + return is$typed(v, id, hashLabelerView) } -export function validateLabelerView(v: unknown): ValidationResult { - return lexicons.validate('app.bsky.labeler.defs#labelerView', v) +export function validateLabelerView(v: V) { + return validate(v, id, hashLabelerView) } export interface LabelerViewDetailed { + $type?: $Type<'app.bsky.labeler.defs', 'labelerViewDetailed'> uri: string cid: string creator: AppBskyActorDefs.ProfileView @@ -40,54 +43,47 @@ export interface LabelerViewDetailed { viewer?: LabelerViewerState indexedAt: string labels?: ComAtprotoLabelDefs.Label[] - [k: string]: unknown } -export function isLabelerViewDetailed(v: unknown): v is LabelerViewDetailed { - return ( - isObj(v) && - hasProp(v, '$type') && - v.$type === 'app.bsky.labeler.defs#labelerViewDetailed' - ) +const hashLabelerViewDetailed = 'labelerViewDetailed' + +export function isLabelerViewDetailed(v: V) { + return is$typed(v, id, hashLabelerViewDetailed) } -export function validateLabelerViewDetailed(v: unknown): ValidationResult { - return lexicons.validate('app.bsky.labeler.defs#labelerViewDetailed', v) +export function validateLabelerViewDetailed(v: V) { + return validate(v, id, hashLabelerViewDetailed) } export interface LabelerViewerState { + $type?: $Type<'app.bsky.labeler.defs', 'labelerViewerState'> like?: string - [k: string]: unknown } -export function isLabelerViewerState(v: unknown): v is LabelerViewerState { - return ( - isObj(v) && - hasProp(v, '$type') && - v.$type === 'app.bsky.labeler.defs#labelerViewerState' - ) +const hashLabelerViewerState = 'labelerViewerState' + +export function isLabelerViewerState(v: V) { + return is$typed(v, id, hashLabelerViewerState) } -export function validateLabelerViewerState(v: unknown): ValidationResult { - return lexicons.validate('app.bsky.labeler.defs#labelerViewerState', v) +export function validateLabelerViewerState(v: V) { + return validate(v, id, hashLabelerViewerState) } export interface LabelerPolicies { + $type?: $Type<'app.bsky.labeler.defs', 'labelerPolicies'> /** The label values which this labeler publishes. May include global or custom labels. */ labelValues: ComAtprotoLabelDefs.LabelValue[] /** Label values created by this labeler and scoped exclusively to it. Labels defined here will override global label definitions for this labeler. */ labelValueDefinitions?: ComAtprotoLabelDefs.LabelValueDefinition[] - [k: string]: unknown } -export function isLabelerPolicies(v: unknown): v is LabelerPolicies { - return ( - isObj(v) && - hasProp(v, '$type') && - v.$type === 'app.bsky.labeler.defs#labelerPolicies' - ) +const hashLabelerPolicies = 'labelerPolicies' + +export function isLabelerPolicies(v: V) { + return is$typed(v, id, hashLabelerPolicies) } -export function validateLabelerPolicies(v: unknown): ValidationResult { - return lexicons.validate('app.bsky.labeler.defs#labelerPolicies', v) +export function validateLabelerPolicies(v: V) { + return validate(v, id, hashLabelerPolicies) } diff --git a/packages/api/src/client/types/app/bsky/labeler/getServices.ts b/packages/api/src/client/types/app/bsky/labeler/getServices.ts index 688f847d508..cfb842bb505 100644 --- a/packages/api/src/client/types/app/bsky/labeler/getServices.ts +++ b/packages/api/src/client/types/app/bsky/labeler/getServices.ts @@ -3,10 +3,14 @@ */ import { HeadersMap, XRPCError } from '@atproto/xrpc' import { ValidationResult, BlobRef } from '@atproto/lexicon' -import { isObj, hasProp } from '../../../../util' -import { lexicons } from '../../../../lexicons' import { CID } from 'multiformats/cid' -import * as AppBskyLabelerDefs from './defs' +import { validate as _validate } from '../../../../lexicons' +import { $Type, $Typed, is$typed as _is$typed, OmitKey } from '../../../../util' +import type * as AppBskyLabelerDefs from './defs' + +const is$typed = _is$typed, + validate = _validate +const id = 'app.bsky.labeler.getServices' export interface QueryParams { dids: string[] @@ -17,11 +21,10 @@ export type InputSchema = undefined export interface OutputSchema { views: ( - | AppBskyLabelerDefs.LabelerView - | AppBskyLabelerDefs.LabelerViewDetailed - | { $type: string; [k: string]: unknown } + | $Typed + | $Typed + | { $type: string } )[] - [k: string]: unknown } export interface CallOptions { diff --git a/packages/api/src/client/types/app/bsky/labeler/service.ts b/packages/api/src/client/types/app/bsky/labeler/service.ts index 818249468ec..8918d152a64 100644 --- a/packages/api/src/client/types/app/bsky/labeler/service.ts +++ b/packages/api/src/client/types/app/bsky/labeler/service.ts @@ -2,30 +2,30 @@ * GENERATED CODE - DO NOT MODIFY */ import { ValidationResult, BlobRef } from '@atproto/lexicon' -import { isObj, hasProp } from '../../../../util' -import { lexicons } from '../../../../lexicons' import { CID } from 'multiformats/cid' -import * as AppBskyLabelerDefs from './defs' -import * as ComAtprotoLabelDefs from '../../../com/atproto/label/defs' +import { validate as _validate } from '../../../../lexicons' +import { $Type, $Typed, is$typed as _is$typed, OmitKey } from '../../../../util' +import type * as AppBskyLabelerDefs from './defs' +import type * as ComAtprotoLabelDefs from '../../../com/atproto/label/defs' + +const is$typed = _is$typed, + validate = _validate +const id = 'app.bsky.labeler.service' export interface Record { + $type: $Type<'app.bsky.labeler.service', 'main'> policies: AppBskyLabelerDefs.LabelerPolicies - labels?: - | ComAtprotoLabelDefs.SelfLabels - | { $type: string; [k: string]: unknown } + labels?: $Typed | { $type: string } createdAt: string [k: string]: unknown } -export function isRecord(v: unknown): v is Record { - return ( - isObj(v) && - hasProp(v, '$type') && - (v.$type === 'app.bsky.labeler.service#main' || - v.$type === 'app.bsky.labeler.service') - ) +const hashRecord = 'main' + +export function isRecord(v: V) { + return is$typed(v, id, hashRecord) } -export function validateRecord(v: unknown): ValidationResult { - return lexicons.validate('app.bsky.labeler.service#main', v) +export function validateRecord(v: V) { + return validate(v, id, hashRecord, true) } diff --git a/packages/api/src/client/types/app/bsky/notification/getUnreadCount.ts b/packages/api/src/client/types/app/bsky/notification/getUnreadCount.ts index 00600ea54e7..b9ee9b1df1d 100644 --- a/packages/api/src/client/types/app/bsky/notification/getUnreadCount.ts +++ b/packages/api/src/client/types/app/bsky/notification/getUnreadCount.ts @@ -3,9 +3,13 @@ */ import { HeadersMap, XRPCError } from '@atproto/xrpc' import { ValidationResult, BlobRef } from '@atproto/lexicon' -import { isObj, hasProp } from '../../../../util' -import { lexicons } from '../../../../lexicons' import { CID } from 'multiformats/cid' +import { validate as _validate } from '../../../../lexicons' +import { $Type, $Typed, is$typed as _is$typed, OmitKey } from '../../../../util' + +const is$typed = _is$typed, + validate = _validate +const id = 'app.bsky.notification.getUnreadCount' export interface QueryParams { priority?: boolean @@ -16,7 +20,6 @@ export type InputSchema = undefined export interface OutputSchema { count: number - [k: string]: unknown } export interface CallOptions { diff --git a/packages/api/src/client/types/app/bsky/notification/listNotifications.ts b/packages/api/src/client/types/app/bsky/notification/listNotifications.ts index 92b3a27fece..87204791221 100644 --- a/packages/api/src/client/types/app/bsky/notification/listNotifications.ts +++ b/packages/api/src/client/types/app/bsky/notification/listNotifications.ts @@ -3,11 +3,15 @@ */ import { HeadersMap, XRPCError } from '@atproto/xrpc' import { ValidationResult, BlobRef } from '@atproto/lexicon' -import { isObj, hasProp } from '../../../../util' -import { lexicons } from '../../../../lexicons' import { CID } from 'multiformats/cid' -import * as AppBskyActorDefs from '../actor/defs' -import * as ComAtprotoLabelDefs from '../../../com/atproto/label/defs' +import { validate as _validate } from '../../../../lexicons' +import { $Type, $Typed, is$typed as _is$typed, OmitKey } from '../../../../util' +import type * as AppBskyActorDefs from '../actor/defs' +import type * as ComAtprotoLabelDefs from '../../../com/atproto/label/defs' + +const is$typed = _is$typed, + validate = _validate +const id = 'app.bsky.notification.listNotifications' export interface QueryParams { /** Notification reasons to include in response. */ @@ -25,7 +29,6 @@ export interface OutputSchema { notifications: Notification[] priority?: boolean seenAt?: string - [k: string]: unknown } export interface CallOptions { @@ -44,6 +47,7 @@ export function toKnownErr(e: any) { } export interface Notification { + $type?: $Type<'app.bsky.notification.listNotifications', 'notification'> uri: string cid: string author: AppBskyActorDefs.ProfileView @@ -58,24 +62,18 @@ export interface Notification { | 'starterpack-joined' | (string & {}) reasonSubject?: string - record: {} + record: { [_ in string]: unknown } isRead: boolean indexedAt: string labels?: ComAtprotoLabelDefs.Label[] - [k: string]: unknown } -export function isNotification(v: unknown): v is Notification { - return ( - isObj(v) && - hasProp(v, '$type') && - v.$type === 'app.bsky.notification.listNotifications#notification' - ) +const hashNotification = 'notification' + +export function isNotification(v: V) { + return is$typed(v, id, hashNotification) } -export function validateNotification(v: unknown): ValidationResult { - return lexicons.validate( - 'app.bsky.notification.listNotifications#notification', - v, - ) +export function validateNotification(v: V) { + return validate(v, id, hashNotification) } diff --git a/packages/api/src/client/types/app/bsky/notification/putPreferences.ts b/packages/api/src/client/types/app/bsky/notification/putPreferences.ts index bc79909f459..e92e5f885f5 100644 --- a/packages/api/src/client/types/app/bsky/notification/putPreferences.ts +++ b/packages/api/src/client/types/app/bsky/notification/putPreferences.ts @@ -3,15 +3,18 @@ */ import { HeadersMap, XRPCError } from '@atproto/xrpc' import { ValidationResult, BlobRef } from '@atproto/lexicon' -import { isObj, hasProp } from '../../../../util' -import { lexicons } from '../../../../lexicons' import { CID } from 'multiformats/cid' +import { validate as _validate } from '../../../../lexicons' +import { $Type, $Typed, is$typed as _is$typed, OmitKey } from '../../../../util' + +const is$typed = _is$typed, + validate = _validate +const id = 'app.bsky.notification.putPreferences' export interface QueryParams {} export interface InputSchema { priority: boolean - [k: string]: unknown } export interface CallOptions { diff --git a/packages/api/src/client/types/app/bsky/notification/registerPush.ts b/packages/api/src/client/types/app/bsky/notification/registerPush.ts index ddc9d438537..e826adbd3c3 100644 --- a/packages/api/src/client/types/app/bsky/notification/registerPush.ts +++ b/packages/api/src/client/types/app/bsky/notification/registerPush.ts @@ -3,9 +3,13 @@ */ import { HeadersMap, XRPCError } from '@atproto/xrpc' import { ValidationResult, BlobRef } from '@atproto/lexicon' -import { isObj, hasProp } from '../../../../util' -import { lexicons } from '../../../../lexicons' import { CID } from 'multiformats/cid' +import { validate as _validate } from '../../../../lexicons' +import { $Type, $Typed, is$typed as _is$typed, OmitKey } from '../../../../util' + +const is$typed = _is$typed, + validate = _validate +const id = 'app.bsky.notification.registerPush' export interface QueryParams {} @@ -14,7 +18,6 @@ export interface InputSchema { token: string platform: 'ios' | 'android' | 'web' | (string & {}) appId: string - [k: string]: unknown } export interface CallOptions { diff --git a/packages/api/src/client/types/app/bsky/notification/updateSeen.ts b/packages/api/src/client/types/app/bsky/notification/updateSeen.ts index 7151e9b0cd4..f57480aab6a 100644 --- a/packages/api/src/client/types/app/bsky/notification/updateSeen.ts +++ b/packages/api/src/client/types/app/bsky/notification/updateSeen.ts @@ -3,15 +3,18 @@ */ import { HeadersMap, XRPCError } from '@atproto/xrpc' import { ValidationResult, BlobRef } from '@atproto/lexicon' -import { isObj, hasProp } from '../../../../util' -import { lexicons } from '../../../../lexicons' import { CID } from 'multiformats/cid' +import { validate as _validate } from '../../../../lexicons' +import { $Type, $Typed, is$typed as _is$typed, OmitKey } from '../../../../util' + +const is$typed = _is$typed, + validate = _validate +const id = 'app.bsky.notification.updateSeen' export interface QueryParams {} export interface InputSchema { seenAt: string - [k: string]: unknown } export interface CallOptions { diff --git a/packages/api/src/client/types/app/bsky/richtext/facet.ts b/packages/api/src/client/types/app/bsky/richtext/facet.ts index 836136b7dac..ae8f4b0b2d2 100644 --- a/packages/api/src/client/types/app/bsky/richtext/facet.ts +++ b/packages/api/src/client/types/app/bsky/richtext/facet.ts @@ -2,97 +2,92 @@ * GENERATED CODE - DO NOT MODIFY */ import { ValidationResult, BlobRef } from '@atproto/lexicon' -import { isObj, hasProp } from '../../../../util' -import { lexicons } from '../../../../lexicons' import { CID } from 'multiformats/cid' +import { validate as _validate } from '../../../../lexicons' +import { $Type, $Typed, is$typed as _is$typed, OmitKey } from '../../../../util' + +const is$typed = _is$typed, + validate = _validate +const id = 'app.bsky.richtext.facet' /** Annotation of a sub-string within rich text. */ export interface Main { + $type?: $Type<'app.bsky.richtext.facet', 'main'> index: ByteSlice - features: (Mention | Link | Tag | { $type: string; [k: string]: unknown })[] - [k: string]: unknown + features: ($Typed | $Typed | $Typed | { $type: string })[] } -export function isMain(v: unknown): v is Main { - return ( - isObj(v) && - hasProp(v, '$type') && - (v.$type === 'app.bsky.richtext.facet#main' || - v.$type === 'app.bsky.richtext.facet') - ) +const hashMain = 'main' + +export function isMain(v: V) { + return is$typed(v, id, hashMain) } -export function validateMain(v: unknown): ValidationResult { - return lexicons.validate('app.bsky.richtext.facet#main', v) +export function validateMain(v: V) { + return validate
(v, id, hashMain) } /** Facet feature for mention of another account. The text is usually a handle, including a '@' prefix, but the facet reference is a DID. */ export interface Mention { + $type?: $Type<'app.bsky.richtext.facet', 'mention'> did: string - [k: string]: unknown } -export function isMention(v: unknown): v is Mention { - return ( - isObj(v) && - hasProp(v, '$type') && - v.$type === 'app.bsky.richtext.facet#mention' - ) +const hashMention = 'mention' + +export function isMention(v: V) { + return is$typed(v, id, hashMention) } -export function validateMention(v: unknown): ValidationResult { - return lexicons.validate('app.bsky.richtext.facet#mention', v) +export function validateMention(v: V) { + return validate(v, id, hashMention) } /** Facet feature for a URL. The text URL may have been simplified or truncated, but the facet reference should be a complete URL. */ export interface Link { + $type?: $Type<'app.bsky.richtext.facet', 'link'> uri: string - [k: string]: unknown } -export function isLink(v: unknown): v is Link { - return ( - isObj(v) && - hasProp(v, '$type') && - v.$type === 'app.bsky.richtext.facet#link' - ) +const hashLink = 'link' + +export function isLink(v: V) { + return is$typed(v, id, hashLink) } -export function validateLink(v: unknown): ValidationResult { - return lexicons.validate('app.bsky.richtext.facet#link', v) +export function validateLink(v: V) { + return validate(v, id, hashLink) } /** Facet feature for a hashtag. The text usually includes a '#' prefix, but the facet reference should not (except in the case of 'double hash tags'). */ export interface Tag { + $type?: $Type<'app.bsky.richtext.facet', 'tag'> tag: string - [k: string]: unknown } -export function isTag(v: unknown): v is Tag { - return ( - isObj(v) && hasProp(v, '$type') && v.$type === 'app.bsky.richtext.facet#tag' - ) +const hashTag = 'tag' + +export function isTag(v: V) { + return is$typed(v, id, hashTag) } -export function validateTag(v: unknown): ValidationResult { - return lexicons.validate('app.bsky.richtext.facet#tag', v) +export function validateTag(v: V) { + return validate(v, id, hashTag) } /** Specifies the sub-string range a facet feature applies to. Start index is inclusive, end index is exclusive. Indices are zero-indexed, counting bytes of the UTF-8 encoded text. NOTE: some languages, like Javascript, use UTF-16 or Unicode codepoints for string slice indexing; in these languages, convert to byte arrays before working with facets. */ export interface ByteSlice { + $type?: $Type<'app.bsky.richtext.facet', 'byteSlice'> byteStart: number byteEnd: number - [k: string]: unknown } -export function isByteSlice(v: unknown): v is ByteSlice { - return ( - isObj(v) && - hasProp(v, '$type') && - v.$type === 'app.bsky.richtext.facet#byteSlice' - ) +const hashByteSlice = 'byteSlice' + +export function isByteSlice(v: V) { + return is$typed(v, id, hashByteSlice) } -export function validateByteSlice(v: unknown): ValidationResult { - return lexicons.validate('app.bsky.richtext.facet#byteSlice', v) +export function validateByteSlice(v: V) { + return validate(v, id, hashByteSlice) } diff --git a/packages/api/src/client/types/app/bsky/unspecced/defs.ts b/packages/api/src/client/types/app/bsky/unspecced/defs.ts index 81f821ecf73..d07ac5faa8a 100644 --- a/packages/api/src/client/types/app/bsky/unspecced/defs.ts +++ b/packages/api/src/client/types/app/bsky/unspecced/defs.ts @@ -2,84 +2,77 @@ * GENERATED CODE - DO NOT MODIFY */ import { ValidationResult, BlobRef } from '@atproto/lexicon' -import { isObj, hasProp } from '../../../../util' -import { lexicons } from '../../../../lexicons' import { CID } from 'multiformats/cid' +import { validate as _validate } from '../../../../lexicons' +import { $Type, $Typed, is$typed as _is$typed, OmitKey } from '../../../../util' + +const is$typed = _is$typed, + validate = _validate +const id = 'app.bsky.unspecced.defs' export interface SkeletonSearchPost { + $type?: $Type<'app.bsky.unspecced.defs', 'skeletonSearchPost'> uri: string - [k: string]: unknown } -export function isSkeletonSearchPost(v: unknown): v is SkeletonSearchPost { - return ( - isObj(v) && - hasProp(v, '$type') && - v.$type === 'app.bsky.unspecced.defs#skeletonSearchPost' - ) +const hashSkeletonSearchPost = 'skeletonSearchPost' + +export function isSkeletonSearchPost(v: V) { + return is$typed(v, id, hashSkeletonSearchPost) } -export function validateSkeletonSearchPost(v: unknown): ValidationResult { - return lexicons.validate('app.bsky.unspecced.defs#skeletonSearchPost', v) +export function validateSkeletonSearchPost(v: V) { + return validate(v, id, hashSkeletonSearchPost) } export interface SkeletonSearchActor { + $type?: $Type<'app.bsky.unspecced.defs', 'skeletonSearchActor'> did: string - [k: string]: unknown } -export function isSkeletonSearchActor(v: unknown): v is SkeletonSearchActor { - return ( - isObj(v) && - hasProp(v, '$type') && - v.$type === 'app.bsky.unspecced.defs#skeletonSearchActor' - ) +const hashSkeletonSearchActor = 'skeletonSearchActor' + +export function isSkeletonSearchActor(v: V) { + return is$typed(v, id, hashSkeletonSearchActor) } -export function validateSkeletonSearchActor(v: unknown): ValidationResult { - return lexicons.validate('app.bsky.unspecced.defs#skeletonSearchActor', v) +export function validateSkeletonSearchActor(v: V) { + return validate(v, id, hashSkeletonSearchActor) } export interface SkeletonSearchStarterPack { + $type?: $Type<'app.bsky.unspecced.defs', 'skeletonSearchStarterPack'> uri: string - [k: string]: unknown } -export function isSkeletonSearchStarterPack( - v: unknown, -): v is SkeletonSearchStarterPack { - return ( - isObj(v) && - hasProp(v, '$type') && - v.$type === 'app.bsky.unspecced.defs#skeletonSearchStarterPack' - ) +const hashSkeletonSearchStarterPack = 'skeletonSearchStarterPack' + +export function isSkeletonSearchStarterPack(v: V) { + return is$typed(v, id, hashSkeletonSearchStarterPack) } -export function validateSkeletonSearchStarterPack( - v: unknown, -): ValidationResult { - return lexicons.validate( - 'app.bsky.unspecced.defs#skeletonSearchStarterPack', +export function validateSkeletonSearchStarterPack(v: V) { + return validate( v, + id, + hashSkeletonSearchStarterPack, ) } export interface TrendingTopic { + $type?: $Type<'app.bsky.unspecced.defs', 'trendingTopic'> topic: string displayName?: string description?: string link: string - [k: string]: unknown } -export function isTrendingTopic(v: unknown): v is TrendingTopic { - return ( - isObj(v) && - hasProp(v, '$type') && - v.$type === 'app.bsky.unspecced.defs#trendingTopic' - ) +const hashTrendingTopic = 'trendingTopic' + +export function isTrendingTopic(v: V) { + return is$typed(v, id, hashTrendingTopic) } -export function validateTrendingTopic(v: unknown): ValidationResult { - return lexicons.validate('app.bsky.unspecced.defs#trendingTopic', v) +export function validateTrendingTopic(v: V) { + return validate(v, id, hashTrendingTopic) } diff --git a/packages/api/src/client/types/app/bsky/unspecced/getConfig.ts b/packages/api/src/client/types/app/bsky/unspecced/getConfig.ts index d1ee6f7957d..a6639870446 100644 --- a/packages/api/src/client/types/app/bsky/unspecced/getConfig.ts +++ b/packages/api/src/client/types/app/bsky/unspecced/getConfig.ts @@ -3,9 +3,13 @@ */ import { HeadersMap, XRPCError } from '@atproto/xrpc' import { ValidationResult, BlobRef } from '@atproto/lexicon' -import { isObj, hasProp } from '../../../../util' -import { lexicons } from '../../../../lexicons' import { CID } from 'multiformats/cid' +import { validate as _validate } from '../../../../lexicons' +import { $Type, $Typed, is$typed as _is$typed, OmitKey } from '../../../../util' + +const is$typed = _is$typed, + validate = _validate +const id = 'app.bsky.unspecced.getConfig' export interface QueryParams {} @@ -13,7 +17,6 @@ export type InputSchema = undefined export interface OutputSchema { checkEmailConfirmed?: boolean - [k: string]: unknown } export interface CallOptions { diff --git a/packages/api/src/client/types/app/bsky/unspecced/getPopularFeedGenerators.ts b/packages/api/src/client/types/app/bsky/unspecced/getPopularFeedGenerators.ts index 780b4b6641c..ea25ba9b1eb 100644 --- a/packages/api/src/client/types/app/bsky/unspecced/getPopularFeedGenerators.ts +++ b/packages/api/src/client/types/app/bsky/unspecced/getPopularFeedGenerators.ts @@ -3,10 +3,14 @@ */ import { HeadersMap, XRPCError } from '@atproto/xrpc' import { ValidationResult, BlobRef } from '@atproto/lexicon' -import { isObj, hasProp } from '../../../../util' -import { lexicons } from '../../../../lexicons' import { CID } from 'multiformats/cid' -import * as AppBskyFeedDefs from '../feed/defs' +import { validate as _validate } from '../../../../lexicons' +import { $Type, $Typed, is$typed as _is$typed, OmitKey } from '../../../../util' +import type * as AppBskyFeedDefs from '../feed/defs' + +const is$typed = _is$typed, + validate = _validate +const id = 'app.bsky.unspecced.getPopularFeedGenerators' export interface QueryParams { limit?: number @@ -19,7 +23,6 @@ export type InputSchema = undefined export interface OutputSchema { cursor?: string feeds: AppBskyFeedDefs.GeneratorView[] - [k: string]: unknown } export interface CallOptions { diff --git a/packages/api/src/client/types/app/bsky/unspecced/getSuggestionsSkeleton.ts b/packages/api/src/client/types/app/bsky/unspecced/getSuggestionsSkeleton.ts index 0759165f1d0..a6758ce4f35 100644 --- a/packages/api/src/client/types/app/bsky/unspecced/getSuggestionsSkeleton.ts +++ b/packages/api/src/client/types/app/bsky/unspecced/getSuggestionsSkeleton.ts @@ -3,10 +3,14 @@ */ import { HeadersMap, XRPCError } from '@atproto/xrpc' import { ValidationResult, BlobRef } from '@atproto/lexicon' -import { isObj, hasProp } from '../../../../util' -import { lexicons } from '../../../../lexicons' import { CID } from 'multiformats/cid' -import * as AppBskyUnspeccedDefs from './defs' +import { validate as _validate } from '../../../../lexicons' +import { $Type, $Typed, is$typed as _is$typed, OmitKey } from '../../../../util' +import type * as AppBskyUnspeccedDefs from './defs' + +const is$typed = _is$typed, + validate = _validate +const id = 'app.bsky.unspecced.getSuggestionsSkeleton' export interface QueryParams { /** DID of the account making the request (not included for public/unauthenticated queries). Used to boost followed accounts in ranking. */ @@ -26,7 +30,6 @@ export interface OutputSchema { relativeToDid?: string /** Snowflake for this recommendation, use when submitting recommendation events. */ recId?: number - [k: string]: unknown } export interface CallOptions { diff --git a/packages/api/src/client/types/app/bsky/unspecced/getTaggedSuggestions.ts b/packages/api/src/client/types/app/bsky/unspecced/getTaggedSuggestions.ts index 0ee04b8e85d..b55c8f21f1d 100644 --- a/packages/api/src/client/types/app/bsky/unspecced/getTaggedSuggestions.ts +++ b/packages/api/src/client/types/app/bsky/unspecced/getTaggedSuggestions.ts @@ -3,9 +3,13 @@ */ import { HeadersMap, XRPCError } from '@atproto/xrpc' import { ValidationResult, BlobRef } from '@atproto/lexicon' -import { isObj, hasProp } from '../../../../util' -import { lexicons } from '../../../../lexicons' import { CID } from 'multiformats/cid' +import { validate as _validate } from '../../../../lexicons' +import { $Type, $Typed, is$typed as _is$typed, OmitKey } from '../../../../util' + +const is$typed = _is$typed, + validate = _validate +const id = 'app.bsky.unspecced.getTaggedSuggestions' export interface QueryParams {} @@ -13,7 +17,6 @@ export type InputSchema = undefined export interface OutputSchema { suggestions: Suggestion[] - [k: string]: unknown } export interface CallOptions { @@ -32,23 +35,18 @@ export function toKnownErr(e: any) { } export interface Suggestion { + $type?: $Type<'app.bsky.unspecced.getTaggedSuggestions', 'suggestion'> tag: string subjectType: 'actor' | 'feed' | (string & {}) subject: string - [k: string]: unknown } -export function isSuggestion(v: unknown): v is Suggestion { - return ( - isObj(v) && - hasProp(v, '$type') && - v.$type === 'app.bsky.unspecced.getTaggedSuggestions#suggestion' - ) +const hashSuggestion = 'suggestion' + +export function isSuggestion(v: V) { + return is$typed(v, id, hashSuggestion) } -export function validateSuggestion(v: unknown): ValidationResult { - return lexicons.validate( - 'app.bsky.unspecced.getTaggedSuggestions#suggestion', - v, - ) +export function validateSuggestion(v: V) { + return validate(v, id, hashSuggestion) } diff --git a/packages/api/src/client/types/app/bsky/unspecced/getTrendingTopics.ts b/packages/api/src/client/types/app/bsky/unspecced/getTrendingTopics.ts index 70e790e14ba..78b5384ff1e 100644 --- a/packages/api/src/client/types/app/bsky/unspecced/getTrendingTopics.ts +++ b/packages/api/src/client/types/app/bsky/unspecced/getTrendingTopics.ts @@ -3,10 +3,14 @@ */ import { HeadersMap, XRPCError } from '@atproto/xrpc' import { ValidationResult, BlobRef } from '@atproto/lexicon' -import { isObj, hasProp } from '../../../../util' -import { lexicons } from '../../../../lexicons' import { CID } from 'multiformats/cid' -import * as AppBskyUnspeccedDefs from './defs' +import { validate as _validate } from '../../../../lexicons' +import { $Type, $Typed, is$typed as _is$typed, OmitKey } from '../../../../util' +import type * as AppBskyUnspeccedDefs from './defs' + +const is$typed = _is$typed, + validate = _validate +const id = 'app.bsky.unspecced.getTrendingTopics' export interface QueryParams { /** DID of the account making the request (not included for public/unauthenticated queries). Used to boost followed accounts in ranking. */ @@ -19,7 +23,6 @@ export type InputSchema = undefined export interface OutputSchema { topics: AppBskyUnspeccedDefs.TrendingTopic[] suggested: AppBskyUnspeccedDefs.TrendingTopic[] - [k: string]: unknown } export interface CallOptions { diff --git a/packages/api/src/client/types/app/bsky/unspecced/searchActorsSkeleton.ts b/packages/api/src/client/types/app/bsky/unspecced/searchActorsSkeleton.ts index d9fe6737d37..bbb1e6102d2 100644 --- a/packages/api/src/client/types/app/bsky/unspecced/searchActorsSkeleton.ts +++ b/packages/api/src/client/types/app/bsky/unspecced/searchActorsSkeleton.ts @@ -3,10 +3,14 @@ */ import { HeadersMap, XRPCError } from '@atproto/xrpc' import { ValidationResult, BlobRef } from '@atproto/lexicon' -import { isObj, hasProp } from '../../../../util' -import { lexicons } from '../../../../lexicons' import { CID } from 'multiformats/cid' -import * as AppBskyUnspeccedDefs from './defs' +import { validate as _validate } from '../../../../lexicons' +import { $Type, $Typed, is$typed as _is$typed, OmitKey } from '../../../../util' +import type * as AppBskyUnspeccedDefs from './defs' + +const is$typed = _is$typed, + validate = _validate +const id = 'app.bsky.unspecced.searchActorsSkeleton' export interface QueryParams { /** Search query string; syntax, phrase, boolean, and faceting is unspecified, but Lucene query syntax is recommended. For typeahead search, only simple term match is supported, not full syntax. */ @@ -27,7 +31,6 @@ export interface OutputSchema { /** Count of search hits. Optional, may be rounded/truncated, and may not be possible to paginate through all hits. */ hitsTotal?: number actors: AppBskyUnspeccedDefs.SkeletonSearchActor[] - [k: string]: unknown } export interface CallOptions { diff --git a/packages/api/src/client/types/app/bsky/unspecced/searchPostsSkeleton.ts b/packages/api/src/client/types/app/bsky/unspecced/searchPostsSkeleton.ts index ea0135dde83..7c0a81b8170 100644 --- a/packages/api/src/client/types/app/bsky/unspecced/searchPostsSkeleton.ts +++ b/packages/api/src/client/types/app/bsky/unspecced/searchPostsSkeleton.ts @@ -3,10 +3,14 @@ */ import { HeadersMap, XRPCError } from '@atproto/xrpc' import { ValidationResult, BlobRef } from '@atproto/lexicon' -import { isObj, hasProp } from '../../../../util' -import { lexicons } from '../../../../lexicons' import { CID } from 'multiformats/cid' -import * as AppBskyUnspeccedDefs from './defs' +import { validate as _validate } from '../../../../lexicons' +import { $Type, $Typed, is$typed as _is$typed, OmitKey } from '../../../../util' +import type * as AppBskyUnspeccedDefs from './defs' + +const is$typed = _is$typed, + validate = _validate +const id = 'app.bsky.unspecced.searchPostsSkeleton' export interface QueryParams { /** Search query string; syntax, phrase, boolean, and faceting is unspecified, but Lucene query syntax is recommended. */ @@ -43,7 +47,6 @@ export interface OutputSchema { /** Count of search hits. Optional, may be rounded/truncated, and may not be possible to paginate through all hits. */ hitsTotal?: number posts: AppBskyUnspeccedDefs.SkeletonSearchPost[] - [k: string]: unknown } export interface CallOptions { diff --git a/packages/api/src/client/types/app/bsky/unspecced/searchStarterPacksSkeleton.ts b/packages/api/src/client/types/app/bsky/unspecced/searchStarterPacksSkeleton.ts index c331b86d3e1..efc08eb4758 100644 --- a/packages/api/src/client/types/app/bsky/unspecced/searchStarterPacksSkeleton.ts +++ b/packages/api/src/client/types/app/bsky/unspecced/searchStarterPacksSkeleton.ts @@ -3,10 +3,14 @@ */ import { HeadersMap, XRPCError } from '@atproto/xrpc' import { ValidationResult, BlobRef } from '@atproto/lexicon' -import { isObj, hasProp } from '../../../../util' -import { lexicons } from '../../../../lexicons' import { CID } from 'multiformats/cid' -import * as AppBskyUnspeccedDefs from './defs' +import { validate as _validate } from '../../../../lexicons' +import { $Type, $Typed, is$typed as _is$typed, OmitKey } from '../../../../util' +import type * as AppBskyUnspeccedDefs from './defs' + +const is$typed = _is$typed, + validate = _validate +const id = 'app.bsky.unspecced.searchStarterPacksSkeleton' export interface QueryParams { /** Search query string; syntax, phrase, boolean, and faceting is unspecified, but Lucene query syntax is recommended. */ @@ -25,7 +29,6 @@ export interface OutputSchema { /** Count of search hits. Optional, may be rounded/truncated, and may not be possible to paginate through all hits. */ hitsTotal?: number starterPacks: AppBskyUnspeccedDefs.SkeletonSearchStarterPack[] - [k: string]: unknown } export interface CallOptions { diff --git a/packages/api/src/client/types/app/bsky/video/defs.ts b/packages/api/src/client/types/app/bsky/video/defs.ts index a7ec84316b6..618d9e0be5b 100644 --- a/packages/api/src/client/types/app/bsky/video/defs.ts +++ b/packages/api/src/client/types/app/bsky/video/defs.ts @@ -2,11 +2,16 @@ * GENERATED CODE - DO NOT MODIFY */ import { ValidationResult, BlobRef } from '@atproto/lexicon' -import { isObj, hasProp } from '../../../../util' -import { lexicons } from '../../../../lexicons' import { CID } from 'multiformats/cid' +import { validate as _validate } from '../../../../lexicons' +import { $Type, $Typed, is$typed as _is$typed, OmitKey } from '../../../../util' + +const is$typed = _is$typed, + validate = _validate +const id = 'app.bsky.video.defs' export interface JobStatus { + $type?: $Type<'app.bsky.video.defs', 'jobStatus'> jobId: string did: string /** The state of the video processing job. All values not listed as a known value indicate that the job is in process. */ @@ -16,17 +21,14 @@ export interface JobStatus { blob?: BlobRef error?: string message?: string - [k: string]: unknown } -export function isJobStatus(v: unknown): v is JobStatus { - return ( - isObj(v) && - hasProp(v, '$type') && - v.$type === 'app.bsky.video.defs#jobStatus' - ) +const hashJobStatus = 'jobStatus' + +export function isJobStatus(v: V) { + return is$typed(v, id, hashJobStatus) } -export function validateJobStatus(v: unknown): ValidationResult { - return lexicons.validate('app.bsky.video.defs#jobStatus', v) +export function validateJobStatus(v: V) { + return validate(v, id, hashJobStatus) } diff --git a/packages/api/src/client/types/app/bsky/video/getJobStatus.ts b/packages/api/src/client/types/app/bsky/video/getJobStatus.ts index 0e9638311c0..378c0472048 100644 --- a/packages/api/src/client/types/app/bsky/video/getJobStatus.ts +++ b/packages/api/src/client/types/app/bsky/video/getJobStatus.ts @@ -3,10 +3,14 @@ */ import { HeadersMap, XRPCError } from '@atproto/xrpc' import { ValidationResult, BlobRef } from '@atproto/lexicon' -import { isObj, hasProp } from '../../../../util' -import { lexicons } from '../../../../lexicons' import { CID } from 'multiformats/cid' -import * as AppBskyVideoDefs from './defs' +import { validate as _validate } from '../../../../lexicons' +import { $Type, $Typed, is$typed as _is$typed, OmitKey } from '../../../../util' +import type * as AppBskyVideoDefs from './defs' + +const is$typed = _is$typed, + validate = _validate +const id = 'app.bsky.video.getJobStatus' export interface QueryParams { jobId: string @@ -16,7 +20,6 @@ export type InputSchema = undefined export interface OutputSchema { jobStatus: AppBskyVideoDefs.JobStatus - [k: string]: unknown } export interface CallOptions { diff --git a/packages/api/src/client/types/app/bsky/video/getUploadLimits.ts b/packages/api/src/client/types/app/bsky/video/getUploadLimits.ts index 4a2f13617b3..b1e3416550c 100644 --- a/packages/api/src/client/types/app/bsky/video/getUploadLimits.ts +++ b/packages/api/src/client/types/app/bsky/video/getUploadLimits.ts @@ -3,9 +3,13 @@ */ import { HeadersMap, XRPCError } from '@atproto/xrpc' import { ValidationResult, BlobRef } from '@atproto/lexicon' -import { isObj, hasProp } from '../../../../util' -import { lexicons } from '../../../../lexicons' import { CID } from 'multiformats/cid' +import { validate as _validate } from '../../../../lexicons' +import { $Type, $Typed, is$typed as _is$typed, OmitKey } from '../../../../util' + +const is$typed = _is$typed, + validate = _validate +const id = 'app.bsky.video.getUploadLimits' export interface QueryParams {} @@ -17,7 +21,6 @@ export interface OutputSchema { remainingDailyBytes?: number message?: string error?: string - [k: string]: unknown } export interface CallOptions { diff --git a/packages/api/src/client/types/app/bsky/video/uploadVideo.ts b/packages/api/src/client/types/app/bsky/video/uploadVideo.ts index f51ba897bbe..850739ff211 100644 --- a/packages/api/src/client/types/app/bsky/video/uploadVideo.ts +++ b/packages/api/src/client/types/app/bsky/video/uploadVideo.ts @@ -3,10 +3,14 @@ */ import { HeadersMap, XRPCError } from '@atproto/xrpc' import { ValidationResult, BlobRef } from '@atproto/lexicon' -import { isObj, hasProp } from '../../../../util' -import { lexicons } from '../../../../lexicons' import { CID } from 'multiformats/cid' -import * as AppBskyVideoDefs from './defs' +import { validate as _validate } from '../../../../lexicons' +import { $Type, $Typed, is$typed as _is$typed, OmitKey } from '../../../../util' +import type * as AppBskyVideoDefs from './defs' + +const is$typed = _is$typed, + validate = _validate +const id = 'app.bsky.video.uploadVideo' export interface QueryParams {} @@ -14,7 +18,6 @@ export type InputSchema = string | Uint8Array | Blob export interface OutputSchema { jobStatus: AppBskyVideoDefs.JobStatus - [k: string]: unknown } export interface CallOptions { diff --git a/packages/api/src/client/types/chat/bsky/actor/declaration.ts b/packages/api/src/client/types/chat/bsky/actor/declaration.ts index 99b14f45608..cff4487b71b 100644 --- a/packages/api/src/client/types/chat/bsky/actor/declaration.ts +++ b/packages/api/src/client/types/chat/bsky/actor/declaration.ts @@ -2,24 +2,26 @@ * GENERATED CODE - DO NOT MODIFY */ import { ValidationResult, BlobRef } from '@atproto/lexicon' -import { isObj, hasProp } from '../../../../util' -import { lexicons } from '../../../../lexicons' import { CID } from 'multiformats/cid' +import { validate as _validate } from '../../../../lexicons' +import { $Type, $Typed, is$typed as _is$typed, OmitKey } from '../../../../util' + +const is$typed = _is$typed, + validate = _validate +const id = 'chat.bsky.actor.declaration' export interface Record { + $type: $Type<'chat.bsky.actor.declaration', 'main'> allowIncoming: 'all' | 'none' | 'following' | (string & {}) [k: string]: unknown } -export function isRecord(v: unknown): v is Record { - return ( - isObj(v) && - hasProp(v, '$type') && - (v.$type === 'chat.bsky.actor.declaration#main' || - v.$type === 'chat.bsky.actor.declaration') - ) +const hashRecord = 'main' + +export function isRecord(v: V) { + return is$typed(v, id, hashRecord) } -export function validateRecord(v: unknown): ValidationResult { - return lexicons.validate('chat.bsky.actor.declaration#main', v) +export function validateRecord(v: V) { + return validate(v, id, hashRecord, true) } diff --git a/packages/api/src/client/types/chat/bsky/actor/defs.ts b/packages/api/src/client/types/chat/bsky/actor/defs.ts index 87aaac96218..7b62c197291 100644 --- a/packages/api/src/client/types/chat/bsky/actor/defs.ts +++ b/packages/api/src/client/types/chat/bsky/actor/defs.ts @@ -2,13 +2,18 @@ * GENERATED CODE - DO NOT MODIFY */ import { ValidationResult, BlobRef } from '@atproto/lexicon' -import { isObj, hasProp } from '../../../../util' -import { lexicons } from '../../../../lexicons' import { CID } from 'multiformats/cid' -import * as AppBskyActorDefs from '../../../app/bsky/actor/defs' -import * as ComAtprotoLabelDefs from '../../../com/atproto/label/defs' +import { validate as _validate } from '../../../../lexicons' +import { $Type, $Typed, is$typed as _is$typed, OmitKey } from '../../../../util' +import type * as AppBskyActorDefs from '../../../app/bsky/actor/defs' +import type * as ComAtprotoLabelDefs from '../../../com/atproto/label/defs' + +const is$typed = _is$typed, + validate = _validate +const id = 'chat.bsky.actor.defs' export interface ProfileViewBasic { + $type?: $Type<'chat.bsky.actor.defs', 'profileViewBasic'> did: string handle: string displayName?: string @@ -18,17 +23,14 @@ export interface ProfileViewBasic { labels?: ComAtprotoLabelDefs.Label[] /** Set to true when the actor cannot actively participate in converations */ chatDisabled?: boolean - [k: string]: unknown } -export function isProfileViewBasic(v: unknown): v is ProfileViewBasic { - return ( - isObj(v) && - hasProp(v, '$type') && - v.$type === 'chat.bsky.actor.defs#profileViewBasic' - ) +const hashProfileViewBasic = 'profileViewBasic' + +export function isProfileViewBasic(v: V) { + return is$typed(v, id, hashProfileViewBasic) } -export function validateProfileViewBasic(v: unknown): ValidationResult { - return lexicons.validate('chat.bsky.actor.defs#profileViewBasic', v) +export function validateProfileViewBasic(v: V) { + return validate(v, id, hashProfileViewBasic) } diff --git a/packages/api/src/client/types/chat/bsky/actor/deleteAccount.ts b/packages/api/src/client/types/chat/bsky/actor/deleteAccount.ts index 1a045c21c29..9fc6499085b 100644 --- a/packages/api/src/client/types/chat/bsky/actor/deleteAccount.ts +++ b/packages/api/src/client/types/chat/bsky/actor/deleteAccount.ts @@ -3,17 +3,19 @@ */ import { HeadersMap, XRPCError } from '@atproto/xrpc' import { ValidationResult, BlobRef } from '@atproto/lexicon' -import { isObj, hasProp } from '../../../../util' -import { lexicons } from '../../../../lexicons' import { CID } from 'multiformats/cid' +import { validate as _validate } from '../../../../lexicons' +import { $Type, $Typed, is$typed as _is$typed, OmitKey } from '../../../../util' + +const is$typed = _is$typed, + validate = _validate +const id = 'chat.bsky.actor.deleteAccount' export interface QueryParams {} export type InputSchema = undefined -export interface OutputSchema { - [k: string]: unknown -} +export interface OutputSchema {} export interface CallOptions { signal?: AbortSignal diff --git a/packages/api/src/client/types/chat/bsky/actor/exportAccountData.ts b/packages/api/src/client/types/chat/bsky/actor/exportAccountData.ts index 0142436b05e..2e6886adf3c 100644 --- a/packages/api/src/client/types/chat/bsky/actor/exportAccountData.ts +++ b/packages/api/src/client/types/chat/bsky/actor/exportAccountData.ts @@ -3,9 +3,13 @@ */ import { HeadersMap, XRPCError } from '@atproto/xrpc' import { ValidationResult, BlobRef } from '@atproto/lexicon' -import { isObj, hasProp } from '../../../../util' -import { lexicons } from '../../../../lexicons' import { CID } from 'multiformats/cid' +import { validate as _validate } from '../../../../lexicons' +import { $Type, $Typed, is$typed as _is$typed, OmitKey } from '../../../../util' + +const is$typed = _is$typed, + validate = _validate +const id = 'chat.bsky.actor.exportAccountData' export interface QueryParams {} diff --git a/packages/api/src/client/types/chat/bsky/convo/defs.ts b/packages/api/src/client/types/chat/bsky/convo/defs.ts index 4908b2dc46a..01a3f1d290b 100644 --- a/packages/api/src/client/types/chat/bsky/convo/defs.ts +++ b/packages/api/src/client/types/chat/bsky/convo/defs.ts @@ -2,215 +2,193 @@ * GENERATED CODE - DO NOT MODIFY */ import { ValidationResult, BlobRef } from '@atproto/lexicon' -import { isObj, hasProp } from '../../../../util' -import { lexicons } from '../../../../lexicons' import { CID } from 'multiformats/cid' -import * as AppBskyRichtextFacet from '../../../app/bsky/richtext/facet' -import * as AppBskyEmbedRecord from '../../../app/bsky/embed/record' -import * as ChatBskyActorDefs from '../actor/defs' +import { validate as _validate } from '../../../../lexicons' +import { $Type, $Typed, is$typed as _is$typed, OmitKey } from '../../../../util' +import type * as AppBskyRichtextFacet from '../../../app/bsky/richtext/facet' +import type * as AppBskyEmbedRecord from '../../../app/bsky/embed/record' +import type * as ChatBskyActorDefs from '../actor/defs' + +const is$typed = _is$typed, + validate = _validate +const id = 'chat.bsky.convo.defs' export interface MessageRef { + $type?: $Type<'chat.bsky.convo.defs', 'messageRef'> did: string convoId: string messageId: string - [k: string]: unknown } -export function isMessageRef(v: unknown): v is MessageRef { - return ( - isObj(v) && - hasProp(v, '$type') && - v.$type === 'chat.bsky.convo.defs#messageRef' - ) +const hashMessageRef = 'messageRef' + +export function isMessageRef(v: V) { + return is$typed(v, id, hashMessageRef) } -export function validateMessageRef(v: unknown): ValidationResult { - return lexicons.validate('chat.bsky.convo.defs#messageRef', v) +export function validateMessageRef(v: V) { + return validate(v, id, hashMessageRef) } export interface MessageInput { + $type?: $Type<'chat.bsky.convo.defs', 'messageInput'> text: string /** Annotations of text (mentions, URLs, hashtags, etc) */ facets?: AppBskyRichtextFacet.Main[] - embed?: AppBskyEmbedRecord.Main | { $type: string; [k: string]: unknown } - [k: string]: unknown + embed?: $Typed | { $type: string } } -export function isMessageInput(v: unknown): v is MessageInput { - return ( - isObj(v) && - hasProp(v, '$type') && - v.$type === 'chat.bsky.convo.defs#messageInput' - ) +const hashMessageInput = 'messageInput' + +export function isMessageInput(v: V) { + return is$typed(v, id, hashMessageInput) } -export function validateMessageInput(v: unknown): ValidationResult { - return lexicons.validate('chat.bsky.convo.defs#messageInput', v) +export function validateMessageInput(v: V) { + return validate(v, id, hashMessageInput) } export interface MessageView { + $type?: $Type<'chat.bsky.convo.defs', 'messageView'> id: string rev: string text: string /** Annotations of text (mentions, URLs, hashtags, etc) */ facets?: AppBskyRichtextFacet.Main[] - embed?: AppBskyEmbedRecord.View | { $type: string; [k: string]: unknown } + embed?: $Typed | { $type: string } sender: MessageViewSender sentAt: string - [k: string]: unknown } -export function isMessageView(v: unknown): v is MessageView { - return ( - isObj(v) && - hasProp(v, '$type') && - v.$type === 'chat.bsky.convo.defs#messageView' - ) +const hashMessageView = 'messageView' + +export function isMessageView(v: V) { + return is$typed(v, id, hashMessageView) } -export function validateMessageView(v: unknown): ValidationResult { - return lexicons.validate('chat.bsky.convo.defs#messageView', v) +export function validateMessageView(v: V) { + return validate(v, id, hashMessageView) } export interface DeletedMessageView { + $type?: $Type<'chat.bsky.convo.defs', 'deletedMessageView'> id: string rev: string sender: MessageViewSender sentAt: string - [k: string]: unknown } -export function isDeletedMessageView(v: unknown): v is DeletedMessageView { - return ( - isObj(v) && - hasProp(v, '$type') && - v.$type === 'chat.bsky.convo.defs#deletedMessageView' - ) +const hashDeletedMessageView = 'deletedMessageView' + +export function isDeletedMessageView(v: V) { + return is$typed(v, id, hashDeletedMessageView) } -export function validateDeletedMessageView(v: unknown): ValidationResult { - return lexicons.validate('chat.bsky.convo.defs#deletedMessageView', v) +export function validateDeletedMessageView(v: V) { + return validate(v, id, hashDeletedMessageView) } export interface MessageViewSender { + $type?: $Type<'chat.bsky.convo.defs', 'messageViewSender'> did: string - [k: string]: unknown } -export function isMessageViewSender(v: unknown): v is MessageViewSender { - return ( - isObj(v) && - hasProp(v, '$type') && - v.$type === 'chat.bsky.convo.defs#messageViewSender' - ) +const hashMessageViewSender = 'messageViewSender' + +export function isMessageViewSender(v: V) { + return is$typed(v, id, hashMessageViewSender) } -export function validateMessageViewSender(v: unknown): ValidationResult { - return lexicons.validate('chat.bsky.convo.defs#messageViewSender', v) +export function validateMessageViewSender(v: V) { + return validate(v, id, hashMessageViewSender) } export interface ConvoView { + $type?: $Type<'chat.bsky.convo.defs', 'convoView'> id: string rev: string members: ChatBskyActorDefs.ProfileViewBasic[] lastMessage?: - | MessageView - | DeletedMessageView - | { $type: string; [k: string]: unknown } + | $Typed + | $Typed + | { $type: string } muted: boolean opened?: boolean unreadCount: number - [k: string]: unknown } -export function isConvoView(v: unknown): v is ConvoView { - return ( - isObj(v) && - hasProp(v, '$type') && - v.$type === 'chat.bsky.convo.defs#convoView' - ) +const hashConvoView = 'convoView' + +export function isConvoView(v: V) { + return is$typed(v, id, hashConvoView) } -export function validateConvoView(v: unknown): ValidationResult { - return lexicons.validate('chat.bsky.convo.defs#convoView', v) +export function validateConvoView(v: V) { + return validate(v, id, hashConvoView) } export interface LogBeginConvo { + $type?: $Type<'chat.bsky.convo.defs', 'logBeginConvo'> rev: string convoId: string - [k: string]: unknown } -export function isLogBeginConvo(v: unknown): v is LogBeginConvo { - return ( - isObj(v) && - hasProp(v, '$type') && - v.$type === 'chat.bsky.convo.defs#logBeginConvo' - ) +const hashLogBeginConvo = 'logBeginConvo' + +export function isLogBeginConvo(v: V) { + return is$typed(v, id, hashLogBeginConvo) } -export function validateLogBeginConvo(v: unknown): ValidationResult { - return lexicons.validate('chat.bsky.convo.defs#logBeginConvo', v) +export function validateLogBeginConvo(v: V) { + return validate(v, id, hashLogBeginConvo) } export interface LogLeaveConvo { + $type?: $Type<'chat.bsky.convo.defs', 'logLeaveConvo'> rev: string convoId: string - [k: string]: unknown } -export function isLogLeaveConvo(v: unknown): v is LogLeaveConvo { - return ( - isObj(v) && - hasProp(v, '$type') && - v.$type === 'chat.bsky.convo.defs#logLeaveConvo' - ) +const hashLogLeaveConvo = 'logLeaveConvo' + +export function isLogLeaveConvo(v: V) { + return is$typed(v, id, hashLogLeaveConvo) } -export function validateLogLeaveConvo(v: unknown): ValidationResult { - return lexicons.validate('chat.bsky.convo.defs#logLeaveConvo', v) +export function validateLogLeaveConvo(v: V) { + return validate(v, id, hashLogLeaveConvo) } export interface LogCreateMessage { + $type?: $Type<'chat.bsky.convo.defs', 'logCreateMessage'> rev: string convoId: string - message: - | MessageView - | DeletedMessageView - | { $type: string; [k: string]: unknown } - [k: string]: unknown + message: $Typed | $Typed | { $type: string } } -export function isLogCreateMessage(v: unknown): v is LogCreateMessage { - return ( - isObj(v) && - hasProp(v, '$type') && - v.$type === 'chat.bsky.convo.defs#logCreateMessage' - ) +const hashLogCreateMessage = 'logCreateMessage' + +export function isLogCreateMessage(v: V) { + return is$typed(v, id, hashLogCreateMessage) } -export function validateLogCreateMessage(v: unknown): ValidationResult { - return lexicons.validate('chat.bsky.convo.defs#logCreateMessage', v) +export function validateLogCreateMessage(v: V) { + return validate(v, id, hashLogCreateMessage) } export interface LogDeleteMessage { + $type?: $Type<'chat.bsky.convo.defs', 'logDeleteMessage'> rev: string convoId: string - message: - | MessageView - | DeletedMessageView - | { $type: string; [k: string]: unknown } - [k: string]: unknown + message: $Typed | $Typed | { $type: string } } -export function isLogDeleteMessage(v: unknown): v is LogDeleteMessage { - return ( - isObj(v) && - hasProp(v, '$type') && - v.$type === 'chat.bsky.convo.defs#logDeleteMessage' - ) +const hashLogDeleteMessage = 'logDeleteMessage' + +export function isLogDeleteMessage(v: V) { + return is$typed(v, id, hashLogDeleteMessage) } -export function validateLogDeleteMessage(v: unknown): ValidationResult { - return lexicons.validate('chat.bsky.convo.defs#logDeleteMessage', v) +export function validateLogDeleteMessage(v: V) { + return validate(v, id, hashLogDeleteMessage) } diff --git a/packages/api/src/client/types/chat/bsky/convo/deleteMessageForSelf.ts b/packages/api/src/client/types/chat/bsky/convo/deleteMessageForSelf.ts index 863d2208cb8..d1ab66721f7 100644 --- a/packages/api/src/client/types/chat/bsky/convo/deleteMessageForSelf.ts +++ b/packages/api/src/client/types/chat/bsky/convo/deleteMessageForSelf.ts @@ -3,17 +3,20 @@ */ import { HeadersMap, XRPCError } from '@atproto/xrpc' import { ValidationResult, BlobRef } from '@atproto/lexicon' -import { isObj, hasProp } from '../../../../util' -import { lexicons } from '../../../../lexicons' import { CID } from 'multiformats/cid' -import * as ChatBskyConvoDefs from './defs' +import { validate as _validate } from '../../../../lexicons' +import { $Type, $Typed, is$typed as _is$typed, OmitKey } from '../../../../util' +import type * as ChatBskyConvoDefs from './defs' + +const is$typed = _is$typed, + validate = _validate +const id = 'chat.bsky.convo.deleteMessageForSelf' export interface QueryParams {} export interface InputSchema { convoId: string messageId: string - [k: string]: unknown } export type OutputSchema = ChatBskyConvoDefs.DeletedMessageView diff --git a/packages/api/src/client/types/chat/bsky/convo/getConvo.ts b/packages/api/src/client/types/chat/bsky/convo/getConvo.ts index b3834234b5f..4dfe462e6c1 100644 --- a/packages/api/src/client/types/chat/bsky/convo/getConvo.ts +++ b/packages/api/src/client/types/chat/bsky/convo/getConvo.ts @@ -3,10 +3,14 @@ */ import { HeadersMap, XRPCError } from '@atproto/xrpc' import { ValidationResult, BlobRef } from '@atproto/lexicon' -import { isObj, hasProp } from '../../../../util' -import { lexicons } from '../../../../lexicons' import { CID } from 'multiformats/cid' -import * as ChatBskyConvoDefs from './defs' +import { validate as _validate } from '../../../../lexicons' +import { $Type, $Typed, is$typed as _is$typed, OmitKey } from '../../../../util' +import type * as ChatBskyConvoDefs from './defs' + +const is$typed = _is$typed, + validate = _validate +const id = 'chat.bsky.convo.getConvo' export interface QueryParams { convoId: string @@ -16,7 +20,6 @@ export type InputSchema = undefined export interface OutputSchema { convo: ChatBskyConvoDefs.ConvoView - [k: string]: unknown } export interface CallOptions { diff --git a/packages/api/src/client/types/chat/bsky/convo/getConvoForMembers.ts b/packages/api/src/client/types/chat/bsky/convo/getConvoForMembers.ts index 9db44887410..c9e1730c68e 100644 --- a/packages/api/src/client/types/chat/bsky/convo/getConvoForMembers.ts +++ b/packages/api/src/client/types/chat/bsky/convo/getConvoForMembers.ts @@ -3,10 +3,14 @@ */ import { HeadersMap, XRPCError } from '@atproto/xrpc' import { ValidationResult, BlobRef } from '@atproto/lexicon' -import { isObj, hasProp } from '../../../../util' -import { lexicons } from '../../../../lexicons' import { CID } from 'multiformats/cid' -import * as ChatBskyConvoDefs from './defs' +import { validate as _validate } from '../../../../lexicons' +import { $Type, $Typed, is$typed as _is$typed, OmitKey } from '../../../../util' +import type * as ChatBskyConvoDefs from './defs' + +const is$typed = _is$typed, + validate = _validate +const id = 'chat.bsky.convo.getConvoForMembers' export interface QueryParams { members: string[] @@ -16,7 +20,6 @@ export type InputSchema = undefined export interface OutputSchema { convo: ChatBskyConvoDefs.ConvoView - [k: string]: unknown } export interface CallOptions { diff --git a/packages/api/src/client/types/chat/bsky/convo/getLog.ts b/packages/api/src/client/types/chat/bsky/convo/getLog.ts index f1470fd89ba..c229a987945 100644 --- a/packages/api/src/client/types/chat/bsky/convo/getLog.ts +++ b/packages/api/src/client/types/chat/bsky/convo/getLog.ts @@ -3,10 +3,14 @@ */ import { HeadersMap, XRPCError } from '@atproto/xrpc' import { ValidationResult, BlobRef } from '@atproto/lexicon' -import { isObj, hasProp } from '../../../../util' -import { lexicons } from '../../../../lexicons' import { CID } from 'multiformats/cid' -import * as ChatBskyConvoDefs from './defs' +import { validate as _validate } from '../../../../lexicons' +import { $Type, $Typed, is$typed as _is$typed, OmitKey } from '../../../../util' +import type * as ChatBskyConvoDefs from './defs' + +const is$typed = _is$typed, + validate = _validate +const id = 'chat.bsky.convo.getLog' export interface QueryParams { cursor?: string @@ -17,13 +21,12 @@ export type InputSchema = undefined export interface OutputSchema { cursor?: string logs: ( - | ChatBskyConvoDefs.LogBeginConvo - | ChatBskyConvoDefs.LogLeaveConvo - | ChatBskyConvoDefs.LogCreateMessage - | ChatBskyConvoDefs.LogDeleteMessage - | { $type: string; [k: string]: unknown } + | $Typed + | $Typed + | $Typed + | $Typed + | { $type: string } )[] - [k: string]: unknown } export interface CallOptions { diff --git a/packages/api/src/client/types/chat/bsky/convo/getMessages.ts b/packages/api/src/client/types/chat/bsky/convo/getMessages.ts index eea7cba82e1..c889e8e324c 100644 --- a/packages/api/src/client/types/chat/bsky/convo/getMessages.ts +++ b/packages/api/src/client/types/chat/bsky/convo/getMessages.ts @@ -3,10 +3,14 @@ */ import { HeadersMap, XRPCError } from '@atproto/xrpc' import { ValidationResult, BlobRef } from '@atproto/lexicon' -import { isObj, hasProp } from '../../../../util' -import { lexicons } from '../../../../lexicons' import { CID } from 'multiformats/cid' -import * as ChatBskyConvoDefs from './defs' +import { validate as _validate } from '../../../../lexicons' +import { $Type, $Typed, is$typed as _is$typed, OmitKey } from '../../../../util' +import type * as ChatBskyConvoDefs from './defs' + +const is$typed = _is$typed, + validate = _validate +const id = 'chat.bsky.convo.getMessages' export interface QueryParams { convoId: string @@ -19,11 +23,10 @@ export type InputSchema = undefined export interface OutputSchema { cursor?: string messages: ( - | ChatBskyConvoDefs.MessageView - | ChatBskyConvoDefs.DeletedMessageView - | { $type: string; [k: string]: unknown } + | $Typed + | $Typed + | { $type: string } )[] - [k: string]: unknown } export interface CallOptions { diff --git a/packages/api/src/client/types/chat/bsky/convo/leaveConvo.ts b/packages/api/src/client/types/chat/bsky/convo/leaveConvo.ts index 4124db1855d..d4b6896f537 100644 --- a/packages/api/src/client/types/chat/bsky/convo/leaveConvo.ts +++ b/packages/api/src/client/types/chat/bsky/convo/leaveConvo.ts @@ -3,21 +3,23 @@ */ import { HeadersMap, XRPCError } from '@atproto/xrpc' import { ValidationResult, BlobRef } from '@atproto/lexicon' -import { isObj, hasProp } from '../../../../util' -import { lexicons } from '../../../../lexicons' import { CID } from 'multiformats/cid' +import { validate as _validate } from '../../../../lexicons' +import { $Type, $Typed, is$typed as _is$typed, OmitKey } from '../../../../util' + +const is$typed = _is$typed, + validate = _validate +const id = 'chat.bsky.convo.leaveConvo' export interface QueryParams {} export interface InputSchema { convoId: string - [k: string]: unknown } export interface OutputSchema { convoId: string rev: string - [k: string]: unknown } export interface CallOptions { diff --git a/packages/api/src/client/types/chat/bsky/convo/listConvos.ts b/packages/api/src/client/types/chat/bsky/convo/listConvos.ts index 3cd1ad68516..bba27cf6c22 100644 --- a/packages/api/src/client/types/chat/bsky/convo/listConvos.ts +++ b/packages/api/src/client/types/chat/bsky/convo/listConvos.ts @@ -3,10 +3,14 @@ */ import { HeadersMap, XRPCError } from '@atproto/xrpc' import { ValidationResult, BlobRef } from '@atproto/lexicon' -import { isObj, hasProp } from '../../../../util' -import { lexicons } from '../../../../lexicons' import { CID } from 'multiformats/cid' -import * as ChatBskyConvoDefs from './defs' +import { validate as _validate } from '../../../../lexicons' +import { $Type, $Typed, is$typed as _is$typed, OmitKey } from '../../../../util' +import type * as ChatBskyConvoDefs from './defs' + +const is$typed = _is$typed, + validate = _validate +const id = 'chat.bsky.convo.listConvos' export interface QueryParams { limit?: number @@ -18,7 +22,6 @@ export type InputSchema = undefined export interface OutputSchema { cursor?: string convos: ChatBskyConvoDefs.ConvoView[] - [k: string]: unknown } export interface CallOptions { diff --git a/packages/api/src/client/types/chat/bsky/convo/muteConvo.ts b/packages/api/src/client/types/chat/bsky/convo/muteConvo.ts index 93ac8785db7..a71bd91199e 100644 --- a/packages/api/src/client/types/chat/bsky/convo/muteConvo.ts +++ b/packages/api/src/client/types/chat/bsky/convo/muteConvo.ts @@ -3,21 +3,23 @@ */ import { HeadersMap, XRPCError } from '@atproto/xrpc' import { ValidationResult, BlobRef } from '@atproto/lexicon' -import { isObj, hasProp } from '../../../../util' -import { lexicons } from '../../../../lexicons' import { CID } from 'multiformats/cid' -import * as ChatBskyConvoDefs from './defs' +import { validate as _validate } from '../../../../lexicons' +import { $Type, $Typed, is$typed as _is$typed, OmitKey } from '../../../../util' +import type * as ChatBskyConvoDefs from './defs' + +const is$typed = _is$typed, + validate = _validate +const id = 'chat.bsky.convo.muteConvo' export interface QueryParams {} export interface InputSchema { convoId: string - [k: string]: unknown } export interface OutputSchema { convo: ChatBskyConvoDefs.ConvoView - [k: string]: unknown } export interface CallOptions { diff --git a/packages/api/src/client/types/chat/bsky/convo/sendMessage.ts b/packages/api/src/client/types/chat/bsky/convo/sendMessage.ts index e260ba6eee0..b4fe99a17eb 100644 --- a/packages/api/src/client/types/chat/bsky/convo/sendMessage.ts +++ b/packages/api/src/client/types/chat/bsky/convo/sendMessage.ts @@ -3,17 +3,20 @@ */ import { HeadersMap, XRPCError } from '@atproto/xrpc' import { ValidationResult, BlobRef } from '@atproto/lexicon' -import { isObj, hasProp } from '../../../../util' -import { lexicons } from '../../../../lexicons' import { CID } from 'multiformats/cid' -import * as ChatBskyConvoDefs from './defs' +import { validate as _validate } from '../../../../lexicons' +import { $Type, $Typed, is$typed as _is$typed, OmitKey } from '../../../../util' +import type * as ChatBskyConvoDefs from './defs' + +const is$typed = _is$typed, + validate = _validate +const id = 'chat.bsky.convo.sendMessage' export interface QueryParams {} export interface InputSchema { convoId: string message: ChatBskyConvoDefs.MessageInput - [k: string]: unknown } export type OutputSchema = ChatBskyConvoDefs.MessageView diff --git a/packages/api/src/client/types/chat/bsky/convo/sendMessageBatch.ts b/packages/api/src/client/types/chat/bsky/convo/sendMessageBatch.ts index 68ff2711df5..b8bde174519 100644 --- a/packages/api/src/client/types/chat/bsky/convo/sendMessageBatch.ts +++ b/packages/api/src/client/types/chat/bsky/convo/sendMessageBatch.ts @@ -3,21 +3,23 @@ */ import { HeadersMap, XRPCError } from '@atproto/xrpc' import { ValidationResult, BlobRef } from '@atproto/lexicon' -import { isObj, hasProp } from '../../../../util' -import { lexicons } from '../../../../lexicons' import { CID } from 'multiformats/cid' -import * as ChatBskyConvoDefs from './defs' +import { validate as _validate } from '../../../../lexicons' +import { $Type, $Typed, is$typed as _is$typed, OmitKey } from '../../../../util' +import type * as ChatBskyConvoDefs from './defs' + +const is$typed = _is$typed, + validate = _validate +const id = 'chat.bsky.convo.sendMessageBatch' export interface QueryParams {} export interface InputSchema { items: BatchItem[] - [k: string]: unknown } export interface OutputSchema { items: ChatBskyConvoDefs.MessageView[] - [k: string]: unknown } export interface CallOptions { @@ -38,19 +40,17 @@ export function toKnownErr(e: any) { } export interface BatchItem { + $type?: $Type<'chat.bsky.convo.sendMessageBatch', 'batchItem'> convoId: string message: ChatBskyConvoDefs.MessageInput - [k: string]: unknown } -export function isBatchItem(v: unknown): v is BatchItem { - return ( - isObj(v) && - hasProp(v, '$type') && - v.$type === 'chat.bsky.convo.sendMessageBatch#batchItem' - ) +const hashBatchItem = 'batchItem' + +export function isBatchItem(v: V) { + return is$typed(v, id, hashBatchItem) } -export function validateBatchItem(v: unknown): ValidationResult { - return lexicons.validate('chat.bsky.convo.sendMessageBatch#batchItem', v) +export function validateBatchItem(v: V) { + return validate(v, id, hashBatchItem) } diff --git a/packages/api/src/client/types/chat/bsky/convo/unmuteConvo.ts b/packages/api/src/client/types/chat/bsky/convo/unmuteConvo.ts index 93ac8785db7..6644305ce65 100644 --- a/packages/api/src/client/types/chat/bsky/convo/unmuteConvo.ts +++ b/packages/api/src/client/types/chat/bsky/convo/unmuteConvo.ts @@ -3,21 +3,23 @@ */ import { HeadersMap, XRPCError } from '@atproto/xrpc' import { ValidationResult, BlobRef } from '@atproto/lexicon' -import { isObj, hasProp } from '../../../../util' -import { lexicons } from '../../../../lexicons' import { CID } from 'multiformats/cid' -import * as ChatBskyConvoDefs from './defs' +import { validate as _validate } from '../../../../lexicons' +import { $Type, $Typed, is$typed as _is$typed, OmitKey } from '../../../../util' +import type * as ChatBskyConvoDefs from './defs' + +const is$typed = _is$typed, + validate = _validate +const id = 'chat.bsky.convo.unmuteConvo' export interface QueryParams {} export interface InputSchema { convoId: string - [k: string]: unknown } export interface OutputSchema { convo: ChatBskyConvoDefs.ConvoView - [k: string]: unknown } export interface CallOptions { diff --git a/packages/api/src/client/types/chat/bsky/convo/updateRead.ts b/packages/api/src/client/types/chat/bsky/convo/updateRead.ts index b7a8041dbd7..d98e29ccb1d 100644 --- a/packages/api/src/client/types/chat/bsky/convo/updateRead.ts +++ b/packages/api/src/client/types/chat/bsky/convo/updateRead.ts @@ -3,22 +3,24 @@ */ import { HeadersMap, XRPCError } from '@atproto/xrpc' import { ValidationResult, BlobRef } from '@atproto/lexicon' -import { isObj, hasProp } from '../../../../util' -import { lexicons } from '../../../../lexicons' import { CID } from 'multiformats/cid' -import * as ChatBskyConvoDefs from './defs' +import { validate as _validate } from '../../../../lexicons' +import { $Type, $Typed, is$typed as _is$typed, OmitKey } from '../../../../util' +import type * as ChatBskyConvoDefs from './defs' + +const is$typed = _is$typed, + validate = _validate +const id = 'chat.bsky.convo.updateRead' export interface QueryParams {} export interface InputSchema { convoId: string messageId?: string - [k: string]: unknown } export interface OutputSchema { convo: ChatBskyConvoDefs.ConvoView - [k: string]: unknown } export interface CallOptions { diff --git a/packages/api/src/client/types/chat/bsky/moderation/getActorMetadata.ts b/packages/api/src/client/types/chat/bsky/moderation/getActorMetadata.ts index 6761c9939b5..63ce6735d8d 100644 --- a/packages/api/src/client/types/chat/bsky/moderation/getActorMetadata.ts +++ b/packages/api/src/client/types/chat/bsky/moderation/getActorMetadata.ts @@ -3,9 +3,13 @@ */ import { HeadersMap, XRPCError } from '@atproto/xrpc' import { ValidationResult, BlobRef } from '@atproto/lexicon' -import { isObj, hasProp } from '../../../../util' -import { lexicons } from '../../../../lexicons' import { CID } from 'multiformats/cid' +import { validate as _validate } from '../../../../lexicons' +import { $Type, $Typed, is$typed as _is$typed, OmitKey } from '../../../../util' + +const is$typed = _is$typed, + validate = _validate +const id = 'chat.bsky.moderation.getActorMetadata' export interface QueryParams { actor: string @@ -17,7 +21,6 @@ export interface OutputSchema { day: Metadata month: Metadata all: Metadata - [k: string]: unknown } export interface CallOptions { @@ -36,21 +39,19 @@ export function toKnownErr(e: any) { } export interface Metadata { + $type?: $Type<'chat.bsky.moderation.getActorMetadata', 'metadata'> messagesSent: number messagesReceived: number convos: number convosStarted: number - [k: string]: unknown } -export function isMetadata(v: unknown): v is Metadata { - return ( - isObj(v) && - hasProp(v, '$type') && - v.$type === 'chat.bsky.moderation.getActorMetadata#metadata' - ) +const hashMetadata = 'metadata' + +export function isMetadata(v: V) { + return is$typed(v, id, hashMetadata) } -export function validateMetadata(v: unknown): ValidationResult { - return lexicons.validate('chat.bsky.moderation.getActorMetadata#metadata', v) +export function validateMetadata(v: V) { + return validate(v, id, hashMetadata) } diff --git a/packages/api/src/client/types/chat/bsky/moderation/getMessageContext.ts b/packages/api/src/client/types/chat/bsky/moderation/getMessageContext.ts index 2315bfd4ad8..0fc458d6aed 100644 --- a/packages/api/src/client/types/chat/bsky/moderation/getMessageContext.ts +++ b/packages/api/src/client/types/chat/bsky/moderation/getMessageContext.ts @@ -3,10 +3,14 @@ */ import { HeadersMap, XRPCError } from '@atproto/xrpc' import { ValidationResult, BlobRef } from '@atproto/lexicon' -import { isObj, hasProp } from '../../../../util' -import { lexicons } from '../../../../lexicons' import { CID } from 'multiformats/cid' -import * as ChatBskyConvoDefs from '../convo/defs' +import { validate as _validate } from '../../../../lexicons' +import { $Type, $Typed, is$typed as _is$typed, OmitKey } from '../../../../util' +import type * as ChatBskyConvoDefs from '../convo/defs' + +const is$typed = _is$typed, + validate = _validate +const id = 'chat.bsky.moderation.getMessageContext' export interface QueryParams { /** Conversation that the message is from. NOTE: this field will eventually be required. */ @@ -20,11 +24,10 @@ export type InputSchema = undefined export interface OutputSchema { messages: ( - | ChatBskyConvoDefs.MessageView - | ChatBskyConvoDefs.DeletedMessageView - | { $type: string; [k: string]: unknown } + | $Typed + | $Typed + | { $type: string } )[] - [k: string]: unknown } export interface CallOptions { diff --git a/packages/api/src/client/types/chat/bsky/moderation/updateActorAccess.ts b/packages/api/src/client/types/chat/bsky/moderation/updateActorAccess.ts index d6f0205672e..62f5935f207 100644 --- a/packages/api/src/client/types/chat/bsky/moderation/updateActorAccess.ts +++ b/packages/api/src/client/types/chat/bsky/moderation/updateActorAccess.ts @@ -3,9 +3,13 @@ */ import { HeadersMap, XRPCError } from '@atproto/xrpc' import { ValidationResult, BlobRef } from '@atproto/lexicon' -import { isObj, hasProp } from '../../../../util' -import { lexicons } from '../../../../lexicons' import { CID } from 'multiformats/cid' +import { validate as _validate } from '../../../../lexicons' +import { $Type, $Typed, is$typed as _is$typed, OmitKey } from '../../../../util' + +const is$typed = _is$typed, + validate = _validate +const id = 'chat.bsky.moderation.updateActorAccess' export interface QueryParams {} @@ -13,7 +17,6 @@ export interface InputSchema { actor: string allowAccess: boolean ref?: string - [k: string]: unknown } export interface CallOptions { diff --git a/packages/api/src/client/types/com/atproto/admin/defs.ts b/packages/api/src/client/types/com/atproto/admin/defs.ts index 26510680347..28530162c78 100644 --- a/packages/api/src/client/types/com/atproto/admin/defs.ts +++ b/packages/api/src/client/types/com/atproto/admin/defs.ts @@ -2,34 +2,37 @@ * GENERATED CODE - DO NOT MODIFY */ import { ValidationResult, BlobRef } from '@atproto/lexicon' -import { isObj, hasProp } from '../../../../util' -import { lexicons } from '../../../../lexicons' import { CID } from 'multiformats/cid' -import * as ComAtprotoServerDefs from '../server/defs' +import { validate as _validate } from '../../../../lexicons' +import { $Type, $Typed, is$typed as _is$typed, OmitKey } from '../../../../util' +import type * as ComAtprotoServerDefs from '../server/defs' + +const is$typed = _is$typed, + validate = _validate +const id = 'com.atproto.admin.defs' export interface StatusAttr { + $type?: $Type<'com.atproto.admin.defs', 'statusAttr'> applied: boolean ref?: string - [k: string]: unknown } -export function isStatusAttr(v: unknown): v is StatusAttr { - return ( - isObj(v) && - hasProp(v, '$type') && - v.$type === 'com.atproto.admin.defs#statusAttr' - ) +const hashStatusAttr = 'statusAttr' + +export function isStatusAttr(v: V) { + return is$typed(v, id, hashStatusAttr) } -export function validateStatusAttr(v: unknown): ValidationResult { - return lexicons.validate('com.atproto.admin.defs#statusAttr', v) +export function validateStatusAttr(v: V) { + return validate(v, id, hashStatusAttr) } export interface AccountView { + $type?: $Type<'com.atproto.admin.defs', 'accountView'> did: string handle: string email?: string - relatedRecords?: {}[] + relatedRecords?: { [_ in string]: unknown }[] indexedAt: string invitedBy?: ComAtprotoServerDefs.InviteCode invites?: ComAtprotoServerDefs.InviteCode[] @@ -38,71 +41,62 @@ export interface AccountView { inviteNote?: string deactivatedAt?: string threatSignatures?: ThreatSignature[] - [k: string]: unknown } -export function isAccountView(v: unknown): v is AccountView { - return ( - isObj(v) && - hasProp(v, '$type') && - v.$type === 'com.atproto.admin.defs#accountView' - ) +const hashAccountView = 'accountView' + +export function isAccountView(v: V) { + return is$typed(v, id, hashAccountView) } -export function validateAccountView(v: unknown): ValidationResult { - return lexicons.validate('com.atproto.admin.defs#accountView', v) +export function validateAccountView(v: V) { + return validate(v, id, hashAccountView) } export interface RepoRef { + $type?: $Type<'com.atproto.admin.defs', 'repoRef'> did: string - [k: string]: unknown } -export function isRepoRef(v: unknown): v is RepoRef { - return ( - isObj(v) && - hasProp(v, '$type') && - v.$type === 'com.atproto.admin.defs#repoRef' - ) +const hashRepoRef = 'repoRef' + +export function isRepoRef(v: V) { + return is$typed(v, id, hashRepoRef) } -export function validateRepoRef(v: unknown): ValidationResult { - return lexicons.validate('com.atproto.admin.defs#repoRef', v) +export function validateRepoRef(v: V) { + return validate(v, id, hashRepoRef) } export interface RepoBlobRef { + $type?: $Type<'com.atproto.admin.defs', 'repoBlobRef'> did: string cid: string recordUri?: string - [k: string]: unknown } -export function isRepoBlobRef(v: unknown): v is RepoBlobRef { - return ( - isObj(v) && - hasProp(v, '$type') && - v.$type === 'com.atproto.admin.defs#repoBlobRef' - ) +const hashRepoBlobRef = 'repoBlobRef' + +export function isRepoBlobRef(v: V) { + return is$typed(v, id, hashRepoBlobRef) } -export function validateRepoBlobRef(v: unknown): ValidationResult { - return lexicons.validate('com.atproto.admin.defs#repoBlobRef', v) +export function validateRepoBlobRef(v: V) { + return validate(v, id, hashRepoBlobRef) } export interface ThreatSignature { + $type?: $Type<'com.atproto.admin.defs', 'threatSignature'> property: string value: string - [k: string]: unknown } -export function isThreatSignature(v: unknown): v is ThreatSignature { - return ( - isObj(v) && - hasProp(v, '$type') && - v.$type === 'com.atproto.admin.defs#threatSignature' - ) +const hashThreatSignature = 'threatSignature' + +export function isThreatSignature(v: V) { + return is$typed(v, id, hashThreatSignature) } -export function validateThreatSignature(v: unknown): ValidationResult { - return lexicons.validate('com.atproto.admin.defs#threatSignature', v) +export function validateThreatSignature(v: V) { + return validate(v, id, hashThreatSignature) } diff --git a/packages/api/src/client/types/com/atproto/admin/deleteAccount.ts b/packages/api/src/client/types/com/atproto/admin/deleteAccount.ts index 72066a656b7..75a836dd40f 100644 --- a/packages/api/src/client/types/com/atproto/admin/deleteAccount.ts +++ b/packages/api/src/client/types/com/atproto/admin/deleteAccount.ts @@ -3,15 +3,18 @@ */ import { HeadersMap, XRPCError } from '@atproto/xrpc' import { ValidationResult, BlobRef } from '@atproto/lexicon' -import { isObj, hasProp } from '../../../../util' -import { lexicons } from '../../../../lexicons' import { CID } from 'multiformats/cid' +import { validate as _validate } from '../../../../lexicons' +import { $Type, $Typed, is$typed as _is$typed, OmitKey } from '../../../../util' + +const is$typed = _is$typed, + validate = _validate +const id = 'com.atproto.admin.deleteAccount' export interface QueryParams {} export interface InputSchema { did: string - [k: string]: unknown } export interface CallOptions { diff --git a/packages/api/src/client/types/com/atproto/admin/disableAccountInvites.ts b/packages/api/src/client/types/com/atproto/admin/disableAccountInvites.ts index 8df420239de..3ecc7199edc 100644 --- a/packages/api/src/client/types/com/atproto/admin/disableAccountInvites.ts +++ b/packages/api/src/client/types/com/atproto/admin/disableAccountInvites.ts @@ -3,9 +3,13 @@ */ import { HeadersMap, XRPCError } from '@atproto/xrpc' import { ValidationResult, BlobRef } from '@atproto/lexicon' -import { isObj, hasProp } from '../../../../util' -import { lexicons } from '../../../../lexicons' import { CID } from 'multiformats/cid' +import { validate as _validate } from '../../../../lexicons' +import { $Type, $Typed, is$typed as _is$typed, OmitKey } from '../../../../util' + +const is$typed = _is$typed, + validate = _validate +const id = 'com.atproto.admin.disableAccountInvites' export interface QueryParams {} @@ -13,7 +17,6 @@ export interface InputSchema { account: string /** Optional reason for disabled invites. */ note?: string - [k: string]: unknown } export interface CallOptions { diff --git a/packages/api/src/client/types/com/atproto/admin/disableInviteCodes.ts b/packages/api/src/client/types/com/atproto/admin/disableInviteCodes.ts index c264b65dbc2..e399862310a 100644 --- a/packages/api/src/client/types/com/atproto/admin/disableInviteCodes.ts +++ b/packages/api/src/client/types/com/atproto/admin/disableInviteCodes.ts @@ -3,16 +3,19 @@ */ import { HeadersMap, XRPCError } from '@atproto/xrpc' import { ValidationResult, BlobRef } from '@atproto/lexicon' -import { isObj, hasProp } from '../../../../util' -import { lexicons } from '../../../../lexicons' import { CID } from 'multiformats/cid' +import { validate as _validate } from '../../../../lexicons' +import { $Type, $Typed, is$typed as _is$typed, OmitKey } from '../../../../util' + +const is$typed = _is$typed, + validate = _validate +const id = 'com.atproto.admin.disableInviteCodes' export interface QueryParams {} export interface InputSchema { codes?: string[] accounts?: string[] - [k: string]: unknown } export interface CallOptions { diff --git a/packages/api/src/client/types/com/atproto/admin/enableAccountInvites.ts b/packages/api/src/client/types/com/atproto/admin/enableAccountInvites.ts index 094b3dfe0af..689493b2ce1 100644 --- a/packages/api/src/client/types/com/atproto/admin/enableAccountInvites.ts +++ b/packages/api/src/client/types/com/atproto/admin/enableAccountInvites.ts @@ -3,9 +3,13 @@ */ import { HeadersMap, XRPCError } from '@atproto/xrpc' import { ValidationResult, BlobRef } from '@atproto/lexicon' -import { isObj, hasProp } from '../../../../util' -import { lexicons } from '../../../../lexicons' import { CID } from 'multiformats/cid' +import { validate as _validate } from '../../../../lexicons' +import { $Type, $Typed, is$typed as _is$typed, OmitKey } from '../../../../util' + +const is$typed = _is$typed, + validate = _validate +const id = 'com.atproto.admin.enableAccountInvites' export interface QueryParams {} @@ -13,7 +17,6 @@ export interface InputSchema { account: string /** Optional reason for enabled invites. */ note?: string - [k: string]: unknown } export interface CallOptions { diff --git a/packages/api/src/client/types/com/atproto/admin/getAccountInfo.ts b/packages/api/src/client/types/com/atproto/admin/getAccountInfo.ts index 645b9e613ff..2fecfdf1db8 100644 --- a/packages/api/src/client/types/com/atproto/admin/getAccountInfo.ts +++ b/packages/api/src/client/types/com/atproto/admin/getAccountInfo.ts @@ -3,10 +3,14 @@ */ import { HeadersMap, XRPCError } from '@atproto/xrpc' import { ValidationResult, BlobRef } from '@atproto/lexicon' -import { isObj, hasProp } from '../../../../util' -import { lexicons } from '../../../../lexicons' import { CID } from 'multiformats/cid' -import * as ComAtprotoAdminDefs from './defs' +import { validate as _validate } from '../../../../lexicons' +import { $Type, $Typed, is$typed as _is$typed, OmitKey } from '../../../../util' +import type * as ComAtprotoAdminDefs from './defs' + +const is$typed = _is$typed, + validate = _validate +const id = 'com.atproto.admin.getAccountInfo' export interface QueryParams { did: string diff --git a/packages/api/src/client/types/com/atproto/admin/getAccountInfos.ts b/packages/api/src/client/types/com/atproto/admin/getAccountInfos.ts index 9c1c273b9d9..fd452d9e92b 100644 --- a/packages/api/src/client/types/com/atproto/admin/getAccountInfos.ts +++ b/packages/api/src/client/types/com/atproto/admin/getAccountInfos.ts @@ -3,10 +3,14 @@ */ import { HeadersMap, XRPCError } from '@atproto/xrpc' import { ValidationResult, BlobRef } from '@atproto/lexicon' -import { isObj, hasProp } from '../../../../util' -import { lexicons } from '../../../../lexicons' import { CID } from 'multiformats/cid' -import * as ComAtprotoAdminDefs from './defs' +import { validate as _validate } from '../../../../lexicons' +import { $Type, $Typed, is$typed as _is$typed, OmitKey } from '../../../../util' +import type * as ComAtprotoAdminDefs from './defs' + +const is$typed = _is$typed, + validate = _validate +const id = 'com.atproto.admin.getAccountInfos' export interface QueryParams { dids: string[] @@ -16,7 +20,6 @@ export type InputSchema = undefined export interface OutputSchema { infos: ComAtprotoAdminDefs.AccountView[] - [k: string]: unknown } export interface CallOptions { diff --git a/packages/api/src/client/types/com/atproto/admin/getInviteCodes.ts b/packages/api/src/client/types/com/atproto/admin/getInviteCodes.ts index faa4c8bed25..e5cf4c016d2 100644 --- a/packages/api/src/client/types/com/atproto/admin/getInviteCodes.ts +++ b/packages/api/src/client/types/com/atproto/admin/getInviteCodes.ts @@ -3,10 +3,14 @@ */ import { HeadersMap, XRPCError } from '@atproto/xrpc' import { ValidationResult, BlobRef } from '@atproto/lexicon' -import { isObj, hasProp } from '../../../../util' -import { lexicons } from '../../../../lexicons' import { CID } from 'multiformats/cid' -import * as ComAtprotoServerDefs from '../server/defs' +import { validate as _validate } from '../../../../lexicons' +import { $Type, $Typed, is$typed as _is$typed, OmitKey } from '../../../../util' +import type * as ComAtprotoServerDefs from '../server/defs' + +const is$typed = _is$typed, + validate = _validate +const id = 'com.atproto.admin.getInviteCodes' export interface QueryParams { sort?: 'recent' | 'usage' | (string & {}) @@ -19,7 +23,6 @@ export type InputSchema = undefined export interface OutputSchema { cursor?: string codes: ComAtprotoServerDefs.InviteCode[] - [k: string]: unknown } export interface CallOptions { diff --git a/packages/api/src/client/types/com/atproto/admin/getSubjectStatus.ts b/packages/api/src/client/types/com/atproto/admin/getSubjectStatus.ts index f11b514507d..ecd087262af 100644 --- a/packages/api/src/client/types/com/atproto/admin/getSubjectStatus.ts +++ b/packages/api/src/client/types/com/atproto/admin/getSubjectStatus.ts @@ -3,11 +3,15 @@ */ import { HeadersMap, XRPCError } from '@atproto/xrpc' import { ValidationResult, BlobRef } from '@atproto/lexicon' -import { isObj, hasProp } from '../../../../util' -import { lexicons } from '../../../../lexicons' import { CID } from 'multiformats/cid' -import * as ComAtprotoAdminDefs from './defs' -import * as ComAtprotoRepoStrongRef from '../repo/strongRef' +import { validate as _validate } from '../../../../lexicons' +import { $Type, $Typed, is$typed as _is$typed, OmitKey } from '../../../../util' +import type * as ComAtprotoAdminDefs from './defs' +import type * as ComAtprotoRepoStrongRef from '../repo/strongRef' + +const is$typed = _is$typed, + validate = _validate +const id = 'com.atproto.admin.getSubjectStatus' export interface QueryParams { did?: string @@ -19,13 +23,12 @@ export type InputSchema = undefined export interface OutputSchema { subject: - | ComAtprotoAdminDefs.RepoRef - | ComAtprotoRepoStrongRef.Main - | ComAtprotoAdminDefs.RepoBlobRef - | { $type: string; [k: string]: unknown } + | $Typed + | $Typed + | $Typed + | { $type: string } takedown?: ComAtprotoAdminDefs.StatusAttr deactivated?: ComAtprotoAdminDefs.StatusAttr - [k: string]: unknown } export interface CallOptions { diff --git a/packages/api/src/client/types/com/atproto/admin/searchAccounts.ts b/packages/api/src/client/types/com/atproto/admin/searchAccounts.ts index 2d22806238c..a66279741d8 100644 --- a/packages/api/src/client/types/com/atproto/admin/searchAccounts.ts +++ b/packages/api/src/client/types/com/atproto/admin/searchAccounts.ts @@ -3,10 +3,14 @@ */ import { HeadersMap, XRPCError } from '@atproto/xrpc' import { ValidationResult, BlobRef } from '@atproto/lexicon' -import { isObj, hasProp } from '../../../../util' -import { lexicons } from '../../../../lexicons' import { CID } from 'multiformats/cid' -import * as ComAtprotoAdminDefs from './defs' +import { validate as _validate } from '../../../../lexicons' +import { $Type, $Typed, is$typed as _is$typed, OmitKey } from '../../../../util' +import type * as ComAtprotoAdminDefs from './defs' + +const is$typed = _is$typed, + validate = _validate +const id = 'com.atproto.admin.searchAccounts' export interface QueryParams { email?: string @@ -19,7 +23,6 @@ export type InputSchema = undefined export interface OutputSchema { cursor?: string accounts: ComAtprotoAdminDefs.AccountView[] - [k: string]: unknown } export interface CallOptions { diff --git a/packages/api/src/client/types/com/atproto/admin/sendEmail.ts b/packages/api/src/client/types/com/atproto/admin/sendEmail.ts index 3f7c06d7070..378b6ac2cd0 100644 --- a/packages/api/src/client/types/com/atproto/admin/sendEmail.ts +++ b/packages/api/src/client/types/com/atproto/admin/sendEmail.ts @@ -3,9 +3,13 @@ */ import { HeadersMap, XRPCError } from '@atproto/xrpc' import { ValidationResult, BlobRef } from '@atproto/lexicon' -import { isObj, hasProp } from '../../../../util' -import { lexicons } from '../../../../lexicons' import { CID } from 'multiformats/cid' +import { validate as _validate } from '../../../../lexicons' +import { $Type, $Typed, is$typed as _is$typed, OmitKey } from '../../../../util' + +const is$typed = _is$typed, + validate = _validate +const id = 'com.atproto.admin.sendEmail' export interface QueryParams {} @@ -16,12 +20,10 @@ export interface InputSchema { senderDid: string /** Additional comment by the sender that won't be used in the email itself but helpful to provide more context for moderators/reviewers */ comment?: string - [k: string]: unknown } export interface OutputSchema { sent: boolean - [k: string]: unknown } export interface CallOptions { diff --git a/packages/api/src/client/types/com/atproto/admin/updateAccountEmail.ts b/packages/api/src/client/types/com/atproto/admin/updateAccountEmail.ts index d13878711a6..58dec00ec4f 100644 --- a/packages/api/src/client/types/com/atproto/admin/updateAccountEmail.ts +++ b/packages/api/src/client/types/com/atproto/admin/updateAccountEmail.ts @@ -3,9 +3,13 @@ */ import { HeadersMap, XRPCError } from '@atproto/xrpc' import { ValidationResult, BlobRef } from '@atproto/lexicon' -import { isObj, hasProp } from '../../../../util' -import { lexicons } from '../../../../lexicons' import { CID } from 'multiformats/cid' +import { validate as _validate } from '../../../../lexicons' +import { $Type, $Typed, is$typed as _is$typed, OmitKey } from '../../../../util' + +const is$typed = _is$typed, + validate = _validate +const id = 'com.atproto.admin.updateAccountEmail' export interface QueryParams {} @@ -13,7 +17,6 @@ export interface InputSchema { /** The handle or DID of the repo. */ account: string email: string - [k: string]: unknown } export interface CallOptions { diff --git a/packages/api/src/client/types/com/atproto/admin/updateAccountHandle.ts b/packages/api/src/client/types/com/atproto/admin/updateAccountHandle.ts index 38fbcae1681..b7eee591174 100644 --- a/packages/api/src/client/types/com/atproto/admin/updateAccountHandle.ts +++ b/packages/api/src/client/types/com/atproto/admin/updateAccountHandle.ts @@ -3,16 +3,19 @@ */ import { HeadersMap, XRPCError } from '@atproto/xrpc' import { ValidationResult, BlobRef } from '@atproto/lexicon' -import { isObj, hasProp } from '../../../../util' -import { lexicons } from '../../../../lexicons' import { CID } from 'multiformats/cid' +import { validate as _validate } from '../../../../lexicons' +import { $Type, $Typed, is$typed as _is$typed, OmitKey } from '../../../../util' + +const is$typed = _is$typed, + validate = _validate +const id = 'com.atproto.admin.updateAccountHandle' export interface QueryParams {} export interface InputSchema { did: string handle: string - [k: string]: unknown } export interface CallOptions { diff --git a/packages/api/src/client/types/com/atproto/admin/updateAccountPassword.ts b/packages/api/src/client/types/com/atproto/admin/updateAccountPassword.ts index 412f0facca2..288f6c6c85e 100644 --- a/packages/api/src/client/types/com/atproto/admin/updateAccountPassword.ts +++ b/packages/api/src/client/types/com/atproto/admin/updateAccountPassword.ts @@ -3,16 +3,19 @@ */ import { HeadersMap, XRPCError } from '@atproto/xrpc' import { ValidationResult, BlobRef } from '@atproto/lexicon' -import { isObj, hasProp } from '../../../../util' -import { lexicons } from '../../../../lexicons' import { CID } from 'multiformats/cid' +import { validate as _validate } from '../../../../lexicons' +import { $Type, $Typed, is$typed as _is$typed, OmitKey } from '../../../../util' + +const is$typed = _is$typed, + validate = _validate +const id = 'com.atproto.admin.updateAccountPassword' export interface QueryParams {} export interface InputSchema { did: string password: string - [k: string]: unknown } export interface CallOptions { diff --git a/packages/api/src/client/types/com/atproto/admin/updateSubjectStatus.ts b/packages/api/src/client/types/com/atproto/admin/updateSubjectStatus.ts index d890993cf3a..dbc892efbd0 100644 --- a/packages/api/src/client/types/com/atproto/admin/updateSubjectStatus.ts +++ b/packages/api/src/client/types/com/atproto/admin/updateSubjectStatus.ts @@ -3,33 +3,35 @@ */ import { HeadersMap, XRPCError } from '@atproto/xrpc' import { ValidationResult, BlobRef } from '@atproto/lexicon' -import { isObj, hasProp } from '../../../../util' -import { lexicons } from '../../../../lexicons' import { CID } from 'multiformats/cid' -import * as ComAtprotoAdminDefs from './defs' -import * as ComAtprotoRepoStrongRef from '../repo/strongRef' +import { validate as _validate } from '../../../../lexicons' +import { $Type, $Typed, is$typed as _is$typed, OmitKey } from '../../../../util' +import type * as ComAtprotoAdminDefs from './defs' +import type * as ComAtprotoRepoStrongRef from '../repo/strongRef' + +const is$typed = _is$typed, + validate = _validate +const id = 'com.atproto.admin.updateSubjectStatus' export interface QueryParams {} export interface InputSchema { subject: - | ComAtprotoAdminDefs.RepoRef - | ComAtprotoRepoStrongRef.Main - | ComAtprotoAdminDefs.RepoBlobRef - | { $type: string; [k: string]: unknown } + | $Typed + | $Typed + | $Typed + | { $type: string } takedown?: ComAtprotoAdminDefs.StatusAttr deactivated?: ComAtprotoAdminDefs.StatusAttr - [k: string]: unknown } export interface OutputSchema { subject: - | ComAtprotoAdminDefs.RepoRef - | ComAtprotoRepoStrongRef.Main - | ComAtprotoAdminDefs.RepoBlobRef - | { $type: string; [k: string]: unknown } + | $Typed + | $Typed + | $Typed + | { $type: string } takedown?: ComAtprotoAdminDefs.StatusAttr - [k: string]: unknown } export interface CallOptions { diff --git a/packages/api/src/client/types/com/atproto/identity/getRecommendedDidCredentials.ts b/packages/api/src/client/types/com/atproto/identity/getRecommendedDidCredentials.ts index a5e4a0296de..11ff6429e2a 100644 --- a/packages/api/src/client/types/com/atproto/identity/getRecommendedDidCredentials.ts +++ b/packages/api/src/client/types/com/atproto/identity/getRecommendedDidCredentials.ts @@ -3,9 +3,13 @@ */ import { HeadersMap, XRPCError } from '@atproto/xrpc' import { ValidationResult, BlobRef } from '@atproto/lexicon' -import { isObj, hasProp } from '../../../../util' -import { lexicons } from '../../../../lexicons' import { CID } from 'multiformats/cid' +import { validate as _validate } from '../../../../lexicons' +import { $Type, $Typed, is$typed as _is$typed, OmitKey } from '../../../../util' + +const is$typed = _is$typed, + validate = _validate +const id = 'com.atproto.identity.getRecommendedDidCredentials' export interface QueryParams {} @@ -15,9 +19,8 @@ export interface OutputSchema { /** Recommended rotation keys for PLC dids. Should be undefined (or ignored) for did:webs. */ rotationKeys?: string[] alsoKnownAs?: string[] - verificationMethods?: {} - services?: {} - [k: string]: unknown + verificationMethods?: { [_ in string]: unknown } + services?: { [_ in string]: unknown } } export interface CallOptions { diff --git a/packages/api/src/client/types/com/atproto/identity/requestPlcOperationSignature.ts b/packages/api/src/client/types/com/atproto/identity/requestPlcOperationSignature.ts index dcab71e2558..2f6407b2de9 100644 --- a/packages/api/src/client/types/com/atproto/identity/requestPlcOperationSignature.ts +++ b/packages/api/src/client/types/com/atproto/identity/requestPlcOperationSignature.ts @@ -3,9 +3,13 @@ */ import { HeadersMap, XRPCError } from '@atproto/xrpc' import { ValidationResult, BlobRef } from '@atproto/lexicon' -import { isObj, hasProp } from '../../../../util' -import { lexicons } from '../../../../lexicons' import { CID } from 'multiformats/cid' +import { validate as _validate } from '../../../../lexicons' +import { $Type, $Typed, is$typed as _is$typed, OmitKey } from '../../../../util' + +const is$typed = _is$typed, + validate = _validate +const id = 'com.atproto.identity.requestPlcOperationSignature' export interface QueryParams {} diff --git a/packages/api/src/client/types/com/atproto/identity/resolveHandle.ts b/packages/api/src/client/types/com/atproto/identity/resolveHandle.ts index 32db72138f7..6dacde86a85 100644 --- a/packages/api/src/client/types/com/atproto/identity/resolveHandle.ts +++ b/packages/api/src/client/types/com/atproto/identity/resolveHandle.ts @@ -3,9 +3,13 @@ */ import { HeadersMap, XRPCError } from '@atproto/xrpc' import { ValidationResult, BlobRef } from '@atproto/lexicon' -import { isObj, hasProp } from '../../../../util' -import { lexicons } from '../../../../lexicons' import { CID } from 'multiformats/cid' +import { validate as _validate } from '../../../../lexicons' +import { $Type, $Typed, is$typed as _is$typed, OmitKey } from '../../../../util' + +const is$typed = _is$typed, + validate = _validate +const id = 'com.atproto.identity.resolveHandle' export interface QueryParams { /** The handle to resolve. */ @@ -16,7 +20,6 @@ export type InputSchema = undefined export interface OutputSchema { did: string - [k: string]: unknown } export interface CallOptions { diff --git a/packages/api/src/client/types/com/atproto/identity/signPlcOperation.ts b/packages/api/src/client/types/com/atproto/identity/signPlcOperation.ts index 88c04c5993c..de03866a55d 100644 --- a/packages/api/src/client/types/com/atproto/identity/signPlcOperation.ts +++ b/packages/api/src/client/types/com/atproto/identity/signPlcOperation.ts @@ -3,9 +3,13 @@ */ import { HeadersMap, XRPCError } from '@atproto/xrpc' import { ValidationResult, BlobRef } from '@atproto/lexicon' -import { isObj, hasProp } from '../../../../util' -import { lexicons } from '../../../../lexicons' import { CID } from 'multiformats/cid' +import { validate as _validate } from '../../../../lexicons' +import { $Type, $Typed, is$typed as _is$typed, OmitKey } from '../../../../util' + +const is$typed = _is$typed, + validate = _validate +const id = 'com.atproto.identity.signPlcOperation' export interface QueryParams {} @@ -14,15 +18,13 @@ export interface InputSchema { token?: string rotationKeys?: string[] alsoKnownAs?: string[] - verificationMethods?: {} - services?: {} - [k: string]: unknown + verificationMethods?: { [_ in string]: unknown } + services?: { [_ in string]: unknown } } export interface OutputSchema { /** A signed DID PLC operation. */ - operation: {} - [k: string]: unknown + operation: { [_ in string]: unknown } } export interface CallOptions { diff --git a/packages/api/src/client/types/com/atproto/identity/submitPlcOperation.ts b/packages/api/src/client/types/com/atproto/identity/submitPlcOperation.ts index 74dea9f196d..14db8ba3d5e 100644 --- a/packages/api/src/client/types/com/atproto/identity/submitPlcOperation.ts +++ b/packages/api/src/client/types/com/atproto/identity/submitPlcOperation.ts @@ -3,15 +3,18 @@ */ import { HeadersMap, XRPCError } from '@atproto/xrpc' import { ValidationResult, BlobRef } from '@atproto/lexicon' -import { isObj, hasProp } from '../../../../util' -import { lexicons } from '../../../../lexicons' import { CID } from 'multiformats/cid' +import { validate as _validate } from '../../../../lexicons' +import { $Type, $Typed, is$typed as _is$typed, OmitKey } from '../../../../util' + +const is$typed = _is$typed, + validate = _validate +const id = 'com.atproto.identity.submitPlcOperation' export interface QueryParams {} export interface InputSchema { - operation: {} - [k: string]: unknown + operation: { [_ in string]: unknown } } export interface CallOptions { diff --git a/packages/api/src/client/types/com/atproto/identity/updateHandle.ts b/packages/api/src/client/types/com/atproto/identity/updateHandle.ts index c01d4887a4a..b10ad644dbf 100644 --- a/packages/api/src/client/types/com/atproto/identity/updateHandle.ts +++ b/packages/api/src/client/types/com/atproto/identity/updateHandle.ts @@ -3,16 +3,19 @@ */ import { HeadersMap, XRPCError } from '@atproto/xrpc' import { ValidationResult, BlobRef } from '@atproto/lexicon' -import { isObj, hasProp } from '../../../../util' -import { lexicons } from '../../../../lexicons' import { CID } from 'multiformats/cid' +import { validate as _validate } from '../../../../lexicons' +import { $Type, $Typed, is$typed as _is$typed, OmitKey } from '../../../../util' + +const is$typed = _is$typed, + validate = _validate +const id = 'com.atproto.identity.updateHandle' export interface QueryParams {} export interface InputSchema { /** The new handle. */ handle: string - [k: string]: unknown } export interface CallOptions { diff --git a/packages/api/src/client/types/com/atproto/label/defs.ts b/packages/api/src/client/types/com/atproto/label/defs.ts index 131682e550c..0e3445c5c1f 100644 --- a/packages/api/src/client/types/com/atproto/label/defs.ts +++ b/packages/api/src/client/types/com/atproto/label/defs.ts @@ -2,12 +2,17 @@ * GENERATED CODE - DO NOT MODIFY */ import { ValidationResult, BlobRef } from '@atproto/lexicon' -import { isObj, hasProp } from '../../../../util' -import { lexicons } from '../../../../lexicons' import { CID } from 'multiformats/cid' +import { validate as _validate } from '../../../../lexicons' +import { $Type, $Typed, is$typed as _is$typed, OmitKey } from '../../../../util' + +const is$typed = _is$typed, + validate = _validate +const id = 'com.atproto.label.defs' /** Metadata tag on an atproto resource (eg, repo or record). */ export interface Label { + $type?: $Type<'com.atproto.label.defs', 'label'> /** The AT Protocol version of the label object. */ ver?: number /** DID of the actor who created this label. */ @@ -26,60 +31,54 @@ export interface Label { exp?: string /** Signature of dag-cbor encoded label. */ sig?: Uint8Array - [k: string]: unknown } -export function isLabel(v: unknown): v is Label { - return ( - isObj(v) && - hasProp(v, '$type') && - v.$type === 'com.atproto.label.defs#label' - ) +const hashLabel = 'label' + +export function isLabel(v: V) { + return is$typed(v, id, hashLabel) } -export function validateLabel(v: unknown): ValidationResult { - return lexicons.validate('com.atproto.label.defs#label', v) +export function validateLabel(v: V) { + return validate