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..3a2b9650b 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/actions/authentication.ts b/packages/client/src/actions/authentication.ts
index 9c6ea179c..7cfba3d08 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,
-): ResultAsync {
- return client.query(MeQuery, {});
+export function fetchMeDetails(
+ client: SessionClient>,
+): ResultAsync, UnauthenticatedError | UnexpectedError> {
+ 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/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..2dadb38cc 100644
--- a/packages/client/src/config.ts
+++ b/packages/client/src/config.ts
@@ -1,10 +1,15 @@
import type { EnvironmentConfig } from '@lens-protocol/env';
+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`).
*/
@@ -27,11 +32,22 @@ 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;
+ /**
+ * 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 995d799f3..538faafc0 100644
--- a/packages/client/src/context.ts
+++ b/packages/client/src/context.ts
@@ -1,27 +1,45 @@
import type { EnvironmentConfig } from '@lens-protocol/env';
+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;
+ postFieldsFragment: 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,
+ 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 4be974022..7caa73e58 100644
--- a/packages/graphql/src/accounts/account.ts
+++ b/packages/graphql/src/accounts/account.ts
@@ -8,23 +8,23 @@ import {
SponsoredTransactionRequestFragment,
TransactionWillFailFragment,
} from '../fragments';
-import { type RequestOf, graphql } from '../graphql';
+import { type RequestOf, dynamic, graphql } from '../graphql';
-export const AccountQuery = graphql(
+export const accountQuery = dynamic(
`query Account($request: AccountRequest!) {
value: account(request: $request) {
...Account
}
}`,
- [AccountFragment],
+ [],
);
-export type AccountRequest = RequestOf;
+export type AccountRequest = RequestOf;
-export const AccountsQuery = graphql(
+export const accountsQuery = dynamic(
`query Accounts($request: AccountsRequest!) {
value: accounts(request: $request) {
__typename
- items{
+ items {
...Account
}
pageInfo {
@@ -32,9 +32,9 @@ export const AccountsQuery = graphql(
}
}
}`,
- [AccountFragment, PaginatedResultInfoFragment],
+ [PaginatedResultInfoFragment],
);
-export type AccountsRequest = RequestOf;
+export type AccountsRequest = RequestOf;
export const AccountsBulkQuery = graphql(
`query AccountsBulk($request: AccountsBulkRequest!) {
@@ -222,7 +222,7 @@ export const AccountGraphsStatsQuery = graphql(
export type AccountGraphsStatsRequest = RequestOf;
-export const AccountsAvailableQuery = graphql(
+export const AccountsAvailableQuery = dynamic(
`query AccountsAvailable($request: AccountsAvailableRequest!) {
value: accountsAvailable(request: $request) {
items{
diff --git a/packages/graphql/src/authentication.ts b/packages/graphql/src/authentication.ts
index 32a3ceb8b..6dc6e2f08 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,
+ type AccountAvailable,
+ AccountAvailableFragment,
+ 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
@@ -210,17 +222,20 @@ const MeResultFragment = graphql(
window
}
loggedInAs {
- ...AccountAvailable
+ ${'...AccountAvailable'}
}
}`,
[AccountAvailableFragment],
);
-export type MeResult = FragmentOf;
+export type MeResult = PartialFragmentOf<
+ typeof MeResultFragment,
+ [FragmentDocumentFor>]
+>;
-export const MeQuery = graphql(
+export const meQuery = dynamic(
`query Me {
value: me {
- ...MeResult
+ ${'...MeResult'}
}
}`,
[MeResultFragment],
diff --git a/packages/graphql/src/fragments/account.ts b/packages/graphql/src/fragments/account.ts
index 42e1a10ab..2c2074962 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';
@@ -45,6 +45,34 @@ export const AccountMetadataFragment = graphql(
export type AccountMetadata = FragmentOf;
export const AccountFragment = graphql(
+ `fragment Account on Account {
+ __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
@@ -63,7 +91,10 @@ export const AccountFragment = graphql(
}`,
[AccountMetadataFragment, LoggedInAccountOperationsFragment, UsernameFragment],
);
-export type Account = FragmentOf;
+/**
+ * @deprecated Define your own FullAccountFragment instead using {@link graphql} and {@link FragmentOf}.
+ */
+export type FullAccount = FragmentOf;
const AccountManagerPermissionsFragment = graphql(
`fragment AccountManagerPermissions on AccountManagerPermissions {
@@ -89,38 +120,54 @@ export const AccountManagerFragment = graphql(
);
export type AccountManager = FragmentOf;
-const AccountManagedFragment = graphql(
+const AccountManagedFragment = partial(
`fragment AccountManaged on AccountManaged {
__typename
addedAt
+ permissions {
+ ...AccountManagerPermissions
+ }
account {
...Account
}
- permissions {
- ...AccountManagerPermissions
+ }`,
+ [AccountManagerPermissionsFragment],
+);
+export type AccountManaged = PartialFragmentOf<
+ typeof AccountManagedFragment,
+ [FragmentDocumentFor]
+>;
+
+const AccountOwnedFragment = partial(
+ `fragment AccountOwned on AccountOwned {
+ __typename
+ addedAt
+ account {
+ ...Account
}
}`,
- [AccountManagerPermissionsFragment, AccountFragment],
);
-export type AccountManaged = FragmentOf;
-export const AccountAvailableFragment = graphql(
+export type AccountOwned = PartialFragmentOf<
+ typeof AccountOwnedFragment,
+ [FragmentDocumentFor]
+>;
+
+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..1a38badac 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,41 +197,6 @@ export const LoggedInPostOperationsFragment = graphql(
);
export type LoggedInPostOperations = FragmentOf;
-export const ReferencedPostFragment = graphql(
- `fragment ReferencedPost on Post {
- __typename
- id
- author {
- ...Account
- }
- feed {
- ...Feed
- }
- timestamp
- app {
- ...App
- }
- metadata {
- ...PostMetadata
- }
- actions {
- ...PostAction
- }
- operations {
- ...LoggedInPostOperations
- }
- }
- `,
- [
- AccountFragment,
- AppFragment,
- FeedFragment,
- PostMetadataFragment,
- PostActionFragment,
- LoggedInPostOperationsFragment,
- ],
-);
-
export const PostStatsFragment = graphql(
`fragment PostStats on PostStats {
__typename
@@ -238,80 +210,179 @@ export const PostStatsFragment = graphql(
);
export type PostStats = FragmentOf;
-export const PostFragment = graphql(
- `fragment Post on Post {
+export const PostFieldsFragment = graphql(
+ `fragment PostFields on Post {
+ __typename
+ id
+ 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
- author {
- ...Account
- }
- feed {
- ...Feed
- }
timestamp
slug
- stats {
- ...PostStats
- }
app {
...App
}
+ feed {
+ ...Feed
+ }
metadata {
...PostMetadata
}
- root {
- ...ReferencedPost
- }
- quoteOf {
- ...ReferencedPost
- }
- commentOn {
- ...ReferencedPost
- }
- actions {
- ...PostAction
- }
operations {
...LoggedInPostOperations
}
- }
- `,
+ stats {
+ ...PostStats
+ }
+ }`,
[
- AccountFragment,
AppFragment,
FeedFragment,
- PostMetadataFragment,
- PostActionFragment,
- PostStatsFragment,
- ReferencedPostFragment,
LoggedInPostOperationsFragment,
+ FullPostMetadataFragment,
+ PostStatsFragment,
],
);
-export type Post = FragmentOf;
+/**
+ * @deprecated Define your own FullPostFieldsFragment instead using {@link graphql} and {@link FragmentOf}.
+ */
+export type FullPostFields = FragmentOf;
+
+export const PostFragment = partial(
+ `fragment Post on Post {
+ ${'...PostFields'}
+
+ author {
+ ...Account
+ }
+ root {
+ ${'...PostFields'}
+
+ author {
+ ...Account
+ }
+ }
+ quoteOf {
+ ${'...PostFields'}
+
+ author {
+ ...Account
+ }
+ }
+ commentOn {
+ ${'...PostFields'}
+
+ author {
+ ...Account
+ }
+ }
+ }`,
+);
+
+export type Post<
+ TPostFields extends PostFields = PostFields,
+ TAccount extends Account = Account,
+> = PartialFragmentOf<
+ typeof PostFragment,
+ [FragmentDocumentFor, FragmentDocumentFor]
+>;
-// operations: LoggedInPostOperations
-export const RepostFragment = graphql(
+export const RepostFragment = partial(
`fragment Repost on Repost {
__typename
id
+ author {
+ ...Account
+ }
+ isDeleted
+ timestamp
+ app {
+ ...App
+ }
+ repostOf {
+ ${'...PostFields'}
+
+ author {
+ ...Account
+ }
+
+ root {
+ ${'...PostFields'}
+
+ author {
+ ...Account
+ }
+ }
+ quoteOf {
+ ${'...PostFields'}
+
+ author {
+ ...Account
+ }
+ }
+ commentOn {
+ ${'...PostFields'}
+
+ author {
+ ...Account
+ }
+ }
+ }
}`,
- [],
+ [AppFragment],
);
-export type Repost = FragmentOf;
-export const AnyPostFragment = graphql(
+export type Repost<
+ TPostFields extends PostFields = PostFields,
+ TAccount extends Account = Account,
+> = PartialFragmentOf<
+ typeof RepostFragment,
+ [FragmentDocumentFor, FragmentDocumentFor]
+>;
+
+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 299cbdd0a..0ee9cd7fe 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;
@@ -164,15 +166,7 @@ export const graphql = initGraphQLTada<{
};
}>();
-/**
- * @internal
- */
-export type RequestOf = Document extends DocumentDecoration<
- unknown,
- { request: infer Request }
->
- ? Request
- : never;
+export type { FragmentOf, TadaDocumentNode };
/**
* @internal
@@ -184,82 +178,136 @@ 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;
-export type RequestFrom = RequestOf>;
+type TypedSelectionSet = { __typename: TTypename };
-// biome-ignore lint/suspicious/noExplicitAny: simplifies necessary type assertions
-export type StandardDocumentNode = TadaDocumentNode<
- StandardData,
- { request: Request }
+/**
+ * @internal
+ */
+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;
+ }
>;
+/**
+ * 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;
}
-/**
- * @internal
- */
+// 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<
+ In extends string = string,
+ StaticFragments extends FragmentShape[] = [],
+> = (
+ dynamicFragments: DynamicFragments,
+) => GetDocumentNode;
+
export type DynamicFragmentOf<
- Document,
- DynamicNodes extends AnyGqlNode[],
-> = Document extends DynamicFragmentDocument
- ? FragmentOf>>
+ Document extends DynamicDocument,
+ DynamicFragments extends FragmentShape[],
+> = Document extends DynamicDocument
+ ? 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}"`,
- );
- }
+/**
+ * @internal
+ */
+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;