From cbb82879f30c85362726ad78a65d5d00ecff5849 Mon Sep 17 00:00:00 2001 From: Cesare Naldi Date: Fri, 13 Dec 2024 07:45:10 +0100 Subject: [PATCH] chore: initial refactoring --- packages/client/src/actions/account.ts | 2 +- packages/client/src/actions/accountManager.ts | 2 +- packages/client/src/actions/authentication.ts | 2 +- packages/client/src/actions/follow.ts | 2 +- packages/client/src/actions/notifications.ts | 3 +- packages/client/src/actions/posts.ts | 2 +- packages/client/src/actions/timeline.ts | 2 +- packages/client/src/actions/username.ts | 2 +- packages/client/src/clients.ts | 49 ++++--- packages/client/src/types.ts | 18 --- packages/graphql/src/common.ts | 17 +++ packages/graphql/src/graphql.ts | 128 +++++++++++++----- packages/graphql/src/index.ts | 1 + 13 files changed, 149 insertions(+), 81 deletions(-) create mode 100644 packages/graphql/src/common.ts diff --git a/packages/client/src/actions/account.ts b/packages/client/src/actions/account.ts index ec5f1f199..93172b0ed 100644 --- a/packages/client/src/actions/account.ts +++ b/packages/client/src/actions/account.ts @@ -17,6 +17,7 @@ import type { CreateAccountWithUsernameResult, EnableSignlessResult, MuteRequest, + Paginated, RecommendAccountRequest, RemoveSignlessResult, ReportAccountRequest, @@ -52,7 +53,6 @@ import type { ResultAsync } from '@lens-protocol/types'; import type { AnyClient, SessionClient } from '../clients'; import type { UnauthenticatedError, UnexpectedError } from '../errors'; -import type { Paginated } from '../types'; /** * Fetch an Account. diff --git a/packages/client/src/actions/accountManager.ts b/packages/client/src/actions/accountManager.ts index 3be1c5857..0f769c3c0 100644 --- a/packages/client/src/actions/accountManager.ts +++ b/packages/client/src/actions/accountManager.ts @@ -4,6 +4,7 @@ import type { AddAccountManagerRequest, AddAccountManagerResult, HideManagedAccountRequest, + Paginated, RemoveAccountManagerRequest, RemoveAccountManagerResult, UnhideManagedAccountRequest, @@ -22,7 +23,6 @@ import type { ResultAsync } from '@lens-protocol/types'; import type { SessionClient } from '../clients'; import type { UnauthenticatedError, UnexpectedError } from '../errors'; -import type { Paginated } from '../types'; /** * Fetch Account Managers. diff --git a/packages/client/src/actions/authentication.ts b/packages/client/src/actions/authentication.ts index ec4d09e71..0b2bb7b5c 100644 --- a/packages/client/src/actions/authentication.ts +++ b/packages/client/src/actions/authentication.ts @@ -1,6 +1,7 @@ import type { AuthenticatedSession, AuthenticatedSessionsRequest, + Paginated, RefreshRequest, RefreshResult, RevokeAuthenticationRequest, @@ -20,7 +21,6 @@ import type { ResultAsync } from '@lens-protocol/types'; import type { AnyClient, SessionClient } from '../clients'; import type { UnauthenticatedError, UnexpectedError } from '../errors'; -import type { Paginated } from '../types'; /** * Get the AuthenticatedSession associated with the authenticated Account. diff --git a/packages/client/src/actions/follow.ts b/packages/client/src/actions/follow.ts index c4cc15ed2..3019408d5 100644 --- a/packages/client/src/actions/follow.ts +++ b/packages/client/src/actions/follow.ts @@ -2,6 +2,7 @@ import type { CreateFollowRequest, CreateUnfollowRequest, FollowResult, + Paginated, UnfollowResult, } from '@lens-protocol/graphql'; import { @@ -23,7 +24,6 @@ import type { FollowStatusRequest } from '@lens-protocol/graphql'; import type { FollowStatusResult } from '@lens-protocol/graphql'; import type { AnyClient, SessionClient } from '../clients'; import type { UnauthenticatedError, UnexpectedError } from '../errors'; -import type { Paginated } from '../types'; /** * Follow an Account diff --git a/packages/client/src/actions/notifications.ts b/packages/client/src/actions/notifications.ts index e561d4d39..3d610d6a4 100644 --- a/packages/client/src/actions/notifications.ts +++ b/packages/client/src/actions/notifications.ts @@ -1,10 +1,9 @@ -import type { Notification, NotificationsRequest } from '@lens-protocol/graphql'; +import type { Notification, NotificationsRequest, Paginated } from '@lens-protocol/graphql'; import { NotificationsQuery } from '@lens-protocol/graphql'; import type { ResultAsync } from '@lens-protocol/types'; import type { SessionClient } from '../clients'; import type { UnexpectedError } from '../errors'; -import type { Paginated } from '../types'; /** * Fetch notifications for the authenticated Account. diff --git a/packages/client/src/actions/posts.ts b/packages/client/src/actions/posts.ts index bf9426d2f..b1baa4d7f 100644 --- a/packages/client/src/actions/posts.ts +++ b/packages/client/src/actions/posts.ts @@ -2,6 +2,7 @@ import type { AccountPostReaction, ActionInfo, AnyPost, + Paginated, Post, PostActionsRequest, PostBookmarksRequest, @@ -20,7 +21,6 @@ import type { ResultAsync } from '@lens-protocol/types'; import type { AnyClient, SessionClient } from '../clients'; import type { UnauthenticatedError, UnexpectedError } from '../errors'; -import type { Paginated } from '../types'; /** * Fetch a Post. diff --git a/packages/client/src/actions/timeline.ts b/packages/client/src/actions/timeline.ts index 41c709a2a..f2a2f2fd5 100644 --- a/packages/client/src/actions/timeline.ts +++ b/packages/client/src/actions/timeline.ts @@ -1,4 +1,5 @@ import type { + Paginated, Post, TimelineHighlightsRequest, TimelineItem, @@ -9,7 +10,6 @@ import type { ResultAsync } from '@lens-protocol/types'; import type { AnyClient } from '../clients'; import type { UnexpectedError } from '../errors'; -import type { Paginated } from '../types'; /** * Fetch timeline from an account. diff --git a/packages/client/src/actions/username.ts b/packages/client/src/actions/username.ts index 78da131d1..040cf6c39 100644 --- a/packages/client/src/actions/username.ts +++ b/packages/client/src/actions/username.ts @@ -3,6 +3,7 @@ import type { AssignUsernameToAccountResult, CreateUsernameRequest, CreateUsernameResult, + Paginated, UnassignUsernameFromAccountRequest, UnassignUsernameToAccountResult, Username, @@ -22,7 +23,6 @@ import type { SessionClient } from '../clients'; import type { UnauthenticatedError, UnexpectedError } from '../errors'; import type { AnyClient } from '../clients'; -import type { Paginated } from '../types'; /** * Create a username diff --git a/packages/client/src/clients.ts b/packages/client/src/clients.ts index d1c5c1592..b2ac14433 100644 --- a/packages/client/src/clients.ts +++ b/packages/client/src/clients.ts @@ -3,6 +3,7 @@ import type { AuthenticationChallenge, ChallengeRequest, SignedAuthChallenge, + StandardData, } from '@lens-protocol/graphql'; import type { Credentials, IStorage } from '@lens-protocol/storage'; import { createCredentialsStorage } from '@lens-protocol/storage'; @@ -42,7 +43,6 @@ import { hasExtensionCode, } from './errors'; import { decodeIdToken } from './tokens'; -import type { StandardData } from './types'; import { delay } from './utils'; function takeValue({ @@ -59,14 +59,14 @@ export type LoginParams = ChallengeRequest & { signMessage: SignMessage; }; -abstract class AbstractClient { +abstract class AbstractClient { protected readonly urql: UrqlClient; protected readonly logger: Logger; protected readonly credentials: IStorage; - protected constructor(public readonly context: Context) { + protected constructor(public readonly context: TContext) { this.credentials = createCredentialsStorage(context.storage, context.environment.name); this.logger = getLogger(this.constructor.name); @@ -99,12 +99,12 @@ abstract class AbstractClient { /** * Asserts that the client is a {@link PublicClient}. */ - public abstract isPublicClient(): this is PublicClient; + public abstract isPublicClient(): this is PublicClient; /** * that the client is a {@link SessionClient}. */ - public abstract isSessionClient(): this is SessionClient; + public abstract isSessionClient(): this is SessionClient; public abstract query( document: TypedDocumentNode, TVariables>, @@ -144,13 +144,16 @@ abstract class AbstractClient { /** * A client to interact with the public access queries and mutations of the Lens GraphQL API. */ -export class PublicClient extends AbstractClient { +export class PublicClient extends AbstractClient< + TContext, + UnexpectedError +> { /** * The current session client. * * This could be the {@link PublicClient} itself if the user is not authenticated, or a {@link SessionClient} if the user is authenticated. */ - public currentSession: PublicClient | SessionClient = this; + public currentSession: PublicClient | SessionClient = this; /** * Create a new instance of the {@link PublicClient}. @@ -165,7 +168,7 @@ export class PublicClient extends AbstractClient { * @param options - The options to configure the client. * @returns The new instance of the client. */ - static create(options: ClientConfig): PublicClient { + static create(options: ClientConfig): PublicClient { return new PublicClient(configureContext(options)); } @@ -181,7 +184,7 @@ export class PublicClient extends AbstractClient { */ authenticate( request: SignedAuthChallenge, - ): ResultAsync { + ): ResultAsync, AuthenticationError | UnexpectedError> { return this.mutation(AuthenticateMutation, { request }) .andThen((result) => { if (result.__typename === 'AuthenticationTokens') { @@ -205,7 +208,7 @@ export class PublicClient extends AbstractClient { signMessage, ...request }: LoginParams): ResultAsync< - SessionClient, + SessionClient, AuthenticationError | SigningError | UnexpectedError > { return this.challenge(request) @@ -232,7 +235,7 @@ export class PublicClient extends AbstractClient { * * @returns The session client if available. */ - resumeSession(): ResultAsync { + resumeSession(): ResultAsync, UnauthenticatedError> { return ResultAsync.fromSafePromise(this.credentials.get()).andThen((credentials) => { if (!credentials) { return new UnauthenticatedError('No credentials found').asResultAsync(); @@ -244,14 +247,14 @@ export class PublicClient extends AbstractClient { /** * {@inheritDoc AbstractClient.isPublicClient} */ - public override isPublicClient(): this is PublicClient { + public override isPublicClient(): this is PublicClient { return true; } /** * {@inheritDoc AbstractClient.isSessionClient} */ - public override isSessionClient(): this is SessionClient { + public override isSessionClient(): this is SessionClient { return false; } @@ -289,12 +292,15 @@ export class PublicClient extends AbstractClient { * * @privateRemarks Intentionally not exported. */ -class SessionClient extends AbstractClient { - public get parent(): PublicClient { +class SessionClient extends AbstractClient< + TContext, + UnauthenticatedError | UnexpectedError +> { + public get parent(): PublicClient { return this._parent; } - constructor(private readonly _parent: PublicClient) { + constructor(private readonly _parent: PublicClient) { super(_parent.context); _parent.currentSession = this; } @@ -328,14 +334,14 @@ class SessionClient extends AbstractClient { return false; } /** * {@inheritDoc AbstractClient.isSessionClient} */ - public override isSessionClient(): this is SessionClient { + public override isSessionClient(): this is SessionClient { return true; } @@ -431,7 +437,8 @@ class SessionClient extends AbstractClient = + | PublicClient + | SessionClient; diff --git a/packages/client/src/types.ts b/packages/client/src/types.ts index 1daa950eb..e2f010c34 100644 --- a/packages/client/src/types.ts +++ b/packages/client/src/types.ts @@ -1,5 +1,3 @@ -import type { PaginatedResultInfo } from '@lens-protocol/graphql'; - import type { SelfFundedTransactionRequest, SponsoredTransactionRequest, @@ -56,19 +54,3 @@ export type DelegableOperationHandler = ( export type OperationHandler = | RestrictedOperationHandler | DelegableOperationHandler; - -/** - * A standardized data object. - * - * All GQL operations should alias their results to `value` to ensure interoperability - * with this client interface. - */ -export type StandardData = { value: T }; - -/** - * A paginated list of items. - */ -export type Paginated = { - items: readonly T[]; - pageInfo: PaginatedResultInfo; -}; diff --git a/packages/graphql/src/common.ts b/packages/graphql/src/common.ts new file mode 100644 index 000000000..e8211a415 --- /dev/null +++ b/packages/graphql/src/common.ts @@ -0,0 +1,17 @@ +import type { PaginatedResultInfo } from './fragments'; + +/** + * A paginated list of items. + */ +export type Paginated = { + items: readonly T[]; + pageInfo: PaginatedResultInfo; +}; + +/** + * A standardized data object. + * + * All GQL operations should alias their results to `value` to ensure interoperability + * with this client interface. + */ +export type StandardData = { value: T }; diff --git a/packages/graphql/src/graphql.ts b/packages/graphql/src/graphql.ts index 374073d17..89cf6e57b 100644 --- a/packages/graphql/src/graphql.ts +++ b/packages/graphql/src/graphql.ts @@ -1,28 +1,33 @@ -import { - type AccessToken, - type BigDecimal, - type BigIntString, - type BlockchainData, - type CompactJwt, - type Cursor, - type DateTime, - type EncodedTransaction, - type EvmAddress, - type ID, - type IdToken, - type LegacyProfileId, - type PostId, - type RefreshToken, - type Signature, - type TxHash, - type URI, - type URL, - type UUID, - type UsernameValue, - type Void, - never, +import type { + AccessToken, + BigDecimal, + BigIntString, + BlockchainData, + CompactJwt, + Cursor, + DateTime, + EncodedTransaction, + EvmAddress, + ID, + IdToken, + LegacyProfileId, + PostId, + RefreshToken, + Signature, + TxHash, + URI, + URL, + UUID, + UsernameValue, + Void, } from '@lens-protocol/types'; -import { type DocumentDecoration, type FragmentOf, initGraphQLTada } from 'gql.tada'; +import { + type DocumentDecoration, + type FragmentOf, + type TadaDocumentNode, + initGraphQLTada, +} from 'gql.tada'; +import type { StandardData } from './common'; import type { AccountReportReason, ContentWarning, @@ -84,21 +89,76 @@ export type RequestOf = Document extends DocumentDecoration< */ export type FragmentShape = NonNullable[1]>[number]; -export type TypedDocumentFrom = ReturnType< - typeof graphql +type GetDocumentNode< + In extends string = string, + Fragments extends FragmentShape[] = FragmentShape[], +> = ReturnType>; + +export type AnyGqlNode = { __typename: TTypename }; + +export type AnyVariables = Record; + +/** + * @internal + */ +export type FragmentDocumentFor = TGqlNode extends AnyGqlNode< + infer TTypename +> + ? TadaDocumentNode< + TGqlNode, + AnyVariables, + { + fragment: TTypename; + on: TTypename; + masked: false; + } + > + : never; + +export type RequestFrom = RequestOf>; + +// biome-ignore lint/suspicious/noExplicitAny: simplifies necessary type assertions +export type StandardDocumentNode = TadaDocumentNode< + StandardData, + { request: Request } >; -export type FragmentNodeFor = T extends FragmentOf ? U : never; +type FragmentDocumentFrom< + In extends string, + Fragments extends FragmentShape[], + Document extends GetDocumentNode = GetDocumentNode, +> = Document extends FragmentShape ? Document : never; -export type Factory = []>( - ...fragments: T -) => TypedDocumentFrom; +type FragmentDocumentForEach = { + [K in keyof Nodes]: FragmentDocumentFor; +}; /** * @internal */ -export function factory(_: In): Factory { - return []>(..._fragments: T): TypedDocumentFrom => { - never('This function should never be called'); - }; +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 DynamicFragmentOf< + Document, + DynamicNodes extends AnyGqlNode[], +> = Document extends DynamicFragmentDocument + ? FragmentOf>> + : never; diff --git a/packages/graphql/src/index.ts b/packages/graphql/src/index.ts index 6fe340df7..3b69bd8a4 100644 --- a/packages/graphql/src/index.ts +++ b/packages/graphql/src/index.ts @@ -2,6 +2,7 @@ export * from './accounts'; export * from './admins'; export * from './app'; export * from './authentication'; +export * from './common'; export * from './enums'; export * from './feed'; export * from './follow';