diff --git a/examples/custom-fragments/README.md b/examples/custom-fragments/README.md new file mode 100644 index 000000000..78a035b54 --- /dev/null +++ b/examples/custom-fragments/README.md @@ -0,0 +1,3 @@ +# Custom Fragments + +[![Open in StackBlitz](https://developer.stackblitz.com/img/open_in_stackblitz.svg)](https://stackblitz.com/github/lens-protocol/lens-sdk/tree/next/examples/custom-fragments) diff --git a/examples/custom-fragments/index.html b/examples/custom-fragments/index.html new file mode 100644 index 000000000..b4529522a --- /dev/null +++ b/examples/custom-fragments/index.html @@ -0,0 +1,18 @@ + + + + + + + + +

Custom Fragments

+
Loading...
+ + + diff --git a/examples/custom-fragments/index.ts b/examples/custom-fragments/index.ts new file mode 100644 index 000000000..5d516eb87 --- /dev/null +++ b/examples/custom-fragments/index.ts @@ -0,0 +1,41 @@ +import 'viem/window'; + +import { + type FragmentOf, + PublicClient, + evmAddress, + graphql, + testnet as protocolTestnet, +} from '@lens-protocol/client'; +import { fetchAccount } from '@lens-protocol/client/actions'; + +const MyAccountFragment = graphql( + `fragment Account on Account { + __typename + address + username { + value + } + metadata { + __typename + name + picture + } + }`, +); + +type MyAccount = FragmentOf; + +const client = PublicClient.create({ + environment: protocolTestnet, + accountFragment: MyAccountFragment, +}); + +const account: MyAccount | null = await fetchAccount(client, { + address: evmAddress('0x57b62a1571F4F09CDB4C3d93dA542bfe142D9F81'), +}).unwrapOr(null); + +export default [ + `

${account?.username?.value}

`, + `
${JSON.stringify(account, null, 2)}
`, +]; diff --git a/examples/custom-fragments/package.json b/examples/custom-fragments/package.json new file mode 100644 index 000000000..dea64d2e6 --- /dev/null +++ b/examples/custom-fragments/package.json @@ -0,0 +1,19 @@ +{ + "name": "example-custom-fragments", + "private": true, + "type": "module", + "scripts": { + "dev": "vite" + }, + "dependencies": { + "@lens-network/sdk": "canary", + "@lens-protocol/client": "file:../../packages/client", + "@lens-protocol/metadata": "next", + "@lens-protocol/storage-node-client": "next", + "viem": "^2.21.55" + }, + "devDependencies": { + "typescript": "^5.6.3", + "vite": "^5.4.11" + } +} diff --git a/examples/custom-fragments/tsconfig.json b/examples/custom-fragments/tsconfig.json new file mode 100644 index 000000000..6da89b87d --- /dev/null +++ b/examples/custom-fragments/tsconfig.json @@ -0,0 +1,19 @@ +{ + "compilerOptions": { + "target": "ESNext", + "useDefineForClassFields": true, + "lib": ["ESNext", "DOM"], + "module": "ESNext", + "moduleResolution": "Bundler", + "strict": true, + "resolveJsonModule": true, + "isolatedModules": true, + "esModuleInterop": true, + "noEmit": true, + "noUnusedLocals": true, + "noUnusedParameters": true, + "noImplicitReturns": true, + "skipLibCheck": true + }, + "include": ["./"] +} diff --git a/packages/client/src/actions/account.ts b/packages/client/src/actions/account.ts index 4606b3204..8b043fa55 100644 --- a/packages/client/src/actions/account.ts +++ b/packages/client/src/actions/account.ts @@ -33,12 +33,10 @@ import type { import { AccountFeedsStatsQuery, AccountGraphsStatsQuery, - AccountQuery, AccountStatsQuery, AccountsAvailableQuery, AccountsBlockedQuery, AccountsBulkQuery, - AccountsQuery, BlockMutation, CreateAccountWithUsernameMutation, EnableSignlessMutation, @@ -50,10 +48,13 @@ import { UnblockMutation, UndoRecommendAccountMutation, UnmuteAccountMutation, + accountQuery, + accountsQuery, } from '@lens-protocol/graphql'; import type { ResultAsync } from '@lens-protocol/types'; import type { AnyClient, SessionClient } from '../clients'; +import type { Context } from '../context'; import type { UnauthenticatedError, UnexpectedError } from '../errors'; /** @@ -71,11 +72,11 @@ import type { UnauthenticatedError, UnexpectedError } from '../errors'; * @param request - The Account query request. * @returns The Account or `null` if it does not exist. */ -export function fetchAccount( - client: AnyClient, +export function fetchAccount( + client: AnyClient>, request: AccountRequest, -): ResultAsync { - return client.query(AccountQuery, { request }); +): ResultAsync { + return client.query(accountQuery(client.context.accountFragment), { request }); } /** @@ -84,18 +85,24 @@ export function fetchAccount( * Using a {@link SessionClient} will yield {@link Account#operations} specific to the authenticated Account. * * ```ts - * const result = await fetchAccounts(anyClient); + * const result = await fetchAccounts(anyClient, { + * filter: { + * searchBy: { + * localNameQuery: 'stani', + * } + * } + * }); * ``` * * @param client - Any Lens client. * @param request - The query request. * @returns The list of accounts. */ -export function fetchAccounts( - client: AnyClient, +export function fetchAccounts( + client: AnyClient>, request: AccountsRequest = {}, -): ResultAsync | null, UnexpectedError> { - return client.query(AccountsQuery, { request }); +): ResultAsync | null, UnexpectedError> { + return client.query(accountsQuery(client.context.accountFragment), { request }); } /** diff --git a/packages/client/src/clients.ts b/packages/client/src/clients.ts index 35f592578..669f5a440 100644 --- a/packages/client/src/clients.ts +++ b/packages/client/src/clients.ts @@ -32,7 +32,7 @@ import { type AuthConfig, authExchange } from '@urql/exchange-auth'; import { type AuthenticatedUser, authenticatedUser } from './AuthenticatedUser'; import { switchAccount, transactionStatus } from './actions'; import type { ClientConfig } from './config'; -import { type Context, configureContext } from './context'; +import { type Context, type ContextFrom, configureContext } from './context'; import { AuthenticationError, GraphQLErrorCode, @@ -151,7 +151,9 @@ export class PublicClient extends AbstractCl * @param options - The options to configure the client. * @returns The new instance of the client. */ - static create(options: ClientConfig): PublicClient { + static create( + options: TConfig, + ): PublicClient> { return new PublicClient(configureContext(options)); } diff --git a/packages/client/src/config.ts b/packages/client/src/config.ts index 2e743c545..540bb8121 100644 --- a/packages/client/src/config.ts +++ b/packages/client/src/config.ts @@ -1,10 +1,12 @@ import type { EnvironmentConfig } from '@lens-protocol/env'; +import type { Account } 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 = { /** * The environment configuration to use (e.g. `mainnet`, `testnet`). */ @@ -27,11 +29,16 @@ export type ClientConfig = { * Use this to set the `Origin` header for requests from non-browser environments. */ origin?: string; - /** * The storage provider to use. * * @defaultValue {@link InMemoryStorageProvider} */ storage?: IStorageProvider; + /** + * The Account Fragment to use. + * + * @defaultValue {@link AccountFragment} + */ + accountFragment?: FragmentDocumentFor; }; diff --git a/packages/client/src/context.ts b/packages/client/src/context.ts index 995d799f3..7c0623a56 100644 --- a/packages/client/src/context.ts +++ b/packages/client/src/context.ts @@ -1,27 +1,40 @@ import type { EnvironmentConfig } from '@lens-protocol/env'; +import { type Account, AccountFragment } from '@lens-protocol/graphql'; +import type { FragmentDocumentFor } from '@lens-protocol/graphql'; import { type IStorageProvider, InMemoryStorageProvider } from '@lens-protocol/storage'; import type { ClientConfig } from './config'; /** * @internal */ -export type Context = { +export type Context = { environment: EnvironmentConfig; cache: boolean; debug: boolean; origin?: string; storage: IStorageProvider; + accountFragment: FragmentDocumentFor; }; /** * @internal */ -export function configureContext(from: ClientConfig): Context { +export type ContextFrom = TConfig extends ClientConfig + ? Context + : never; + +/** + * @internal + */ +export function configureContext( + from: TConfig, +): ContextFrom { return { environment: from.environment, cache: from.cache ?? false, debug: from.debug ?? false, origin: from.origin, storage: from.storage ?? new InMemoryStorageProvider(), - }; + accountFragment: from.accountFragment ?? AccountFragment, + } as ContextFrom; } diff --git a/packages/graphql/src/accounts/account.ts b/packages/graphql/src/accounts/account.ts index 4be974022..1019bd162 100644 --- a/packages/graphql/src/accounts/account.ts +++ b/packages/graphql/src/accounts/account.ts @@ -1,5 +1,7 @@ import type { FragmentOf } from 'gql.tada'; +import type { Paginated } from '../common'; import { + type Account, AccountAvailableFragment, AccountBlockedFragment, AccountFragment, @@ -8,23 +10,37 @@ import { SponsoredTransactionRequestFragment, TransactionWillFailFragment, } from '../fragments'; -import { type RequestOf, graphql } from '../graphql'; - -export const AccountQuery = graphql( - `query Account($request: AccountRequest!) { +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!) { value: account(request: $request) { ...Account } }`, - [AccountFragment], -); -export type AccountRequest = RequestOf; - -export const AccountsQuery = graphql( - `query Accounts($request: AccountsRequest!) { + [fragment], + ) as StandardDocumentNode; +} + +export type AccountsRequest = RequestTypeOf<'AccountsRequest'>; +export function accountsQuery( + fragment: FragmentDocumentFor, +): StandardDocumentNode, AccountsRequest> { + return graphql( + `query Accounts($request: AccountsRequest!) { value: accounts(request: $request) { __typename - items{ + items { ...Account } pageInfo { @@ -32,9 +48,9 @@ export const AccountsQuery = graphql( } } }`, - [AccountFragment, PaginatedResultInfoFragment], -); -export type AccountsRequest = RequestOf; + [fragment, PaginatedResultInfoFragment], + ) as StandardDocumentNode; +} export const AccountsBulkQuery = graphql( `query AccountsBulk($request: AccountsBulkRequest!) { diff --git a/packages/graphql/src/fragments/account.ts b/packages/graphql/src/fragments/account.ts index 42e1a10ab..112ed209d 100644 --- a/packages/graphql/src/fragments/account.ts +++ b/packages/graphql/src/fragments/account.ts @@ -48,9 +48,15 @@ export const AccountFragment = graphql( `fragment Account on Account { __typename address - owner - score - createdAt + }`, + [], +); +export type Account = FragmentOf; + +export const FullAccountFragment = graphql( + `fragment Account on Account { + __typename + address username{ ...Username } @@ -63,7 +69,7 @@ export const AccountFragment = graphql( }`, [AccountMetadataFragment, LoggedInAccountOperationsFragment, UsernameFragment], ); -export type Account = FragmentOf; +export type FullAccount = FragmentOf; const AccountManagerPermissionsFragment = graphql( `fragment AccountManagerPermissions on AccountManagerPermissions { diff --git a/packages/graphql/src/graphql.ts b/packages/graphql/src/graphql.ts index 299cbdd0a..00a66a240 100644 --- a/packages/graphql/src/graphql.ts +++ b/packages/graphql/src/graphql.ts @@ -164,6 +164,8 @@ export const graphql = initGraphQLTada<{ }; }>(); +export type { FragmentOf, TadaDocumentNode }; + /** * @internal */ @@ -205,7 +207,12 @@ export type FragmentDocumentFor = TGqlNode extends > : never; -export type RequestFrom = RequestOf>; +/** + * @internal + */ +export type RequestTypeOf = RequestOf< + GetDocumentNode<`query Named($request: ${Name}) {}`, FragmentShape[]> +>; // biome-ignore lint/suspicious/noExplicitAny: simplifies necessary type assertions export type StandardDocumentNode = TadaDocumentNode<