Files
omnivore/packages/api/src/resolvers/function_resolvers.ts
2024-07-31 19:14:38 +08:00

941 lines
31 KiB
TypeScript

/* eslint-disable @typescript-eslint/no-unused-vars */
/* eslint-disable @typescript-eslint/require-await */
/* eslint-disable @typescript-eslint/no-unsafe-return */
/* eslint-disable @typescript-eslint/no-unsafe-member-access */
/* eslint-disable @typescript-eslint/explicit-module-boundary-types */
import { createHmac } from 'crypto'
import * as httpContext from 'express-http-context2'
import { isError } from 'lodash'
import { Highlight } from '../entity/highlight'
import { LibraryItem, LibraryItemState } from '../entity/library_item'
import {
EXISTING_NEWSLETTER_FOLDER,
NewsletterEmail,
} from '../entity/newsletter_email'
import { Post } from '../entity/post'
import { PublicItem } from '../entity/public_item'
import { Recommendation } from '../entity/recommendation'
import {
DEFAULT_SUBSCRIPTION_FOLDER,
Subscription,
} from '../entity/subscription'
import { User as UserEntity } from '../entity/user'
import { env } from '../env'
import {
HomeItem,
HomeItemSource,
HomeItemSourceType,
PageType,
User,
} from '../generated/graphql'
import { getAISummary } from '../services/ai-summaries'
import { findUserFeatures } from '../services/features'
import { Merge } from '../util'
import { isBase64Image, validatedDate, wordsCount } from '../utils/helpers'
import { createImageProxyUrl } from '../utils/imageproxy'
import { contentConverter } from '../utils/parser'
import {
generateDownloadSignedUrl,
generateUploadFilePathName,
} from '../utils/uploads'
import {
ArticleFormat,
emptyTrashResolver,
fetchContentResolver,
PartialLibraryItem,
} from './article'
import {
addDiscoverFeedResolver,
deleteDiscoverArticleResolver,
deleteDiscoverFeedsResolver,
editDiscoverFeedsResolver,
getDiscoverFeedArticlesResolver,
getDiscoverFeedsResolver,
saveDiscoverArticleResolver,
} from './discover_feeds'
import { optInFeatureResolver } from './features'
import {
createFolderPolicyResolver,
deleteFolderPolicyResolver,
folderPoliciesResolver,
updateFolderPolicyResolver,
} from './folder_policy'
import { highlightsResolver } from './highlight'
import {
hiddenHomeSectionResolver,
homeResolver,
refreshHomeResolver,
} from './home'
import { uploadImportFileResolver } from './importers/uploadImportFileResolver'
import {
addPopularReadResolver,
apiKeysResolver,
articleSavingRequestResolver,
bulkActionResolver,
createArticleResolver,
createArticleSavingRequestResolver,
createGroupResolver,
createHighlightResolver,
createLabelResolver,
createNewsletterEmailResolver,
deleteAccountResolver,
deleteFilterResolver,
deleteHighlightResolver,
deleteIntegrationResolver,
deleteLabelResolver,
deleteNewsletterEmailResolver,
deleteRuleResolver,
deleteWebhookResolver,
deviceTokensResolver,
exportToIntegrationResolver,
feedsResolver,
filtersResolver,
generateApiKeyResolver,
getAllUsersResolver,
getArticleResolver,
getMeUserResolver,
getUserPersonalizationResolver,
getUserResolver,
googleLoginResolver,
googleSignupResolver,
groupsResolver,
importFromIntegrationResolver,
integrationResolver,
integrationsResolver,
joinGroupResolver,
labelsResolver,
leaveGroupResolver,
logOutResolver,
mergeHighlightResolver,
moveFilterResolver,
moveLabelResolver,
moveToFolderResolver,
newsletterEmailsResolver,
recommendHighlightsResolver,
recommendResolver,
reportItemResolver,
revokeApiKeyResolver,
rulesResolver,
saveArticleReadingProgressResolver,
saveFileResolver,
saveFilterResolver,
savePageResolver,
saveUrlResolver,
scanFeedsResolver,
searchResolver,
sendInstallInstructionsResolver,
setBookmarkArticleResolver,
setDeviceTokenResolver,
setFavoriteArticleResolver,
setIntegrationResolver,
setLabelsForHighlightResolver,
setLabelsResolver,
setLinkArchivedResolver,
setRuleResolver,
setUserPersonalizationResolver,
setWebhookResolver,
subscribeResolver,
subscriptionsResolver,
typeaheadSearchResolver,
unsubscribeResolver,
updateFilterResolver,
updateHighlightResolver,
updateLabelResolver,
updateNewsletterEmailResolver,
updatePageResolver,
updatesSinceResolver,
updateSubscriptionResolver,
updateUserProfileResolver,
updateUserResolver,
uploadFileRequestResolver,
validateUsernameResolver,
webhookResolver,
webhooksResolver,
} from './index'
import {
createPostResolver,
deletePostResolver,
postResolver,
postsResolver,
updatePostResolver,
} from './posts'
import {
markEmailAsItemResolver,
recentEmailsResolver,
replyToEmailResolver,
} from './recent_emails'
import { recentSearchesResolver } from './recent_searches'
import { subscriptionResolver } from './subscriptions'
import { ResolverContext } from './types'
import { updateEmailResolver } from './user'
/* eslint-disable @typescript-eslint/naming-convention */
type ResultResolveType = {
[x: string]: {
__resolveType: (obj: { errorCodes: string[] | undefined }) => string
}
}
const resultResolveTypeResolver = (
resolverName: string
): ResultResolveType => ({
[`${resolverName}Result`]: {
__resolveType: (obj) =>
obj.errorCodes ? `${resolverName}Error` : `${resolverName}Success`,
},
})
const readingProgressHandlers = {
async readingProgressPercent(
article: LibraryItem,
_: unknown,
ctx: ResolverContext
) {
if (ctx.claims?.uid) {
const readingProgress =
await ctx.dataSources.readingProgress.getReadingProgress(
ctx.claims?.uid,
article.id
)
if (readingProgress) {
return Math.max(
article.readingProgressBottomPercent ?? 0,
readingProgress.readingProgressPercent
)
}
}
return article.readingProgressBottomPercent
},
async readingProgressAnchorIndex(
article: LibraryItem,
_: unknown,
ctx: ResolverContext
) {
if (ctx.claims?.uid) {
const readingProgress =
await ctx.dataSources.readingProgress.getReadingProgress(
ctx.claims?.uid,
article.id
)
if (readingProgress && readingProgress.readingProgressAnchorIndex) {
return Math.max(
article.readingProgressHighestReadAnchor ?? 0,
readingProgress.readingProgressAnchorIndex
)
}
}
return article.readingProgressHighestReadAnchor
},
async readingProgressTopPercent(
article: LibraryItem,
_: unknown,
ctx: ResolverContext
) {
if (ctx.claims?.uid) {
const readingProgress =
await ctx.dataSources.readingProgress.getReadingProgress(
ctx.claims?.uid,
article.id
)
if (readingProgress && readingProgress.readingProgressTopPercent) {
return Math.max(
article.readingProgressTopPercent ?? 0,
readingProgress.readingProgressTopPercent
)
}
}
return article.readingProgressTopPercent
},
}
// Provide resolver functions for your schema fields
export const functionResolvers = {
Mutation: {
googleLogin: googleLoginResolver,
googleSignup: googleSignupResolver,
logOut: logOutResolver,
deleteAccount: deleteAccountResolver,
saveArticleReadingProgress: saveArticleReadingProgressResolver,
updateUser: updateUserResolver,
updateUserProfile: updateUserProfileResolver,
createArticle: createArticleResolver,
createHighlight: createHighlightResolver,
mergeHighlight: mergeHighlightResolver,
updateHighlight: updateHighlightResolver,
deleteHighlight: deleteHighlightResolver,
uploadFileRequest: uploadFileRequestResolver,
setBookmarkArticle: setBookmarkArticleResolver,
setUserPersonalization: setUserPersonalizationResolver,
createArticleSavingRequest: createArticleSavingRequestResolver,
reportItem: reportItemResolver,
setLinkArchived: setLinkArchivedResolver,
createNewsletterEmail: createNewsletterEmailResolver,
deleteNewsletterEmail: deleteNewsletterEmailResolver,
saveUrl: saveUrlResolver,
savePage: savePageResolver,
saveFile: saveFileResolver,
setDeviceToken: setDeviceTokenResolver,
createLabel: createLabelResolver,
updateLabel: updateLabelResolver,
deleteLabel: deleteLabelResolver,
setLabels: setLabelsResolver,
generateApiKey: generateApiKeyResolver,
unsubscribe: unsubscribeResolver,
updatePage: updatePageResolver,
subscribe: subscribeResolver,
addPopularRead: addPopularReadResolver,
setWebhook: setWebhookResolver,
deleteWebhook: deleteWebhookResolver,
revokeApiKey: revokeApiKeyResolver,
setLabelsForHighlight: setLabelsForHighlightResolver,
moveLabel: moveLabelResolver,
setIntegration: setIntegrationResolver,
deleteIntegration: deleteIntegrationResolver,
optInFeature: optInFeatureResolver,
setRule: setRuleResolver,
deleteRule: deleteRuleResolver,
saveFilter: saveFilterResolver,
deleteFilter: deleteFilterResolver,
moveFilter: moveFilterResolver,
createGroup: createGroupResolver,
recommend: recommendResolver,
joinGroup: joinGroupResolver,
recommendHighlights: recommendHighlightsResolver,
leaveGroup: leaveGroupResolver,
uploadImportFile: uploadImportFileResolver,
markEmailAsItem: markEmailAsItemResolver,
bulkAction: bulkActionResolver,
importFromIntegration: importFromIntegrationResolver,
setFavoriteArticle: setFavoriteArticleResolver,
updateSubscription: updateSubscriptionResolver,
updateFilter: updateFilterResolver,
updateEmail: updateEmailResolver,
saveDiscoverArticle: saveDiscoverArticleResolver,
deleteDiscoverArticle: deleteDiscoverArticleResolver,
moveToFolder: moveToFolderResolver,
updateNewsletterEmail: updateNewsletterEmailResolver,
addDiscoverFeed: addDiscoverFeedResolver,
deleteDiscoverFeed: deleteDiscoverFeedsResolver,
editDiscoverFeed: editDiscoverFeedsResolver,
emptyTrash: emptyTrashResolver,
fetchContent: fetchContentResolver,
exportToIntegration: exportToIntegrationResolver,
replyToEmail: replyToEmailResolver,
refreshHome: refreshHomeResolver,
createFolderPolicy: createFolderPolicyResolver,
updateFolderPolicy: updateFolderPolicyResolver,
deleteFolderPolicy: deleteFolderPolicyResolver,
createPost: createPostResolver,
updatePost: updatePostResolver,
deletePost: deletePostResolver,
},
Query: {
me: getMeUserResolver,
getDiscoverFeedArticles: getDiscoverFeedArticlesResolver,
discoverFeeds: getDiscoverFeedsResolver,
user: getUserResolver,
users: getAllUsersResolver,
validateUsername: validateUsernameResolver,
article: getArticleResolver,
getUserPersonalization: getUserPersonalizationResolver,
articleSavingRequest: articleSavingRequestResolver,
newsletterEmails: newsletterEmailsResolver,
labels: labelsResolver,
search: searchResolver,
subscriptions: subscriptionsResolver,
sendInstallInstructions: sendInstallInstructionsResolver,
webhooks: webhooksResolver,
webhook: webhookResolver,
apiKeys: apiKeysResolver,
typeaheadSearch: typeaheadSearchResolver,
updatesSince: updatesSinceResolver,
integrations: integrationsResolver,
recentSearches: recentSearchesResolver,
rules: rulesResolver,
deviceTokens: deviceTokensResolver,
filters: filtersResolver,
groups: groupsResolver,
recentEmails: recentEmailsResolver,
feeds: feedsResolver,
scanFeeds: scanFeedsResolver,
integration: integrationResolver,
home: homeResolver,
subscription: subscriptionResolver,
hiddenHomeSection: hiddenHomeSectionResolver,
highlights: highlightsResolver,
folderPolicies: folderPoliciesResolver,
posts: postsResolver,
post: postResolver,
},
User: {
async intercomHash(user: User) {
let secret: string
const client = httpContext.get('client') as string
switch (client.toLowerCase()) {
case 'ios':
secret = env.intercom.iosSecret
break
case 'android':
secret = env.intercom.androidSecret
break
default:
secret = env.intercom.webSecret
}
if (!secret) {
return undefined
}
const userIdentifier = user.id
return createHmac('sha256', secret).update(userIdentifier).digest('hex')
},
async features(_: User, __: Record<string, unknown>, ctx: ResolverContext) {
if (!ctx.claims?.uid) {
return undefined
}
return []
},
async featureList(
_: User,
__: Record<string, unknown>,
ctx: ResolverContext
) {
if (!ctx.claims?.uid) {
return undefined
}
return findUserFeatures(ctx.claims.uid)
},
picture: (user: UserEntity) => user.profile.pictureUrl,
// not implemented yet
friendsCount: () => 0,
followersCount: () => 0,
isFullUser: () => true,
viewerIsFollowing: () => false,
sharedArticles: () => [],
sharedArticlesCount: () => 0,
sharedHighlightsCount: () => 0,
sharedNotesCount: () => 0,
},
Article: {
async url(article: LibraryItem, _: unknown, ctx: ResolverContext) {
if (
(article.itemType == PageType.File ||
article.itemType == PageType.Book) &&
ctx.claims &&
article.uploadFileId
) {
const upload = await ctx.dataLoaders.uploadFiles.load(
article.uploadFileId
)
if (!upload || !upload.fileName) {
return undefined
}
const filePath = generateUploadFilePathName(upload.id, upload.fileName)
return generateDownloadSignedUrl(filePath)
}
return article.originalUrl
},
originalArticleUrl(article: LibraryItem) {
return article.originalUrl
},
hasContent(article: LibraryItem) {
return !!article.readableContent
},
publishedAt(article: LibraryItem) {
return validatedDate(article.publishedAt || undefined)
},
image(article: LibraryItem): string | undefined {
if (article.thumbnail) {
return createImageProxyUrl(article.thumbnail, 320, 320)
}
return undefined
},
wordsCount(article: LibraryItem): number | undefined {
if (article.wordCount) return article.wordCount
return article.readableContent
? wordsCount(article.readableContent, true)
: undefined
},
async labels(article: LibraryItem, _: unknown, ctx: ResolverContext) {
if (article.labels) return article.labels
return ctx.dataLoaders.labels.load(article.id)
},
async highlights(article: LibraryItem, _: unknown, ctx: ResolverContext) {
if (article.highlights) return article.highlights
return ctx.dataLoaders.highlights.load(article.id)
},
content: (item: LibraryItem) => item.readableContent,
hash: (item: LibraryItem) => item.textContentHash || '',
isArchived: (item: LibraryItem) => !!item.archivedAt,
uploadFileId: (item: LibraryItem) => item.uploadFile?.id,
pageType: (item: LibraryItem) => item.itemType,
...readingProgressHandlers,
},
Highlight: {
reactions: () => [],
replies: () => [],
type: (highlight: Highlight) => highlight.highlightType,
async user(highlight: Highlight, __: unknown, ctx: ResolverContext) {
return ctx.dataLoaders.users.load(highlight.userId)
},
createdByMe(highlight: Highlight, __: unknown, ctx: ResolverContext) {
return highlight.userId === ctx.claims?.uid
},
libraryItem(highlight: Highlight, _: unknown, ctx: ResolverContext) {
if (highlight.libraryItem) {
return highlight.libraryItem
}
return ctx.dataLoaders.libraryItems.load(highlight.libraryItemId)
},
labels: async (highlight: Highlight, _: unknown, ctx: ResolverContext) => {
return (
highlight.labels || ctx.dataLoaders.highlightLabels.load(highlight.id)
)
},
},
SearchItem: {
async url(item: LibraryItem, _: unknown, ctx: ResolverContext) {
if (
(item.itemType == PageType.File || item.itemType == PageType.Book) &&
ctx.claims &&
item.uploadFileId
) {
const upload = await ctx.dataLoaders.uploadFiles.load(item.uploadFileId)
if (!upload || !upload.fileName) {
return undefined
}
const filePath = generateUploadFilePathName(upload.id, upload.fileName)
return generateDownloadSignedUrl(filePath)
}
return item.originalUrl
},
image(item: LibraryItem) {
return item.thumbnail && createImageProxyUrl(item.thumbnail, 320, 320)
},
originalArticleUrl(item: LibraryItem) {
return item.originalUrl
},
wordsCount(item: LibraryItem) {
if (item.wordCount) return item.wordCount
return item.readableContent
? wordsCount(item.readableContent, true)
: undefined
},
siteIcon(item: LibraryItem) {
if (item.siteIcon && !isBase64Image(item.siteIcon)) {
return createImageProxyUrl(item.siteIcon, 128, 128)
}
return item.siteIcon
},
async labels(item: LibraryItem, _: unknown, ctx: ResolverContext) {
if (item.labels) return item.labels
return ctx.dataLoaders.labels.load(item.id)
},
async recommendations(item: LibraryItem, _: unknown, ctx: ResolverContext) {
if (item.recommendations) return item.recommendations
return ctx.dataLoaders.recommendations.load(item.id)
},
async aiSummary(item: LibraryItem, _: unknown, ctx: ResolverContext) {
if (!ctx.claims) return undefined
return (
await getAISummary({
userId: ctx.claims.uid,
libraryItemId: item.id,
idx: 'latest',
})
)?.summary
},
async highlights(item: LibraryItem, _: unknown, ctx: ResolverContext) {
if (item.highlights) return item.highlights
return ctx.dataLoaders.highlights.load(item.id)
},
async content(item: PartialLibraryItem, _: unknown, ctx: ResolverContext) {
// convert html to the requested format if requested
if (
item.format &&
item.format !== ArticleFormat.Html &&
item.readableContent
) {
let highlights: Highlight[] = []
// load highlights if needed
if (
item.format === ArticleFormat.HighlightedMarkdown &&
item.highlightAnnotations?.length
) {
highlights = await ctx.dataLoaders.highlights.load(item.id)
}
try {
ctx.log.info(`Converting content to: ${item.format}`)
// convert html to the requested format
const converter = contentConverter(item.format)
if (converter) {
return converter(item.readableContent, highlights)
}
} catch (error) {
ctx.log.error('Error converting content', error)
}
}
return item.readableContent
},
isArchived: (item: LibraryItem) => !!item.archivedAt,
pageType: (item: LibraryItem) => item.itemType,
...readingProgressHandlers,
},
Subscription: {
newsletterEmail(subscription: Subscription) {
return subscription.newsletterEmail?.address
},
icon(subscription: Subscription) {
return (
subscription.icon && createImageProxyUrl(subscription.icon, 128, 128)
)
},
folder(subscription: Subscription) {
return (
subscription.folder ||
subscription.newsletterEmail?.folder ||
DEFAULT_SUBSCRIPTION_FOLDER
)
},
// for campability with old clients
lastFetchedAt(subscription: Subscription) {
return subscription.refreshedAt
},
},
NewsletterEmail: {
subscriptionCount(newsletterEmail: NewsletterEmail) {
return newsletterEmail.subscriptions?.length || 0
},
folder(newsletterEmail: NewsletterEmail) {
return newsletterEmail.folder || EXISTING_NEWSLETTER_FOLDER
},
},
HomeSection: {
title: (section: { title?: string; layout: string }) => {
if (section.title) return section.title
switch (section.layout) {
case 'just_added':
return 'Recently Added'
case 'top_picks':
return 'Top Picks'
case 'quick_links':
return 'Quick Links'
case 'hidden':
return 'Hidden Gems'
default:
return ''
}
},
async items(
section: {
items: Array<{
id: string
type: 'library_item' | 'public_item'
score: number
}>
},
_: unknown,
ctx: ResolverContext
) {
const items = section.items
const libraryItemIds = items
.filter((item) => item.type === 'library_item')
.map((item) => item.id)
const libraryItems = (
await ctx.dataLoaders.libraryItems.loadMany(libraryItemIds)
).filter(
(libraryItem) =>
!!libraryItem &&
!isError(libraryItem) &&
[
LibraryItemState.Succeeded,
LibraryItemState.ContentNotFetched,
].includes(libraryItem.state) &&
!libraryItem.seenAt
) as Array<LibraryItem>
const publicItemIds = section.items
.filter((item) => item.type === 'public_item')
.map((item) => item.id)
const publicItems = (
await ctx.dataLoaders.publicItems.loadMany(publicItemIds)
).filter((publicItem) => !isError(publicItem)) as Array<PublicItem>
return items
.map((item) => {
const libraryItem = libraryItems.find(
(libraryItem) => item.id === libraryItem.id
)
if (libraryItem) {
return {
id: libraryItem.id,
title: libraryItem.title,
author: libraryItem.author,
thumbnail: libraryItem.thumbnail,
wordCount: libraryItem.wordCount,
date: libraryItem.savedAt,
url: libraryItem.originalUrl,
canArchive: !libraryItem.archivedAt,
canDelete: !libraryItem.deletedAt,
canSave: false,
canComment: false,
canShare: true,
dir: libraryItem.directionality,
previewContent:
libraryItem.previewContent || libraryItem.description,
subscription: libraryItem.subscription,
siteName: libraryItem.siteName,
siteIcon: libraryItem.siteIcon,
slug: libraryItem.slug,
score: item.score,
canMove: libraryItem.folder === 'following',
}
}
const publicItem = publicItems.find(
(publicItem) => item.id === publicItem.id
)
if (publicItem) {
return {
id: publicItem.id,
title: publicItem.title,
author: publicItem.author,
dir: publicItem.dir,
previewContent: publicItem.previewContent,
thumbnail: publicItem.thumbnail,
wordCount: publicItem.wordCount,
date: publicItem.createdAt,
url: publicItem.url,
canArchive: false,
canDelete: false,
canSave: true,
canComment: true,
canShare: true,
broadcastCount: publicItem.stats.broadcastCount,
likeCount: publicItem.stats.likeCount,
saveCount: publicItem.stats.saveCount,
source: publicItem.source,
score: item.score,
}
}
})
.filter((item) => !!item)
},
},
HomeItem: {
async source(
item: Merge<
HomeItem,
{ subscription?: string; siteName: string; siteIcon?: string }
>,
_: unknown,
ctx: ResolverContext
): Promise<HomeItemSource> {
if (item.source) {
return item.source
}
if (!item.subscription) {
return {
name: item.siteName,
icon: item.siteIcon,
type: HomeItemSourceType.Library,
}
}
const subscription = await ctx.dataLoaders.subscriptions.load(
item.subscription
)
if (!subscription) {
return {
name: item.siteName,
icon: item.siteIcon,
type: HomeItemSourceType.Library,
}
}
return {
id: subscription.id,
url: subscription.url,
name: subscription.name,
icon: subscription.icon,
type: subscription.type as unknown as HomeItemSourceType,
}
},
thumbnail(item: HomeItem) {
return item.thumbnail && createImageProxyUrl(item.thumbnail, 320, 320)
},
},
ArticleSavingRequest: {
status: (item: LibraryItem) => item.state,
url: (item: LibraryItem) => item.originalUrl,
async user(_item: LibraryItem, __: unknown, ctx: ResolverContext) {
if (ctx.claims?.uid) {
return ctx.dataLoaders.users.load(ctx.claims.uid)
}
},
},
Recommendation: {
user: (recommendation: Recommendation) => {
return {
userId: recommendation.recommender.id,
username: recommendation.recommender.profile.username,
profileImageURL: recommendation.recommender.profile.pictureUrl,
name: recommendation.recommender.name,
}
},
name: (recommendation: Recommendation) => recommendation.group.name,
recommendedAt: (recommendation: Recommendation) => recommendation.createdAt,
},
Post: {
async author(post: Post, _: never, ctx: ResolverContext) {
const author = await ctx.dataLoaders.users.load(post.userId)
return author?.name
},
ownedByViewer(post: Post, _: never, ctx: ResolverContext) {
return post.userId === ctx.claims?.uid
},
async libraryItems(
post: { libraryItemIds: string[] },
_: never,
ctx: ResolverContext
) {
const items = await ctx.dataLoaders.libraryItems.loadMany(
post.libraryItemIds
)
return items.filter((item) => !!item)
},
async highlights(
post: { highlightIds: string[] },
_: never,
ctx: ResolverContext
) {
const highlights = await ctx.dataLoaders.highlights.loadMany(
post.highlightIds
)
return highlights.filter((highlight) => !!highlight)
},
},
...resultResolveTypeResolver('Login'),
...resultResolveTypeResolver('LogOut'),
...resultResolveTypeResolver('GoogleSignup'),
...resultResolveTypeResolver('UpdateUser'),
...resultResolveTypeResolver('UpdateUserProfile'),
...resultResolveTypeResolver('Article'),
...resultResolveTypeResolver('Articles'),
...resultResolveTypeResolver('User'),
...resultResolveTypeResolver('Users'),
...resultResolveTypeResolver('SaveArticleReadingProgress'),
...resultResolveTypeResolver('CreateArticle'),
...resultResolveTypeResolver('CreateHighlight'),
...resultResolveTypeResolver('MergeHighlight'),
...resultResolveTypeResolver('UpdateHighlight'),
...resultResolveTypeResolver('DeleteHighlight'),
...resultResolveTypeResolver('UploadFileRequest'),
...resultResolveTypeResolver('SetBookmarkArticle'),
...resultResolveTypeResolver('GetUserPersonalization'),
...resultResolveTypeResolver('SetUserPersonalization'),
...resultResolveTypeResolver('ArticleSavingRequest'),
...resultResolveTypeResolver('CreateArticleSavingRequest'),
...resultResolveTypeResolver('ArchiveLink'),
...resultResolveTypeResolver('CreateNewsletterEmail'),
...resultResolveTypeResolver('NewsletterEmails'),
...resultResolveTypeResolver('DeleteNewsletterEmail'),
...resultResolveTypeResolver('CreateReminder'),
...resultResolveTypeResolver('Reminder'),
...resultResolveTypeResolver('UpdateReminder'),
...resultResolveTypeResolver('DeleteReminder'),
...resultResolveTypeResolver('SetDeviceToken'),
...resultResolveTypeResolver('Save'),
...resultResolveTypeResolver('Labels'),
...resultResolveTypeResolver('CreateLabel'),
...resultResolveTypeResolver('DeleteLabel'),
...resultResolveTypeResolver('SetLabels'),
...resultResolveTypeResolver('GenerateApiKey'),
...resultResolveTypeResolver('Search'),
...resultResolveTypeResolver('Subscriptions'),
...resultResolveTypeResolver('Unsubscribe'),
...resultResolveTypeResolver('UpdateLabel'),
...resultResolveTypeResolver('SendInstallInstructions'),
...resultResolveTypeResolver('UpdatePage'),
...resultResolveTypeResolver('Subscribe'),
...resultResolveTypeResolver('AddPopularRead'),
...resultResolveTypeResolver('SetWebhook'),
...resultResolveTypeResolver('Webhooks'),
...resultResolveTypeResolver('DeleteWebhook'),
...resultResolveTypeResolver('Webhook'),
...resultResolveTypeResolver('ApiKeys'),
...resultResolveTypeResolver('RevokeApiKey'),
...resultResolveTypeResolver('DeleteAccount'),
...resultResolveTypeResolver('TypeaheadSearch'),
...resultResolveTypeResolver('UpdatesSince'),
...resultResolveTypeResolver('MoveLabel'),
...resultResolveTypeResolver('SetIntegration'),
...resultResolveTypeResolver('Integrations'),
...resultResolveTypeResolver('DeleteIntegration'),
...resultResolveTypeResolver('RecentSearches'),
...resultResolveTypeResolver('OptInFeature'),
...resultResolveTypeResolver('SetRule'),
...resultResolveTypeResolver('Rules'),
...resultResolveTypeResolver('DeviceTokens'),
...resultResolveTypeResolver('DeleteRule'),
...resultResolveTypeResolver('SaveFilter'),
...resultResolveTypeResolver('Filters'),
...resultResolveTypeResolver('DeleteFilter'),
...resultResolveTypeResolver('MoveFilter'),
...resultResolveTypeResolver('CreateGroup'),
...resultResolveTypeResolver('Groups'),
...resultResolveTypeResolver('Recommend'),
...resultResolveTypeResolver('JoinGroup'),
...resultResolveTypeResolver('RecommendHighlights'),
...resultResolveTypeResolver('LeaveGroup'),
...resultResolveTypeResolver('UploadImportFile'),
...resultResolveTypeResolver('RecentEmails'),
...resultResolveTypeResolver('MarkEmailAsItem'),
...resultResolveTypeResolver('BulkAction'),
...resultResolveTypeResolver('ImportFromIntegration'),
...resultResolveTypeResolver('SetFavoriteArticle'),
...resultResolveTypeResolver('UpdateSubscription'),
...resultResolveTypeResolver('UpdateEmail'),
...resultResolveTypeResolver('ScanFeeds'),
...resultResolveTypeResolver('MoveToFolder'),
...resultResolveTypeResolver('UpdateNewsletterEmail'),
...resultResolveTypeResolver('EmptyTrash'),
...resultResolveTypeResolver('FetchContent'),
...resultResolveTypeResolver('Integration'),
...resultResolveTypeResolver('ExportToIntegration'),
...resultResolveTypeResolver('ReplyToEmail'),
...resultResolveTypeResolver('Home'),
...resultResolveTypeResolver('Subscription'),
...resultResolveTypeResolver('RefreshHome'),
...resultResolveTypeResolver('HiddenHomeSection'),
...resultResolveTypeResolver('Highlights'),
...resultResolveTypeResolver('FolderPolicies'),
...resultResolveTypeResolver('CreateFolderPolicy'),
...resultResolveTypeResolver('UpdateFolderPolicy'),
...resultResolveTypeResolver('DeleteFolderPolicy'),
...resultResolveTypeResolver('Posts'),
...resultResolveTypeResolver('Post'),
...resultResolveTypeResolver('CreatePost'),
...resultResolveTypeResolver('UpdatePost'),
...resultResolveTypeResolver('DeletePost'),
}