From c864a8b138bc04c0b4e06aac8bc14eab338fb72e Mon Sep 17 00:00:00 2001 From: Cesare Naldi Date: Thu, 12 Dec 2024 14:40:52 +0100 Subject: [PATCH] feat: injects AccountFragment into notifications query --- packages/client/src/actions/account.test.ts | 2 +- packages/client/src/actions/notifications.ts | 15 ++- packages/graphql/src/accounts/account.ts | 28 ++-- packages/graphql/src/fragments/post.ts | 8 +- packages/graphql/src/graphql.ts | 78 ++++++----- packages/graphql/src/notifications.ts | 135 +++++++++++-------- packages/react/src/provider.tsx | 7 +- packages/types/src/helpers/assertions.ts | 10 +- 8 files changed, 170 insertions(+), 113 deletions(-) diff --git a/packages/client/src/actions/account.test.ts b/packages/client/src/actions/account.test.ts index c1a1b1e89..3ed73696d 100644 --- a/packages/client/src/actions/account.test.ts +++ b/packages/client/src/actions/account.test.ts @@ -18,7 +18,7 @@ describe('Given the Account query actions', () => { const result = await fetchAccount(client, { address: evmAddress(import.meta.env.TEST_ACCOUNT), }); - console.log(result); + assertOk(result); }); }); diff --git a/packages/client/src/actions/notifications.ts b/packages/client/src/actions/notifications.ts index 4ec4c0822..0e013969c 100644 --- a/packages/client/src/actions/notifications.ts +++ b/packages/client/src/actions/notifications.ts @@ -1,8 +1,10 @@ import type { Notification, NotificationsRequest } from '@lens-protocol/graphql'; -import { NotificationsQuery } from '@lens-protocol/graphql'; +import { notificationsQuery } from '@lens-protocol/graphql'; import type { ResultAsync } from '@lens-protocol/types'; +import type { Account } from '@lens-protocol/graphql'; import type { SessionClient } from '../clients'; +import type { Context } from '../context'; import type { UnexpectedError } from '../errors'; import type { Paginated } from '../types'; @@ -17,9 +19,10 @@ import type { Paginated } from '../types'; * @param request - The query request. * @returns Paginated notifications. */ -export function fetchNotifications( - client: SessionClient, - request: NotificationsRequest, -): ResultAsync, UnexpectedError> { - return client.query(NotificationsQuery, { request }); +export function fetchNotifications( + client: SessionClient>, + request: NotificationsRequest = {}, +): ResultAsync>, UnexpectedError> { + const document = notificationsQuery(client.context.accountFragment); + return client.query(document, { request }); } diff --git a/packages/graphql/src/accounts/account.ts b/packages/graphql/src/accounts/account.ts index 16f784451..9e43a33a6 100644 --- a/packages/graphql/src/accounts/account.ts +++ b/packages/graphql/src/accounts/account.ts @@ -1,5 +1,6 @@ import type { FragmentOf } from 'gql.tada'; import { + type Account, AccountAvailable, AccountBlocked, AccountFragment, @@ -8,17 +9,28 @@ import { SponsoredTransactionRequest, TransactionWillFail, } from '../fragments'; -import { type RequestOf, type RequestOfFactory, factory, graphql } from '../graphql'; - -export const accountQuery = factory( - `query Account($request: AccountRequest!) { +import { + type FragmentDocumentFor, + type RequestFrom, + type RequestOf, + type StandardDocumentNode, + graphql, +} from '../graphql'; + +const AccountQueryString = ` + query Account($request: AccountRequest!) { value: account(request: $request) { ...Account } - }`, -); - -export type AccountRequest = RequestOfFactory; + } +`; +export type AccountRequest = RequestFrom; + +export function accountQuery( + fragment: FragmentDocumentFor, +): StandardDocumentNode { + return graphql(AccountQueryString, [fragment]) as StandardDocumentNode; +} export const SearchAccountsQuery = graphql( `query SearchAccounts($request: AccountSearchRequest!) { diff --git a/packages/graphql/src/fragments/post.ts b/packages/graphql/src/fragments/post.ts index 9c5c3690c..ea918f90e 100644 --- a/packages/graphql/src/fragments/post.ts +++ b/packages/graphql/src/fragments/post.ts @@ -132,9 +132,8 @@ export const ReferencedPost = graphql( operations { ...LoggedInPostOperations } - } - `, - [AccountFragment, App, Feed, PostMetadata, PostAction, LoggedInPostOperations], + }`, + [App, Feed, PostMetadata, PostAction, LoggedInPostOperations], ); export const NestedPost = graphql( @@ -184,9 +183,8 @@ export const PostFragment = graphql( } } `, - [AccountFragment, App, Feed, PostMetadata, PostAction, NestedPost, LoggedInPostOperations], + [App, Feed, PostMetadata, PostAction, NestedPost, LoggedInPostOperations], ); -export type PostFragment = Post; export type Post = FragmentOf; diff --git a/packages/graphql/src/graphql.ts b/packages/graphql/src/graphql.ts index 9f8b485c5..33cd497d0 100644 --- a/packages/graphql/src/graphql.ts +++ b/packages/graphql/src/graphql.ts @@ -23,11 +23,12 @@ import type { } from '@lens-protocol/types'; import { type DocumentDecoration, + type FragmentOf, type TadaDocumentNode, - type VariablesOf, initGraphQLTada, } from 'gql.tada'; import type { PageSize } from './enums'; +import type { PaginatedResultInfo } from './fragments'; import type { introspection } from './graphql-env'; export const graphql = initGraphQLTada<{ @@ -76,15 +77,11 @@ export type RequestOf = Document extends DocumentDecoration< /** * @internal */ -export type UnknownFragmentShape = NonNullable[1]>[number]; +export type FragmentShape = NonNullable[1]>[number]; -/** - * @internal - */ - -export type TypedDocumentFrom< - In extends string, - Fragments extends UnknownFragmentShape[], +type GetDocumentNode< + In extends string = string, + Fragments extends FragmentShape[] = FragmentShape[], > = ReturnType>; export type AnyGqlNode = { __typename: TTypename }; @@ -113,36 +110,55 @@ export type FragmentDocumentFor = TGqlNode extends */ export type StandardData = { value: T }; -export type Factory = < - Nodes extends AnyGqlNode[], - Fragments extends { [K in keyof Nodes]: FragmentDocumentFor }, - Document extends TypedDocumentFrom, ->( - ...fragments: Fragments -) => TadaDocumentNode>, VariablesOf>; +export type RequestFrom = RequestOf>; + +// biome-ignore lint/suspicious/noExplicitAny: simplifies necessary type assertions +export type StandardDocumentNode = TadaDocumentNode< + StandardData, + { request: Request } +>; + +type FragmentDocumentFrom< + In extends string, + Fragments extends FragmentShape[], + Document extends GetDocumentNode = GetDocumentNode, +> = Document extends FragmentShape ? Document : never; -type First = T extends [infer First, ...unknown[]] ? First : never; +type FragmentDocumentForEach = { + [K in keyof Nodes]: FragmentDocumentFor; +}; /** * @internal */ -export function factory(operation: In): Factory { - return < - Nodes extends AnyGqlNode[], - Fragments extends { [K in keyof Nodes]: FragmentDocumentFor }, - Document extends TypedDocumentFrom, - Result extends StandardData>, - Variables extends VariablesOf, - >( - ...fragments: Fragments - ): TadaDocumentNode => { - return graphql(operation, fragments) as TadaDocumentNode; - }; +export type DynamicFragmentDocument< + In extends string, + StaticNodes extends AnyGqlNode[], +> = FragmentDocumentFrom> & { + __phantom: In; +}; + +/** + * @internal + */ +export function fragment( + input: In, + staticFragments: FragmentDocumentForEach = [] as FragmentDocumentForEach, +): DynamicFragmentDocument { + return graphql(input, staticFragments) as DynamicFragmentDocument; } /** * @internal */ -export type RequestOfFactory> = F extends Factory - ? RequestOf>> +export type DynamicFragmentOf< + Document, + DynamicNodes extends AnyGqlNode[], +> = Document extends DynamicFragmentDocument + ? FragmentOf>> : never; + +export type Paginated = { + items: readonly T[]; + pageInfo: PaginatedResultInfo; +}; diff --git a/packages/graphql/src/notifications.ts b/packages/graphql/src/notifications.ts index a0b248628..ee9f75664 100644 --- a/packages/graphql/src/notifications.ts +++ b/packages/graphql/src/notifications.ts @@ -1,8 +1,17 @@ +import type { Prettify } from '@lens-protocol/types'; import type { FragmentOf } from 'gql.tada'; -import { AccountFragment, PaginatedResultInfo, PostFragment } from './fragments'; -import { type RequestOf, graphql } from './graphql'; +import { type Account, PaginatedResultInfoFragment, PostFragment } from './fragments'; +import { + type DynamicFragmentOf, + type FragmentDocumentFor, + type Paginated, + type RequestFrom, + type StandardDocumentNode, + fragment, + graphql, +} from './graphql'; -const FollowNotification = graphql( +const FollowNotificationFragment = fragment( `fragment FollowNotification on FollowNotification { __typename id @@ -13,11 +22,13 @@ const FollowNotification = graphql( followedAt } }`, - [AccountFragment], ); -export type FollowNotification = FragmentOf; +export type FollowNotification = DynamicFragmentOf< + typeof FollowNotificationFragment, + [TAccount] +>; -const ReactionNotification = graphql( +const ReactionNotificationFragment = fragment( `fragment ReactionNotification on ReactionNotification { __typename id @@ -34,11 +45,14 @@ const ReactionNotification = graphql( ...Post } }`, - [AccountFragment, PostFragment], + [PostFragment], ); -export type ReactionNotification = FragmentOf; +export type ReactionNotification = DynamicFragmentOf< + typeof ReactionNotificationFragment, + [TAccount] +>; -const CommentNotification = graphql( +const CommentNotificationFragment = graphql( `fragment CommentNotification on CommentNotification { __typename id @@ -48,9 +62,9 @@ const CommentNotification = graphql( }`, [PostFragment], ); -export type CommentNotification = FragmentOf; +export type CommentNotification = FragmentOf; -const RepostNotification = graphql( +const RepostNotificationFragment = fragment( `fragment RepostNotification on RepostNotification { __typename id @@ -65,11 +79,13 @@ const RepostNotification = graphql( ...Post } }`, - [AccountFragment], + [PostFragment], ); -export type RepostNotification = FragmentOf; +export type RepostNotification = Prettify< + DynamicFragmentOf +>; -const QuoteNotification = graphql( +const QuoteNotificationFragment = graphql( `fragment QuoteNotification on QuoteNotification { __typename id @@ -79,9 +95,9 @@ const QuoteNotification = graphql( }`, [PostFragment], ); -export type QuoteNotification = FragmentOf; +export type QuoteNotification = FragmentOf; -const MentionNotification = graphql( +const MentionNotificationFragment = graphql( `fragment MentionNotification on MentionNotification { __typename id @@ -91,53 +107,60 @@ const MentionNotification = graphql( }`, [PostFragment], ); -export type MentionNotification = FragmentOf; +export type MentionNotification = FragmentOf; -const Notification = graphql( - `fragment Notification on Notification { - __typename - ... on FollowNotification { - ...FollowNotification - } - ... on ReactionNotification { - ...ReactionNotification - } - ... on CommentNotification { - ...CommentNotification - } - ... on RepostNotification { - ...RepostNotification - } - ... on QuoteNotification { - ...QuoteNotification - } - ... on MentionNotification { - ...MentionNotification - } - }`, - [ - FollowNotification, - ReactionNotification, - CommentNotification, - RepostNotification, - QuoteNotification, - MentionNotification, - ], -); -export type Notification = FragmentOf; +export type Notification = + | FollowNotification + | ReactionNotification + | CommentNotification + | RepostNotification + | QuoteNotification + | MentionNotification; -export const NotificationsQuery = graphql( - `query Notifications($request: NotificationRequest!) { +const query = ` + query Notifications($request: NotificationRequest!) { value: notifications(request: $request) { __typename items { - ...Notification + ... on FollowNotification { + ...FollowNotification + } + ... on ReactionNotification { + ...ReactionNotification + } + ... on CommentNotification { + ...CommentNotification + } + ... on RepostNotification { + ...RepostNotification + } + ... on QuoteNotification { + ...QuoteNotification + } + ... on MentionNotification { + ...MentionNotification + } } pageInfo { ...PaginatedResultInfo } } - }`, - [Notification, PaginatedResultInfo], -); -export type NotificationsRequest = RequestOf; + } +`; +export type NotificationsRequest = RequestFrom; + +export function notificationsQuery( + AccountFragment: FragmentDocumentFor, +): StandardDocumentNode>, NotificationsRequest> { + return graphql(query, [ + FollowNotificationFragment, + ReactionNotificationFragment, + CommentNotificationFragment, + RepostNotificationFragment, + QuoteNotificationFragment, + MentionNotificationFragment, + AccountFragment, + PostFragment, + PaginatedResultInfoFragment, + ]) as StandardDocumentNode; +} diff --git a/packages/react/src/provider.tsx b/packages/react/src/provider.tsx index e9cd76eb4..17d438a64 100644 --- a/packages/react/src/provider.tsx +++ b/packages/react/src/provider.tsx @@ -1,13 +1,14 @@ import type { PublicClient } from '@lens-protocol/client'; import React from 'react'; +import type { Context } from '@lens-protocol/client'; import type { ReactNode } from 'react'; import { LensContextProvider } from './context'; /** * props */ -export type ProviderProps = { +export type ProviderProps = { /** * The children to render */ @@ -15,7 +16,7 @@ export type ProviderProps = { /** * The configuration for the Lens SDK */ - client: PublicClient; + client: PublicClient; }; /** @@ -36,6 +37,6 @@ export type ProviderProps = { * } * ``` */ -export function Provider({ children, client }: ProviderProps) { +export function Provider({ children, client }: ProviderProps) { return {children}; } diff --git a/packages/types/src/helpers/assertions.ts b/packages/types/src/helpers/assertions.ts index adc83785b..e3cc5b63c 100644 --- a/packages/types/src/helpers/assertions.ts +++ b/packages/types/src/helpers/assertions.ts @@ -1,6 +1,6 @@ import type { Err, Ok, Result } from 'neverthrow'; import type { UnknownRecord } from 'type-fest'; -import { InvariantError, invariant } from './invariant'; +import { InvariantError } from './invariant'; function isObject(value: unknown): value is UnknownRecord { const type = typeof value; @@ -27,12 +27,16 @@ export function assertNever(x: never, message = `Unexpected object: ${String(x)} * Asserts that the given `Result` is an `Ok` variant. */ export function assertOk(result: Result): asserts result is Ok { - invariant(result.isOk(), 'Expected result to be Ok'); + if (result.isErr()) { + throw new InvariantError(`Expected result to be Ok: ${result.error.message}`); + } } /** * Asserts that the given `Result` is an `Err` variant. */ export function assertErr(result: Result): asserts result is Err { - invariant(result.isErr(), 'Expected result to be Err'); + if (result.isOk()) { + throw new InvariantError(`Expected result to be Err: ${result.value}`); + } }