diff --git a/packages/client/src/actions/account.ts b/packages/client/src/actions/account.ts index 8b043fa55..3a2b9650b 100644 --- a/packages/client/src/actions/account.ts +++ b/packages/client/src/actions/account.ts @@ -76,7 +76,7 @@ export function fetchAccount( client: AnyClient>, request: AccountRequest, ): ResultAsync { - return client.query(accountQuery(client.context.accountFragment), { request }); + return client.query(accountQuery([client.context.accountFragment]), { request }); } /** @@ -102,7 +102,7 @@ export function fetchAccounts( client: AnyClient>, request: AccountsRequest = {}, ): ResultAsync | null, UnexpectedError> { - return client.query(accountsQuery(client.context.accountFragment), { request }); + return client.query(accountsQuery([client.context.accountFragment]), { request }); } /** diff --git a/packages/client/src/actions/authentication.ts b/packages/client/src/actions/authentication.ts index 9c6ea179c..08fc62ea9 100644 --- a/packages/client/src/actions/authentication.ts +++ b/packages/client/src/actions/authentication.ts @@ -14,14 +14,16 @@ import { AuthenticatedSessionsQuery, CurrentSessionQuery, LegacyRolloverRefreshMutation, - MeQuery, RefreshMutation, RevokeAuthenticationMutation, SwitchAccountMutation, + meQuery, } from '@lens-protocol/graphql'; import type { ResultAsync } from '@lens-protocol/types'; +import type { Account } from '@lens-protocol/graphql'; import type { AnyClient, SessionClient } from '../clients'; +import type { Context } from '../context'; import type { UnauthenticatedError, UnexpectedError } from '../errors'; /** @@ -151,8 +153,8 @@ export function switchAccount( * @param client - The session client for the authenticated Account. * @returns The details of the authenticated Account. */ -export function fetchMeDetails( - client: SessionClient, +export function fetchMeDetails( + client: SessionClient>, ): ResultAsync { - return client.query(MeQuery, {}); + return client.query(meQuery([client.context.accountFragment]), {}); } diff --git a/packages/client/src/actions/posts.ts b/packages/client/src/actions/posts.ts index 7f854fb27..2ab301689 100644 --- a/packages/client/src/actions/posts.ts +++ b/packages/client/src/actions/posts.ts @@ -1,4 +1,5 @@ import type { + Account, AccountPostReaction, ActionInfo, AnyPost, @@ -6,35 +7,36 @@ import type { Post, PostActionsRequest, PostBookmarksRequest, + PostEdit, + PostEditsRequest, + PostFields, + PostReactionStatus, + PostReactionStatusRequest, PostReactionsRequest, PostReferencesRequest, PostRequest, + PostTagsRequest, PostsRequest, + WhoActedOnPostQueryRequest, + WhoReferencedPostRequest, } from '@lens-protocol/graphql'; import { PostActionsQuery, - PostBookmarksQuery, PostEditsQuery, - PostQuery, PostReactionStatusQuery, PostReactionsQuery, - PostReferencesQuery, PostTagsQuery, - PostsQuery, WhoActedOnPostQuery, WhoReferencedPostQuery, + postBookmarksQuery, + postQuery, + postReferencesQuery, + postsQuery, } from '@lens-protocol/graphql'; import type { ResultAsync } from '@lens-protocol/types'; -import type { PostTagsRequest } from '@lens-protocol/graphql'; -import type { PostReactionStatusRequest } from '@lens-protocol/graphql'; -import type { PostReactionStatus } from '@lens-protocol/graphql'; -import type { WhoReferencedPostRequest } from '@lens-protocol/graphql'; -import type { Account } from '@lens-protocol/graphql'; -import type { WhoActedOnPostQueryRequest } from '@lens-protocol/graphql'; -import type { PostEditsRequest } from '@lens-protocol/graphql'; -import type { PostEdit } from '@lens-protocol/graphql'; import type { AnyClient, SessionClient } from '../clients'; +import type { Context } from '../context'; import type { UnauthenticatedError, UnexpectedError } from '../errors'; /** @@ -53,11 +55,14 @@ import type { UnauthenticatedError, UnexpectedError } from '../errors'; * @param request - The query request. * @returns The Post or `null` if it does not exist. */ -export function fetchPost( - client: AnyClient, +export function fetchPost( + client: AnyClient>, request: PostRequest, -): ResultAsync { - return client.query(PostQuery, { request }); +): ResultAsync | null, UnexpectedError> { + return client.query( + postQuery([client.context.postFieldsFragment, client.context.accountFragment]), + { request }, + ); } /** @@ -82,7 +87,7 @@ export function fetchPosts( client: AnyClient, request: PostsRequest, ): ResultAsync, UnexpectedError> { - return client.query(PostsQuery, { request }); + return client.query(postsQuery, { request }); } /** @@ -138,7 +143,7 @@ export function fetchPostBookmarks( client: SessionClient, request: PostBookmarksRequest = {}, ): ResultAsync, UnexpectedError | UnauthenticatedError> { - return client.query(PostBookmarksQuery, { request }); + return client.query(postBookmarksQuery, { request }); } /** @@ -158,7 +163,7 @@ export function fetchPostReferences( client: AnyClient, request: PostReferencesRequest, ): ResultAsync, UnexpectedError | UnauthenticatedError> { - return client.query(PostReferencesQuery, { request }); + return client.query(postReferencesQuery, { request }); } /** diff --git a/packages/client/src/actions/timeline.ts b/packages/client/src/actions/timeline.ts index b6fa7b130..616202bab 100644 --- a/packages/client/src/actions/timeline.ts +++ b/packages/client/src/actions/timeline.ts @@ -5,7 +5,7 @@ import type { TimelineItem, TimelineRequest, } from '@lens-protocol/graphql'; -import { TimelineHighlightsQuery, TimelineQuery } from '@lens-protocol/graphql'; +import { timelineHighlightsQuery, timelineQuery } from '@lens-protocol/graphql'; import type { ResultAsync } from '@lens-protocol/types'; import type { AnyClient } from '../clients'; @@ -28,7 +28,7 @@ export function fetchTimeline( client: AnyClient, request: TimelineRequest, ): ResultAsync | null, UnexpectedError> { - return client.query(TimelineQuery, { request }); + return client.query(timelineQuery, { request }); } /** @@ -48,5 +48,5 @@ export function fetchTimelineHighlights( client: AnyClient, request: TimelineHighlightsRequest, ): ResultAsync, UnexpectedError> { - return client.query(TimelineHighlightsQuery, { request }); + return client.query(timelineHighlightsQuery, { request }); } diff --git a/packages/client/src/config.ts b/packages/client/src/config.ts index 540bb8121..2dadb38cc 100644 --- a/packages/client/src/config.ts +++ b/packages/client/src/config.ts @@ -1,12 +1,15 @@ import type { EnvironmentConfig } from '@lens-protocol/env'; -import type { Account } from '@lens-protocol/graphql'; +import type { Account, PostFields } from '@lens-protocol/graphql'; import type { FragmentDocumentFor } from '@lens-protocol/graphql'; import type { IStorageProvider } from '@lens-protocol/storage'; /** * The client configuration. */ -export type ClientConfig = { +export type ClientConfig< + TAccount extends Account = Account, + TPostFields extends PostFields = PostFields, +> = { /** * The environment configuration to use (e.g. `mainnet`, `testnet`). */ @@ -40,5 +43,11 @@ export type ClientConfig = { * * @defaultValue {@link AccountFragment} */ - accountFragment?: FragmentDocumentFor; + accountFragment?: FragmentDocumentFor; + /** + * The Post Fragment to use. + * + * @defaultValue {@link PostFragment} + */ + postFieldsFragment?: FragmentDocumentFor; }; diff --git a/packages/client/src/context.ts b/packages/client/src/context.ts index 7c0623a56..538faafc0 100644 --- a/packages/client/src/context.ts +++ b/packages/client/src/context.ts @@ -1,19 +1,23 @@ import type { EnvironmentConfig } from '@lens-protocol/env'; -import { type Account, AccountFragment } from '@lens-protocol/graphql'; -import type { FragmentDocumentFor } from '@lens-protocol/graphql'; +import { type Account, AccountFragment, PostFieldsFragment } from '@lens-protocol/graphql'; +import type { FragmentDocumentFor, PostFields } from '@lens-protocol/graphql'; import { type IStorageProvider, InMemoryStorageProvider } from '@lens-protocol/storage'; import type { ClientConfig } from './config'; /** * @internal */ -export type Context = { +export type Context< + TAccount extends Account = Account, + TPostFields extends PostFields = PostFields, +> = { environment: EnvironmentConfig; cache: boolean; debug: boolean; origin?: string; storage: IStorageProvider; - accountFragment: FragmentDocumentFor; + accountFragment: FragmentDocumentFor; + postFieldsFragment: FragmentDocumentFor; }; /** @@ -36,5 +40,6 @@ export function configureContext( origin: from.origin, storage: from.storage ?? new InMemoryStorageProvider(), accountFragment: from.accountFragment ?? AccountFragment, + postFieldsFragment: from.postFieldsFragment ?? PostFieldsFragment, } as ContextFrom; } diff --git a/packages/client/testing-utils.ts b/packages/client/testing-utils.ts index 0f7bfe8a2..f44d9ed18 100644 --- a/packages/client/testing-utils.ts +++ b/packages/client/testing-utils.ts @@ -4,6 +4,7 @@ import { evmAddress } from '@lens-protocol/types'; import { http, type Account, type Transport, type WalletClient, createWalletClient } from 'viem'; import { privateKeyToAccount } from 'viem/accounts'; +import { FullAccountFragment, FullPostFieldsFragment } from '@lens-protocol/graphql'; import { GraphQLErrorCode, PublicClient, testnet as apiEnv } from './src'; const pk = privateKeyToAccount(import.meta.env.PRIVATE_KEY); @@ -16,6 +17,8 @@ export function createPublicClient() { return PublicClient.create({ environment: apiEnv, origin: 'http://example.com', + accountFragment: FullAccountFragment, + postFieldsFragment: FullPostFieldsFragment, }); } diff --git a/packages/graphql/src/accounts/account.ts b/packages/graphql/src/accounts/account.ts index 1019bd162..d09faa95c 100644 --- a/packages/graphql/src/accounts/account.ts +++ b/packages/graphql/src/accounts/account.ts @@ -1,7 +1,5 @@ import type { FragmentOf } from 'gql.tada'; -import type { Paginated } from '../common'; import { - type Account, AccountAvailableFragment, AccountBlockedFragment, AccountFragment, @@ -10,34 +8,20 @@ import { SponsoredTransactionRequestFragment, TransactionWillFailFragment, } from '../fragments'; -import { - type FragmentDocumentFor, - type RequestOf, - type RequestTypeOf, - type StandardDocumentNode, - graphql, -} from '../graphql'; - -export type AccountRequest = RequestTypeOf<'AccountRequest'>; -export function accountQuery( - fragment: FragmentDocumentFor, -): StandardDocumentNode { - return graphql( - `query Account($request: AccountRequest!) { +import { type RequestOf, dynamic, graphql } from '../graphql'; + +export const accountQuery = dynamic( + `query Account($request: AccountRequest!) { value: account(request: $request) { ...Account } }`, - [fragment], - ) as StandardDocumentNode; -} - -export type AccountsRequest = RequestTypeOf<'AccountsRequest'>; -export function accountsQuery( - fragment: FragmentDocumentFor, -): StandardDocumentNode, AccountsRequest> { - return graphql( - `query Accounts($request: AccountsRequest!) { + [], +); +export type AccountRequest = RequestOf; + +export const accountsQuery = dynamic( + `query Accounts($request: AccountsRequest!) { value: accounts(request: $request) { __typename items { @@ -48,9 +32,9 @@ export function accountsQuery( } } }`, - [fragment, PaginatedResultInfoFragment], - ) as StandardDocumentNode; -} + [PaginatedResultInfoFragment], +); +export type AccountsRequest = RequestOf; export const AccountsBulkQuery = graphql( `query AccountsBulk($request: AccountsBulkRequest!) { diff --git a/packages/graphql/src/authentication.ts b/packages/graphql/src/authentication.ts index 32a3ceb8b..7ea7be5e0 100644 --- a/packages/graphql/src/authentication.ts +++ b/packages/graphql/src/authentication.ts @@ -1,6 +1,18 @@ import type { FragmentOf } from 'gql.tada'; -import { AccountAvailableFragment, PaginatedResultInfoFragment } from './fragments'; -import { type RequestOf, graphql } from './graphql'; +import { + type Account, + AccountAvailableFragment, + type FullAccount, + PaginatedResultInfoFragment, +} from './fragments'; +import { + type FragmentDocumentFor, + type PartialFragmentOf, + type RequestOf, + dynamic, + graphql, + partial, +} from './graphql'; const AuthenticationChallengeFragment = graphql( `fragment AuthenticationChallenge on AuthenticationChallenge { @@ -198,7 +210,7 @@ export const SwitchAccountMutation = graphql( ); export type SwitchAccountRequest = RequestOf; -const MeResultFragment = graphql( +const MeResultFragment = partial( `fragment MeResult on MeResult { appLoggedIn isSignless @@ -215,9 +227,16 @@ const MeResultFragment = graphql( }`, [AccountAvailableFragment], ); +export type MeDetails = PartialFragmentOf< + typeof MeResultFragment, + [FragmentDocumentFor] +>; + +type test = MeDetails; + export type MeResult = FragmentOf; -export const MeQuery = graphql( +export const meQuery = dynamic( `query Me { value: me { ...MeResult diff --git a/packages/graphql/src/fragments/account.ts b/packages/graphql/src/fragments/account.ts index 112ed209d..1bc188b5c 100644 --- a/packages/graphql/src/fragments/account.ts +++ b/packages/graphql/src/fragments/account.ts @@ -1,5 +1,5 @@ import type { FragmentOf } from 'gql.tada'; -import { graphql } from '../graphql'; +import { type FragmentDocumentFor, type PartialFragmentOf, graphql, partial } from '../graphql'; import { OperationValidationOutcomeFragment } from './common'; import { MetadataAttributeFragment } from './metadata'; import { UsernameFragment } from './username'; @@ -49,14 +49,36 @@ export const AccountFragment = graphql( __typename address }`, - [], ); export type Account = FragmentOf; +/** + * @deprecated Define your own AccountFragment instead using {@link graphql} and {@link FragmentOf}. + * + * @example + * ```ts + * const AccountFragment = graphql( + * `fragment Account on Account { + * __typename + * address + * owner + * username { + * ...Username + * } + * }`, + * [UsernameFragment], + * ); + * + * type Account = FragmentOf; + * ``` + */ export const FullAccountFragment = graphql( `fragment Account on Account { __typename address + owner + score + createdAt username{ ...Username } @@ -69,7 +91,10 @@ export const FullAccountFragment = graphql( }`, [AccountMetadataFragment, LoggedInAccountOperationsFragment, UsernameFragment], ); -export type FullAccount = FragmentOf; +/** + * @deprecated Define your own FullAccountFragment instead using {@link graphql} and {@link FragmentOf}. + */ +export type FullAccount = FragmentOf; const AccountManagerPermissionsFragment = graphql( `fragment AccountManagerPermissions on AccountManagerPermissions { @@ -95,7 +120,7 @@ export const AccountManagerFragment = graphql( ); export type AccountManager = FragmentOf; -const AccountManagedFragment = graphql( +const AccountManagedFragment = partial( `fragment AccountManaged on AccountManaged { __typename addedAt @@ -106,27 +131,43 @@ const AccountManagedFragment = graphql( ...AccountManagerPermissions } }`, - [AccountManagerPermissionsFragment, AccountFragment], + [AccountManagerPermissionsFragment], +); +export type AccountManaged = PartialFragmentOf< + typeof AccountManagedFragment, + [FragmentDocumentFor] +>; + +const AccountOwnedFragment = partial( + `fragment AccountOwned on AccountOwned { + __typename + addedAt + account { + ...Account + } + }`, + [], ); -export type AccountManaged = FragmentOf; +export type AccountOwned = PartialFragmentOf< + typeof AccountOwnedFragment, + [FragmentDocumentFor] +>; -export const AccountAvailableFragment = graphql( +export const AccountAvailableFragment = partial( `fragment AccountAvailable on AccountAvailable { __typename ... on AccountManaged { ...AccountManaged } ... on AccountOwned { - __typename - addedAt - account { - ...Account - } + ...AccountOwned } }`, - [AccountFragment, AccountManagedFragment], + [AccountManagedFragment, AccountOwnedFragment], ); -export type AccountAvailable = FragmentOf; +export type AccountAvailable = + | AccountManaged + | AccountOwned; export const AccountBlockedFragment = graphql( `fragment AccountBlocked on AccountBlocked { diff --git a/packages/graphql/src/fragments/post.ts b/packages/graphql/src/fragments/post.ts index ef08f23e5..958e96ac3 100644 --- a/packages/graphql/src/fragments/post.ts +++ b/packages/graphql/src/fragments/post.ts @@ -1,6 +1,6 @@ import type { FragmentOf } from 'gql.tada'; -import { graphql } from '../graphql'; -import { AccountFragment } from './account'; +import { type FragmentDocumentFor, type PartialFragmentOf, graphql, partial } from '../graphql'; +import { type Account, AccountFragment } from './account'; import { ActionInputInfoFragment, AmountFragment, @@ -15,6 +15,7 @@ import { EmbedMetadataFragment, EventMetadataFragment, ImageMetadataFragment, + LinkMetadataFragment, LivestreamMetadataFragment, MintMetadataFragment, SpaceMetadataFragment, @@ -56,9 +57,7 @@ export const SimpleCollectActionSettingsFragment = graphql( }`, [AmountFragment, NetworkAddressFragment, RecipientDataOutputFragment], ); -export type SimpleCollectActionSettingsFragment = FragmentOf< - typeof SimpleCollectActionSettingsFragment ->; +export type SimpleCollectActionSettings = FragmentOf; export const UnknownActionSettingsFragment = graphql( `fragment UnknownActionSettings on UnknownActionSettings { @@ -75,6 +74,7 @@ export const UnknownActionSettingsFragment = graphql( ); export type UnknownActionSettings = FragmentOf; +export type PostAction = SimpleCollectActionSettings | UnknownActionSettings; export const PostActionFragment = graphql( `fragment PostAction on PostAction { ... on SimpleCollectActionSettings { @@ -86,9 +86,11 @@ export const PostActionFragment = graphql( }`, [SimpleCollectActionSettingsFragment, UnknownActionSettingsFragment], ); -export type PostAction = FragmentOf; -export const PostMetadataFragment = graphql( +/** + * @deprecated Define your own PostMetadataFragment instead using {@link graphql} and {@link FragmentOf}. + */ +export const FullPostMetadataFragment = graphql( `fragment PostMetadata on PostMetadata { ... on ArticleMetadata { ...ArticleMetadata @@ -114,6 +116,9 @@ export const PostMetadataFragment = graphql( ... on EventMetadata { ...EventMetadata } + ... on LinkMetadata { + ...LinkMetadata + } ... on LivestreamMetadata { ...LivestreamMetadata } @@ -136,21 +141,23 @@ export const PostMetadataFragment = graphql( [ ArticleMetadataFragment, AudioMetadataFragment, - TextOnlyMetadataFragment, CheckingInMetadataFragment, - ImageMetadataFragment, - VideoMetadataFragment, EmbedMetadataFragment, EventMetadataFragment, + ImageMetadataFragment, LivestreamMetadataFragment, MintMetadataFragment, SpaceMetadataFragment, StoryMetadataFragment, + TextOnlyMetadataFragment, ThreeDMetadataFragment, + LinkMetadataFragment, TransactionMetadataFragment, + VideoMetadataFragment, ], ); -export type PostMetadata = FragmentOf; + +export type FullPostMetadata = FragmentOf; export const LoggedInPostOperationsFragment = graphql( `fragment LoggedInPostOperations on LoggedInPostOperations { @@ -190,128 +197,185 @@ export const LoggedInPostOperationsFragment = graphql( ); export type LoggedInPostOperations = FragmentOf; -export const ReferencedPostFragment = graphql( - `fragment ReferencedPost on Post { +export const PostStatsFragment = graphql( + `fragment PostStats on PostStats { + __typename + bookmarks + collects + comments + quotes + reactions + reposts + }`, +); +export type PostStats = FragmentOf; + +export const PostFieldsFragment = graphql( + `fragment PostFields on Post { __typename id - author { - ...Account - } - feed { - ...Feed - } timestamp + slug + }`, +); +export type PostFields = FragmentOf; + +/** + * @deprecated Define your own PostFieldsFragment instead using {@link graphql} and {@link FragmentOf}. + * + * @example + * ```ts + * const PostFieldsFragment = graphql( + * `fragment PostFields on Post { + * __typename + * id + * timestamp + * metadata { + * ...PostMetadata + * } + * }`, + * [], + * ); + * + * type PostFields = FragmentOf; + * ``` + */ +export const FullPostFieldsFragment = graphql( + `fragment PostFields on Post { + __typename + id + timestamp + slug app { ...App } + feed { + ...Feed + } metadata { ...PostMetadata } - actions { - ...PostAction - } operations { ...LoggedInPostOperations } - } - `, + stats { + ...PostStats + } + }`, [ - AccountFragment, AppFragment, FeedFragment, - PostMetadataFragment, - PostActionFragment, LoggedInPostOperationsFragment, + FullPostMetadataFragment, + PostStatsFragment, ], ); +/** + * @deprecated Define your own FullPostFieldsFragment instead using {@link graphql} and {@link FragmentOf}. + */ +export type FullPostFields = FragmentOf; -export const PostStatsFragment = graphql( - `fragment PostStats on PostStats { - __typename - bookmarks - collects - comments - quotes - reactions - reposts +export const ReferencedPostFragment = partial( + `fragment ReferencedPost on Post { + ${'...PostFields'} + + author { + ...Account + } }`, ); -export type PostStats = FragmentOf; -export const PostFragment = graphql( +export type ReferencedPost< + TPostFields extends PostFields = PostFields, + TAccount extends Account = Account, +> = PartialFragmentOf< + typeof ReferencedPostFragment, + [FragmentDocumentFor, FragmentDocumentFor] +>; + +export const PostFragment = partial( `fragment Post on Post { - __typename - id + ${'...PostFields'} + author { ...Account } - feed { - ...Feed - } - timestamp - slug - stats { - ...PostStats - } - app { - ...App - } - metadata { - ...PostMetadata - } root { - ...ReferencedPost + ${'...ReferencedPost'} } quoteOf { - ...ReferencedPost + ${'...ReferencedPost'} } commentOn { - ...ReferencedPost + ${'...ReferencedPost'} } - actions { - ...PostAction - } - operations { - ...LoggedInPostOperations - } - } - `, - [ - AccountFragment, - AppFragment, - FeedFragment, - PostMetadataFragment, - PostActionFragment, - PostStatsFragment, - ReferencedPostFragment, - LoggedInPostOperationsFragment, - ], + }`, + [ReferencedPostFragment], ); -export type Post = FragmentOf; -// operations: LoggedInPostOperations -export const RepostFragment = graphql( +export type Post< + TPostFields extends PostFields = PostFields, + TAccount extends Account = Account, +> = PartialFragmentOf< + typeof PostFragment, + [FragmentDocumentFor, FragmentDocumentFor] +>; + +export const RepostFragment = partial( `fragment Repost on Repost { __typename id + author { + ...Account + } + isDeleted + timestamp + app { + ...App + } + repostOf { + ${'...PostFields'} + + author { + ...Account + } + root { + ${'...ReferencedPost'} + } + quoteOf { + ${'...ReferencedPost'} + } + commentOn { + ${'...ReferencedPost'} + } + } }`, - [], + [AppFragment, PostFragment], ); -export type Repost = FragmentOf; +export type Repost< + TPostFields extends PostFields = PostFields, + TAccount extends Account = Account, +> = PartialFragmentOf< + typeof RepostFragment, + [FragmentDocumentFor, FragmentDocumentFor] +>; -export const AnyPostFragment = graphql( +export const AnyPostFragment = partial( `fragment AnyPost on AnyPost { ...on Post { - ...Post + ${'...Post'} } ...on Repost { - ...Repost + ${'...Repost'} } }`, [PostFragment, RepostFragment], ); -export type AnyPost = FragmentOf; +export type AnyPost< + TPostFields extends PostFields = PostFields, + TAccount extends Account = Account, +> = Post | Repost; export const KnownActionFragment = graphql( `fragment KnownAction on KnownAction { diff --git a/packages/graphql/src/graphql.ts b/packages/graphql/src/graphql.ts index 00a66a240..f7b237be5 100644 --- a/packages/graphql/src/graphql.ts +++ b/packages/graphql/src/graphql.ts @@ -28,7 +28,6 @@ import { type TadaDocumentNode, initGraphQLTada, } from 'gql.tada'; -import type { StandardData } from './common'; import type { AccessConditionComparison, AccountReportReason, @@ -82,6 +81,9 @@ import type { } from './enums'; import type { introspection } from './graphql-env'; +/** + * A function that may be used to create documents typed using the Lens API GraphQL schema. + */ export const graphql = initGraphQLTada<{ disableMasking: true; introspection: introspection; @@ -166,16 +168,6 @@ export const graphql = initGraphQLTada<{ export type { FragmentOf, TadaDocumentNode }; -/** - * @internal - */ -export type RequestOf = Document extends DocumentDecoration< - unknown, - { request: infer Request } -> - ? Request - : never; - /** * @internal */ @@ -186,87 +178,128 @@ type GetDocumentNode< Fragments extends FragmentShape[] = FragmentShape[], > = ReturnType>; -export type AnyGqlNode = { __typename: TTypename }; - -export type AnyVariables = Record; +/** + * @internal + */ +export type AnySelectionSet = Record; /** * @internal */ -export type FragmentDocumentFor = TGqlNode extends AnyGqlNode< - infer TTypename -> - ? TadaDocumentNode< - TGqlNode, - AnyVariables, - { - fragment: TTypename; - on: TTypename; - masked: false; - } - > - : never; +export type AnyVariables = Record; + +type TypedSelectionSet = { __typename: TTypename }; /** * @internal */ -export type RequestTypeOf = RequestOf< - GetDocumentNode<`query Named($request: ${Name}) {}`, FragmentShape[]> +export type FragmentDocumentFor< + TGqlNode extends AnySelectionSet, + TTypename extends string = TGqlNode extends TypedSelectionSet + ? TTypename + : never, + TFragmentName extends string = TTypename, +> = TadaDocumentNode< + TGqlNode, + AnyVariables, + { + fragment: TFragmentName; + on: TTypename; + masked: false; + } >; -// biome-ignore lint/suspicious/noExplicitAny: simplifies necessary type assertions -export type StandardDocumentNode = TadaDocumentNode< - StandardData, - { request: Request } ->; +/** + * Asserts that the node is of a specific type in a union. + * + * ```ts + * type A = { __typename: 'A', a: string }; + * type B = { __typename: 'B', b: string }; + * + * const node: A | B = { __typename: 'A', a: 'a' }; + * + * assertTypename(node, 'A'); + * + * console.log(node.a); // OK + * ``` + * + * @param node - The node to assert the typename of + * @param typename - The expected typename + */ +export function assertTypename( + node: TypedSelectionSet, + typename: Typename, +): asserts node is TypedSelectionSet { + if (node.__typename !== typename) { + throw new InvariantError( + `Expected node to have typename "${typename}", but got "${node.__typename}"`, + ); + } +} type FragmentDocumentFrom< In extends string, - Fragments extends FragmentShape[], - Document extends GetDocumentNode = GetDocumentNode, -> = Document extends FragmentShape ? Document : never; - -type FragmentDocumentForEach = { - [K in keyof Nodes]: FragmentDocumentFor; -}; + Fragments extends FragmentShape[] = FragmentShape[], +> = GetDocumentNode extends FragmentShape ? GetDocumentNode : never; /** * @internal */ -export type DynamicFragmentDocument< - In extends string, - StaticNodes extends AnyGqlNode[], -> = FragmentDocumentFrom> & { +export type PartialFragment< + In extends string = string, + StaticFragments extends FragmentShape[] = [], +> = FragmentDocumentFrom & { __phantom: In; }; /** * @internal */ -export function fragment( +export function partial( input: In, - staticFragments: FragmentDocumentForEach = [] as FragmentDocumentForEach, -): DynamicFragmentDocument { - return graphql(input, staticFragments) as DynamicFragmentDocument; + staticFragments?: StaticFragments, +): PartialFragment { + return graphql(input, staticFragments) as PartialFragment; } +// https://github.com/0no-co/GraphQLSP/blob/6d9ce44d46dc6adbaf387ad5c96e4125570c3a94/packages/graphqlsp/src/ast/checks.ts#L26-L27 +partial.scalar = true; +partial.persisted = true; + +export type PartialFragmentOf< + Fragment extends FragmentShape, + DynamicFragments extends FragmentShape[], +> = Fragment extends PartialFragment + ? FragmentOf> + : never; + +export type DynamicDocument = < + DynamicFragments extends FragmentShape[], +>( + dynamicFragments: DynamicFragments, +) => GetDocumentNode; + /** * @internal */ -export type DynamicFragmentOf< - Document, - DynamicNodes extends AnyGqlNode[], -> = Document extends DynamicFragmentDocument - ? FragmentOf>> - : never; - -export function assertTypename( - node: AnyGqlNode, - typename: Typename, -): asserts node is AnyGqlNode { - if (node.__typename !== typename) { - throw new InvariantError( - `Expected node to have typename "${typename}", but got "${node.__typename}"`, - ); - } +export function dynamic( + input: In, + // biome-ignore lint/suspicious/noExplicitAny: simplicity + staticFragments: StaticFragments = [] as any, +): DynamicDocument { + return (dynamicFragments: DynamicFragments) => + graphql(input, staticFragments.concat(dynamicFragments) as FragmentShape[]); } + +// https://github.com/0no-co/GraphQLSP/blob/6d9ce44d46dc6adbaf387ad5c96e4125570c3a94/packages/graphqlsp/src/ast/checks.ts#L26-L27 +dynamic.scalar = true; +dynamic.persisted = true; + +/** + * @internal + */ +export type RequestOf = Document extends DynamicDocument + ? RequestOf> + : Document extends DocumentDecoration + ? Request + : never; diff --git a/packages/graphql/src/post.ts b/packages/graphql/src/post.ts index fced0f8a4..2289b2bb2 100644 --- a/packages/graphql/src/post.ts +++ b/packages/graphql/src/post.ts @@ -4,13 +4,13 @@ import { AccountPostReactionFragment, ActionInfoFragment, AnyPostFragment, + FullPostMetadataFragment, PaginatedResultInfoFragment, - PostMetadataFragment, SelfFundedTransactionRequestFragment, SponsoredTransactionRequestFragment, TransactionWillFailFragment, } from './fragments'; -import { type RequestOf, graphql } from './graphql'; +import { type RequestOf, dynamic, graphql } from './graphql'; const PostResponseFragment = graphql( `fragment PostResponse on PostResponse { @@ -74,21 +74,21 @@ export const EditPostMutation = graphql( ); export type EditPostRequest = RequestOf; -export const PostQuery = graphql( +export const postQuery = dynamic( `query Post($request: PostRequest!) { value: post(request: $request) { - ...AnyPost + ${'...AnyPost'} } }`, [AnyPostFragment], ); -export type PostRequest = RequestOf; +export type PostRequest = RequestOf; -export const PostsQuery = graphql( +export const postsQuery = dynamic( `query Posts($request: PostsRequest!) { value: posts(request: $request) { items { - ...AnyPost + ${'...AnyPost'} } pageInfo { ...PaginatedResultInfo @@ -97,7 +97,7 @@ export const PostsQuery = graphql( }`, [AnyPostFragment, PaginatedResultInfoFragment], ); -export type PostsRequest = RequestOf; +export type PostsRequest = RequestOf; export const PostActionsQuery = graphql( `query PostActions($request: PostActionsRequest!) { @@ -129,11 +129,11 @@ export const PostReactionsQuery = graphql( ); export type PostReactionsRequest = RequestOf; -export const PostBookmarksQuery = graphql( +export const postBookmarksQuery = dynamic( `query PostBookmarks($request: PostBookmarksRequest!) { value: postBookmarks(request: $request) { items { - ...AnyPost + ${'...AnyPost'} }, pageInfo { ...PaginatedResultInfo @@ -142,13 +142,13 @@ export const PostBookmarksQuery = graphql( }`, [AnyPostFragment, PaginatedResultInfoFragment], ); -export type PostBookmarksRequest = RequestOf; +export type PostBookmarksRequest = RequestOf; -export const PostReferencesQuery = graphql( +export const postReferencesQuery = dynamic( `query PostReferences($request: PostReferencesRequest!) { value: postReferences(request: $request) { items { - ...AnyPost + ${'...AnyPost'} }, pageInfo { ...PaginatedResultInfo @@ -157,7 +157,7 @@ export const PostReferencesQuery = graphql( }`, [AnyPostFragment, PaginatedResultInfoFragment], ); -export type PostReferencesRequest = RequestOf; +export type PostReferencesRequest = RequestOf; const AddReactionResultFragment = graphql( `fragment AddReactionResult on AddReactionResult { @@ -358,7 +358,7 @@ export const PostEditFragment = graphql( } timestamp }`, - [PostMetadataFragment], + [FullPostMetadataFragment], ); export type PostEdit = FragmentOf; diff --git a/packages/graphql/src/timeline.ts b/packages/graphql/src/timeline.ts index 3d5ac2815..8321c7448 100644 --- a/packages/graphql/src/timeline.ts +++ b/packages/graphql/src/timeline.ts @@ -1,53 +1,53 @@ import type { FragmentOf } from 'gql.tada'; -import { PaginatedResultInfoFragment, PostFragment } from './fragments'; -import { type RequestOf, graphql } from './graphql'; +import { PaginatedResultInfoFragment } from './fragments'; +import { type RequestOf, dynamic, partial } from './graphql'; -const TimelineItemFragment = graphql( +const timelineItemFragment = partial( `fragment TimelineItem on TimelineItem { __typename id primary { - ...Post + ${'...Post'} } comments { - ...Post + ${'...Post'} } reposts { - ...Post + ${'...Post'} } }`, - [PostFragment], + [], ); -export type TimelineItem = FragmentOf; +export type TimelineItem = FragmentOf; -export const TimelineQuery = graphql( +export const timelineQuery = dynamic( `query Timeline($request: TimelineRequest!) { value: timeline(request: $request) { __typename items { - ...TimelineItem + ${'...TimelineItem'} } pageInfo { ...PaginatedResultInfo } } }`, - [TimelineItemFragment, PaginatedResultInfoFragment], + [timelineItemFragment, PaginatedResultInfoFragment], ); -export type TimelineRequest = RequestOf; +export type TimelineRequest = RequestOf; -export const TimelineHighlightsQuery = graphql( +export const timelineHighlightsQuery = dynamic( `query TimelineHighlights($request: TimelineHighlightsRequest!) { value: timelineHighlights(request: $request) { __typename items { - ...Post + ${'...Post'} } pageInfo { ...PaginatedResultInfo } } }`, - [PostFragment, PaginatedResultInfoFragment], + [PaginatedResultInfoFragment], ); -export type TimelineHighlightsRequest = RequestOf; +export type TimelineHighlightsRequest = RequestOf;