diff --git a/packages/frontend/admin/package.json b/packages/frontend/admin/package.json
index 9f969a735ef71..c3c97b72cf6c2 100644
--- a/packages/frontend/admin/package.json
+++ b/packages/frontend/admin/package.json
@@ -66,7 +66,6 @@
"update-shadcn": "shadcn-ui add -p src/components/ui"
},
"exports": {
- "./utils": "./src/utils.ts",
- "./components/ui/*": "./src/components/ui/*.tsx"
+ "./*": "./src/*"
}
}
diff --git a/packages/frontend/admin/src/app.tsx b/packages/frontend/admin/src/app.tsx
index c17f25fe0958e..199452062ed25 100644
--- a/packages/frontend/admin/src/app.tsx
+++ b/packages/frontend/admin/src/app.tsx
@@ -1,20 +1,5 @@
import { Toaster } from '@affine/admin/components/ui/sonner';
-import {
- configureCloudModule,
- DefaultServerService,
-} from '@affine/core/modules/cloud';
-import { configureLocalStorageStateStorageImpls } from '@affine/core/modules/storage';
-import { configureUrlModule } from '@affine/core/modules/url';
import { wrapCreateBrowserRouter } from '@sentry/react';
-import {
- configureGlobalContextModule,
- configureGlobalStorageModule,
- configureLifecycleModule,
- Framework,
- FrameworkRoot,
- FrameworkScope,
- LifecycleService,
-} from '@toeverything/infra';
import { useEffect } from 'react';
import {
createBrowserRouter as reactRouterCreateBrowserRouter,
@@ -124,38 +109,18 @@ export const router = _createBrowserRouter(
}
);
-const framework = new Framework();
-configureLifecycleModule(framework);
-configureLocalStorageStateStorageImpls(framework);
-configureGlobalStorageModule(framework);
-configureGlobalContextModule(framework);
-configureUrlModule(framework);
-configureCloudModule(framework);
-const frameworkProvider = framework.provider();
-
-// setup application lifecycle events, and emit application start event
-window.addEventListener('focus', () => {
- frameworkProvider.get(LifecycleService).applicationFocus();
-});
-frameworkProvider.get(LifecycleService).applicationStart();
-const serverService = frameworkProvider.get(DefaultServerService);
-
export const App = () => {
return (
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
);
};
diff --git a/packages/frontend/admin/src/modules/accounts/components/data-table-toolbar.tsx b/packages/frontend/admin/src/modules/accounts/components/data-table-toolbar.tsx
index 0a9fb406f52e9..270bd82e02f3d 100644
--- a/packages/frontend/admin/src/modules/accounts/components/data-table-toolbar.tsx
+++ b/packages/frontend/admin/src/modules/accounts/components/data-table-toolbar.tsx
@@ -1,6 +1,6 @@
import { Button } from '@affine/admin/components/ui/button';
import { Input } from '@affine/admin/components/ui/input';
-import { useQuery } from '@affine/core/components/hooks/use-query';
+import { useQuery } from '@affine/admin/use-query';
import { getUserByEmailQuery } from '@affine/graphql';
import { PlusIcon } from 'lucide-react';
import type { SetStateAction } from 'react';
diff --git a/packages/frontend/admin/src/modules/accounts/components/use-user-management.ts b/packages/frontend/admin/src/modules/accounts/components/use-user-management.ts
index f062ecf4c4331..94ee0bcca5c50 100644
--- a/packages/frontend/admin/src/modules/accounts/components/use-user-management.ts
+++ b/packages/frontend/admin/src/modules/accounts/components/use-user-management.ts
@@ -1,9 +1,9 @@
-import { useAsyncCallback } from '@affine/core/components/hooks/affine-async-hooks';
import {
useMutateQueryResource,
useMutation,
-} from '@affine/core/components/hooks/use-mutation';
-import { useQuery } from '@affine/core/components/hooks/use-query';
+} from '@affine/admin/use-mutation';
+import { useQuery } from '@affine/admin/use-query';
+import { useAsyncCallback } from '@affine/core/components/hooks/affine-async-hooks';
import {
createChangePasswordUrlMutation,
createUserMutation,
diff --git a/packages/frontend/admin/src/modules/accounts/use-user-list.ts b/packages/frontend/admin/src/modules/accounts/use-user-list.ts
index 58656010c2199..6b1abeaadfa26 100644
--- a/packages/frontend/admin/src/modules/accounts/use-user-list.ts
+++ b/packages/frontend/admin/src/modules/accounts/use-user-list.ts
@@ -1,4 +1,4 @@
-import { useQuery } from '@affine/core/components/hooks/use-query';
+import { useQuery } from '@affine/admin/use-query';
import { listUsersQuery } from '@affine/graphql';
import { useState } from 'react';
diff --git a/packages/frontend/admin/src/modules/ai/use-prompt.ts b/packages/frontend/admin/src/modules/ai/use-prompt.ts
index cc41bcfa1c6ca..3c84273c6c6a6 100644
--- a/packages/frontend/admin/src/modules/ai/use-prompt.ts
+++ b/packages/frontend/admin/src/modules/ai/use-prompt.ts
@@ -1,9 +1,9 @@
-import { useAsyncCallback } from '@affine/core/components/hooks/affine-async-hooks';
import {
useMutateQueryResource,
useMutation,
-} from '@affine/core/components/hooks/use-mutation';
-import { useQuery } from '@affine/core/components/hooks/use-query';
+} from '@affine/admin/use-mutation';
+import { useQuery } from '@affine/admin/use-query';
+import { useAsyncCallback } from '@affine/core/components/hooks/affine-async-hooks';
import { getPromptsQuery, updatePromptMutation } from '@affine/graphql';
import { toast } from 'sonner';
diff --git a/packages/frontend/admin/src/modules/common.ts b/packages/frontend/admin/src/modules/common.ts
index 2447cb6c32eb0..046e44fc98448 100644
--- a/packages/frontend/admin/src/modules/common.ts
+++ b/packages/frontend/admin/src/modules/common.ts
@@ -1,5 +1,3 @@
-import { useMutateQueryResource } from '@affine/core/components/hooks/use-mutation';
-import { useQuery } from '@affine/core/components/hooks/use-query';
import type { GetCurrentUserFeaturesQuery } from '@affine/graphql';
import {
adminServerConfigQuery,
@@ -7,6 +5,9 @@ import {
getCurrentUserFeaturesQuery,
} from '@affine/graphql';
+import { useMutateQueryResource } from '../use-mutation';
+import { useQuery } from '../use-query';
+
export const useServerConfig = () => {
const { data } = useQuery({
query: adminServerConfigQuery,
diff --git a/packages/frontend/admin/src/modules/config/use-server-service-configs.ts b/packages/frontend/admin/src/modules/config/use-server-service-configs.ts
index a027e3598bf98..bcd439ca0f81e 100644
--- a/packages/frontend/admin/src/modules/config/use-server-service-configs.ts
+++ b/packages/frontend/admin/src/modules/config/use-server-service-configs.ts
@@ -1,4 +1,4 @@
-import { useQueryImmutable } from '@affine/core/components/hooks/use-query';
+import { useQueryImmutable } from '@affine/admin/use-query';
import { getServerServiceConfigsQuery } from '@affine/graphql';
import { useMemo } from 'react';
diff --git a/packages/frontend/admin/src/modules/settings/use-get-server-runtime-config.ts b/packages/frontend/admin/src/modules/settings/use-get-server-runtime-config.ts
index 594efa58fff10..cc0988d399470 100644
--- a/packages/frontend/admin/src/modules/settings/use-get-server-runtime-config.ts
+++ b/packages/frontend/admin/src/modules/settings/use-get-server-runtime-config.ts
@@ -1,4 +1,4 @@
-import { useQuery } from '@affine/core/components/hooks/use-query';
+import { useQuery } from '@affine/admin/use-query';
import { getServerRuntimeConfigQuery } from '@affine/graphql';
import { useMemo } from 'react';
diff --git a/packages/frontend/admin/src/modules/settings/use-update-server-runtime-config.ts b/packages/frontend/admin/src/modules/settings/use-update-server-runtime-config.ts
index 3c7232fd68b04..7c10f921505b1 100644
--- a/packages/frontend/admin/src/modules/settings/use-update-server-runtime-config.ts
+++ b/packages/frontend/admin/src/modules/settings/use-update-server-runtime-config.ts
@@ -1,9 +1,9 @@
-import { notify } from '@affine/component';
-import { useAsyncCallback } from '@affine/core/components/hooks/affine-async-hooks';
import {
useMutateQueryResource,
useMutation,
-} from '@affine/core/components/hooks/use-mutation';
+} from '@affine/admin/use-mutation';
+import { notify } from '@affine/component';
+import { useAsyncCallback } from '@affine/core/components/hooks/affine-async-hooks';
import {
getServerRuntimeConfigQuery,
updateServerRuntimeConfigsMutation,
diff --git a/packages/frontend/admin/src/use-mutation.ts b/packages/frontend/admin/src/use-mutation.ts
new file mode 100644
index 0000000000000..b17150e5fffc5
--- /dev/null
+++ b/packages/frontend/admin/src/use-mutation.ts
@@ -0,0 +1,94 @@
+import type {
+ GraphQLQuery,
+ MutationOptions,
+ QueryResponse,
+ QueryVariables,
+ RecursiveMaybeFields,
+} from '@affine/graphql';
+import type { GraphQLError } from 'graphql';
+import { useMemo } from 'react';
+import type { Key } from 'swr';
+import { useSWRConfig } from 'swr';
+import type {
+ SWRMutationConfiguration,
+ SWRMutationResponse,
+} from 'swr/mutation';
+import useSWRMutation from 'swr/mutation';
+
+import { gqlFetcher } from './use-query';
+
+/**
+ * A useSWRMutation wrapper for sending graphql mutations
+ *
+ * @example
+ *
+ * ```ts
+ * import { someMutation } from '@affine/graphql'
+ *
+ * const { trigger } = useMutation({
+ * mutation: someMutation,
+ * })
+ *
+ * trigger({ name: 'John Doe' })
+ */
+export function useMutation(
+ options: Omit, 'variables'>,
+ config?: Omit<
+ SWRMutationConfiguration<
+ QueryResponse,
+ GraphQLError,
+ K,
+ QueryVariables
+ >,
+ 'fetcher'
+ >
+): SWRMutationResponse<
+ QueryResponse,
+ GraphQLError,
+ K,
+ QueryVariables
+>;
+export function useMutation(
+ options: Omit, 'variables'>,
+ config?: any
+) {
+ return useSWRMutation(
+ () => ['cloud', options.mutation.id],
+ (_: unknown[], { arg }: { arg: any }) =>
+ gqlFetcher({
+ ...options,
+ query: options.mutation,
+ variables: arg,
+ }),
+ config
+ );
+}
+
+// use this to revalidate all queries that match the filter
+export const useMutateQueryResource = () => {
+ const { mutate } = useSWRConfig();
+ const revalidateResource = useMemo(
+ () =>
+ (
+ query: Q,
+ varsFilter: (
+ vars: RecursiveMaybeFields>
+ ) => boolean = _vars => true
+ ) => {
+ return mutate(key => {
+ const res =
+ Array.isArray(key) &&
+ key[0] === 'cloud' &&
+ key[1] === query.id &&
+ varsFilter(key[2]);
+ if (res) {
+ console.debug('revalidate resource', key);
+ }
+ return res;
+ });
+ },
+ [mutate]
+ );
+
+ return revalidateResource;
+};
diff --git a/packages/frontend/admin/src/use-query.ts b/packages/frontend/admin/src/use-query.ts
new file mode 100644
index 0000000000000..414a1a6b18dee
--- /dev/null
+++ b/packages/frontend/admin/src/use-query.ts
@@ -0,0 +1,131 @@
+import {
+ gqlFetcherFactory,
+ type GraphQLQuery,
+ type QueryOptions,
+ type QueryResponse,
+} from '@affine/graphql';
+import type { GraphQLError } from 'graphql';
+import { useCallback, useMemo } from 'react';
+import type { SWRConfiguration, SWRResponse } from 'swr';
+import useSWR from 'swr';
+import useSWRImmutable from 'swr/immutable';
+import useSWRInfinite from 'swr/infinite';
+
+/**
+ * A `useSWR` wrapper for sending graphql queries
+ *
+ * @example
+ *
+ * ```ts
+ * import { someQuery, someQueryWithNoVars } from '@affine/graphql'
+ *
+ * const swrResponse1 = useQuery({
+ * query: workspaceByIdQuery,
+ * variables: { id: '1' }
+ * })
+ *
+ * const swrResponse2 = useQuery({
+ * query: someQueryWithNoVars
+ * })
+ * ```
+ */
+type useQueryFn = (
+ options?: QueryOptions,
+ config?: Omit<
+ SWRConfiguration<
+ QueryResponse,
+ GraphQLError,
+ (options: QueryOptions) => Promise>
+ >,
+ 'fetcher'
+ >
+) => SWRResponse<
+ QueryResponse,
+ GraphQLError,
+ {
+ suspense: true;
+ }
+>;
+
+const createUseQuery =
+ (immutable: boolean): useQueryFn =>
+ (options, config) => {
+ const configWithSuspense: SWRConfiguration = useMemo(
+ () => ({
+ suspense: true,
+ ...config,
+ }),
+ [config]
+ );
+
+ const useSWRFn = immutable ? useSWRImmutable : useSWR;
+ return useSWRFn(
+ options ? () => ['cloud', options.query.id, options.variables] : null,
+ options ? () => gqlFetcher(options) : null,
+ configWithSuspense
+ );
+ };
+
+export const useQuery = createUseQuery(false);
+export const useQueryImmutable = createUseQuery(true);
+
+export const gqlFetcher = gqlFetcherFactory('/graphql', window.fetch);
+
+export function useQueryInfinite(
+ options: Omit, 'variables'> & {
+ getVariables: (
+ pageIndex: number,
+ previousPageData: QueryResponse
+ ) => QueryOptions['variables'];
+ },
+ config?: Omit<
+ SWRConfiguration<
+ QueryResponse,
+ GraphQLError | GraphQLError[],
+ (options: QueryOptions) => Promise>
+ >,
+ 'fetcher'
+ >
+) {
+ const configWithSuspense: SWRConfiguration = useMemo(
+ () => ({
+ suspense: true,
+ ...config,
+ }),
+ [config]
+ );
+
+ const { data, setSize, size, error } = useSWRInfinite<
+ QueryResponse,
+ GraphQLError | GraphQLError[]
+ >(
+ (pageIndex: number, previousPageData: QueryResponse) => [
+ 'cloud',
+ options.query.id,
+ options.getVariables(pageIndex, previousPageData),
+ ],
+ async ([_, __, variables]) => {
+ const params = { ...options, variables } as QueryOptions;
+ return gqlFetcher(params);
+ },
+ configWithSuspense
+ );
+
+ const loadingMore = size > 0 && data && !data[size - 1];
+
+ // TODO(@Peng): find a generic way to know whether or not there are more items to load
+ const loadMore = useCallback(() => {
+ if (loadingMore) {
+ return;
+ }
+ setSize(size => size + 1).catch(err => {
+ console.error(err);
+ });
+ }, [loadingMore, setSize]);
+ return {
+ data,
+ error,
+ loadingMore,
+ loadMore,
+ };
+}