Changes
22 changed files (+847/-40)
-
-
@@ -25,11 +25,20 @@ method: { input: Input; output: Output } & DescMethod,handler: ( request: MessageShape<Input>, ) => Promise<MessageInitShape<Output>> | MessageInitShape<Output>, { delayMs = 0 }: { delayMs?: number } = {}, ): MethodMock { return { service: method.parent.typeName, method: method.name, async handler(data) { if (delayMs > 0) { await new Promise<void>((resolve) => { setTimeout(() => { resolve(); }, delayMs); }); } const request = fromBinary(method.input, data); return toBinary(method.output, create(method.output, await handler(request)));
-
@@ -52,7 +61,10 @@ } satisfies ProtoRPC;return function (Story) { return ( <ProtoRPCProvider rpc={rpc}> <ProtoRPCProvider rpc={rpc} config={{ defaultOptions: { queries: { retry: false } } }} > <Story /> </ProtoRPCProvider> );
-
-
-
@@ -0,0 +1,28 @@/** * SPDX-FileCopyrightText: 2024 Shota FUJI <pockawoooh@gmail.com> * SPDX-License-Identifier: AGPL-3.0-only */ :where(.textarea) { all: unset; } .textarea { line-height: var(--line-height-1); resize: none; display: block; width: 100%; border: 1px solid var(--gray-6); padding-bottom: var(--space-5); white-space: pre; background-color: var(--background-surface); border-radius: var(--radius-2); color: var(--gray-12); } @media (hover: hover) { .textarea:focus-visible { outline: 2px solid var(--focus-8); outline-offset: -1px; } }
-
-
-
@@ -0,0 +1,24 @@// SPDX-FileCopyrightText: 2024 Shota FUJI <pockawoooh@gmail.com> // SPDX-License-Identifier: AGPL-3.0-only import type { Meta, StoryObj } from "@storybook/react"; import { CopyableText } from "./CopyableText"; export default { component: CopyableText, args: { children: JSON.stringify({ foo: { bar: "baz" } }, null, 2), mono: false, }, } satisfies Meta<typeof CopyableText>; type Story = StoryObj<typeof CopyableText>; export const Defaults: Story = {}; export const Mono: Story = { args: { mono: true, }, };
-
-
-
@@ -0,0 +1,75 @@// SPDX-FileCopyrightText: 2024 Shota FUJI <pockawoooh@gmail.com> // SPDX-License-Identifier: AGPL-3.0-only import { CopyIcon } from "@radix-ui/react-icons"; import { Box, type BoxProps, IconButton, Text, Tooltip } from "@radix-ui/themes"; import { type FC, useMemo, useRef, useState } from "react"; import css from "./CopyableText.module.css"; export interface CopyableTextProps extends Omit<BoxProps, "children"> { children: string; mono?: boolean; } export const CopyableText: FC<CopyableTextProps> = ({ children, mono = false, ...rest }) => { const ref = useRef<HTMLTextAreaElement>(null); // TODO: 結果をユーザに通知する const [_clipboardWriteResult, setClipboardWriteResult] = useState< { ok: false; error: unknown } | { ok: true } | null >(); const rows = useMemo(() => { const lines = children.split("\n"); return Math.min(10, Math.max(3, lines.length)); }, [children]); return ( <Box position="relative" {...rest}> <Box asChild p="2"> <Text asChild size="2"> <textarea ref={ref} className={css.textarea} value={children} readOnly rows={rows} style={{ fontFamily: mono ? "var(--code-font-family)" : "var(--default-font-family)", }} /> </Text> </Box> <Tooltip content="クリップボードにコピー"> <Box asChild position="absolute" bottom="2" right="2"> <IconButton size="1" variant="surface" color="gray" onClick={async (event) => { event.preventDefault(); event.stopPropagation(); try { await navigator.clipboard.writeText(children); setClipboardWriteResult({ ok: true }); } catch (error) { setClipboardWriteResult({ ok: false, error }); } }} > <CopyIcon /> </IconButton> </Box> </Tooltip> </Box> ); };
-
-
-
@@ -0,0 +1,8 @@// SPDX-FileCopyrightText: 2024 Shota FUJI <pockawoooh@gmail.com> // SPDX-License-Identifier: AGPL-3.0-only export { ErrorCallout } from "./ErrorCallout/ErrorCallout.tsx"; export type { ErrorCalloutProps } from "./ErrorCallout/ErrorCallout.tsx"; export { Managed as ManagedErrorCallout } from "./ErrorCallout/Managed.tsx"; export type { ManagedProps as ManagedErrorCalloutProps } from "./ErrorCallout/Managed.tsx";
-
-
-
@@ -0,0 +1,33 @@// SPDX-FileCopyrightText: 2024 Shota FUJI <pockawoooh@gmail.com> // SPDX-License-Identifier: AGPL-3.0-only import { Button } from "@radix-ui/themes"; import type { Meta, StoryObj } from "@storybook/react"; import { ErrorCallout } from "./ErrorCallout.tsx"; export default { component: ErrorCallout, args: { title: "タイトル", children: "説明", actions: <Button size="1">アクション</Button>, }, } satisfies Meta<typeof ErrorCallout>; type Story = StoryObj<typeof ErrorCallout>; export const Defaults: Story = {}; export const Warning: Story = { args: { severity: "warning", }, }; export const TitleOnly: Story = { args: { children: null, actions: null, }, };
-
-
-
@@ -0,0 +1,47 @@// SPDX-FileCopyrightText: 2024 Shota FUJI <pockawoooh@gmail.com> // SPDX-License-Identifier: AGPL-3.0-only import { ExclamationTriangleIcon } from "@radix-ui/react-icons"; import { Callout, Flex, Text } from "@radix-ui/themes"; import { type FC, type ReactNode } from "react"; export interface ErrorCalloutProps { className?: string | undefined; severity?: "danger" | "warning"; title: ReactNode; children?: ReactNode; actions?: ReactNode; } export const ErrorCallout: FC<ErrorCalloutProps> = ({ className, severity = "danger", title, children, actions, }) => { return ( <Callout.Root className={className} color={severity === "danger" ? "red" : "amber"}> <Callout.Icon> <ExclamationTriangleIcon /> </Callout.Icon> <Flex asChild direction="column" gap="3" width="100%"> <Callout.Text> <Flex as="span" direction="column" gap="1"> <Text weight="bold">{title}</Text> {children && <Text>{children}</Text>} </Flex> {actions && ( <Flex as="span" justify="start" gap="2"> {actions} </Flex> )} </Callout.Text> </Flex> </Callout.Root> ); };
-
-
-
@@ -0,0 +1,36 @@// SPDX-FileCopyrightText: 2024 Shota FUJI <pockawoooh@gmail.com> // SPDX-License-Identifier: AGPL-3.0-only import { type FC } from "react"; import { type ErrorCalloutProps } from "./ErrorCallout.tsx"; import * as SystemError from "./errors/SystemError.tsx"; import * as IllegalMessageError from "./errors/IllegalMessageError.tsx"; import * as GenericError from "./errors/GenericError.tsx"; import * as UnhandledMessageError from "./errors/UnhandledMessageError.tsx"; import * as NonErrorThrownError from "./errors/NonErrorThrownError.tsx"; export interface ManagedProps extends ErrorCalloutProps { error: unknown; } export const Managed: FC<ManagedProps> = ({ error, ...rest }) => { if (SystemError.is(error)) { return <SystemError.Callout error={error} {...rest} />; } if (UnhandledMessageError.is(error)) { return <UnhandledMessageError.Callout error={error} {...rest} />; } if (IllegalMessageError.is(error)) { return <IllegalMessageError.Callout error={error} {...rest} />; } if (GenericError.is(error)) { return <GenericError.Callout error={error} {...rest} />; } return <NonErrorThrownError.Callout error={error} {...rest} />; };
-
-
-
@@ -0,0 +1,18 @@// SPDX-FileCopyrightText: 2024 Shota FUJI <pockawoooh@gmail.com> // SPDX-License-Identifier: AGPL-3.0-only import type { Meta, StoryObj } from "@storybook/react"; import { Callout } from "./GenericError.tsx"; export default { component: Callout, args: { title: "エラー", error: new Error("Foo Bar"), }, } satisfies Meta<typeof Callout>; type Story = StoryObj<typeof Callout>; export const Defaults: Story = {};
-
-
-
@@ -0,0 +1,69 @@// SPDX-FileCopyrightText: 2024 Shota FUJI <pockawoooh@gmail.com> // SPDX-License-Identifier: AGPL-3.0-only import { Button, Dialog, Heading, Flex, Text } from "@radix-ui/themes"; import { type FC } from "react"; import { CopyableText } from "../../CopyableText.tsx"; import { ErrorCallout, type ErrorCalloutProps } from "../ErrorCallout.tsx"; export function is(x: unknown): x is Error { return x instanceof Error; } export interface CalloutProps extends ErrorCalloutProps { error: Error; } export const Callout: FC<CalloutProps> = ({ error, actions, children, ...rest }) => { return ( <Dialog.Root> <ErrorCallout {...rest} actions={ <> {actions} <Dialog.Trigger> <Button size="1" variant="outline"> 詳細 </Button> </Dialog.Trigger> </> } > {children || "アプリケーションエラーが発生しました"} </ErrorCallout> <Dialog.Content> <Dialog.Title>アプリケーションエラー</Dialog.Title> <Dialog.Description size="2"> アプリケーションコードの実行中に予期せぬエラーが発生し、処理が中断されました。 </Dialog.Description> <Text as="p" size="2" mt="2"> ブラウザやアプリケーションの不具合、古いブラウザを利用している、ブラウザの拡張機能がアプリケーションの動作に対して影響を与えている、といった可能性が考えられます。 また、ブラウザの設定で一部機能が制限されていたりネットワークの状況が悪い場合もこのエラーが起こることがあります。 </Text> <Heading as="h2" size="3" mt="5"> エラー内容 </Heading> <CopyableText mono mt="3" mb="1"> {error.message + (error.stack ? "\n" + error.stack : "")} </CopyableText> <Text as="p" color="gray" size="1" mt="3"> 上記は問い合わせの際に必要な情報となります。 </Text> <Flex justify="end" mt="5"> <Dialog.Close> <Button>閉じる</Button> </Dialog.Close> </Flex> </Dialog.Content> </Dialog.Root> ); };
-
-
-
@@ -0,0 +1,20 @@// SPDX-FileCopyrightText: 2024 Shota FUJI <pockawoooh@gmail.com> // SPDX-License-Identifier: AGPL-3.0-only import type { Meta, StoryObj } from "@storybook/react"; import { IllegalMessageError } from "../../../errors/IllegalMessageError.ts"; import { Callout } from "./IllegalMessageError.tsx"; export default { component: Callout, args: { title: "エラー", error: new IllegalMessageError({ foo: "bar" }), }, } satisfies Meta<typeof Callout>; type Story = StoryObj<typeof Callout>; export const Defaults: Story = {};
-
-
-
@@ -0,0 +1,70 @@// SPDX-FileCopyrightText: 2024 Shota FUJI <pockawoooh@gmail.com> // SPDX-License-Identifier: AGPL-3.0-only import { Button, Dialog, Heading, Flex, Text } from "@radix-ui/themes"; import { type FC } from "react"; import { IllegalMessageError } from "../../../errors/IllegalMessageError.ts"; import { CopyableText } from "../../CopyableText.tsx"; import { ErrorCallout, type ErrorCalloutProps } from "../ErrorCallout.tsx"; export function is(x: unknown): x is IllegalMessageError { return x instanceof IllegalMessageError; } export interface CalloutProps extends ErrorCalloutProps { error: IllegalMessageError; } export const Callout: FC<CalloutProps> = ({ error, actions, children, ...rest }) => { return ( <Dialog.Root> <ErrorCallout {...rest} actions={ <> {actions} <Dialog.Trigger> <Button size="1" variant="outline"> 詳細 </Button> </Dialog.Trigger> </> } > {children || "処理結果を読み込めませんでした"} </ErrorCallout> <Dialog.Content> <Dialog.Title>不正処理電文エラー</Dialog.Title> <Dialog.Description size="2"> 処理結果を受信しましたが、結果データ内に必須な項目が抜けているなどして表示が行えない場合のエラーです。 </Dialog.Description> <Text as="p" size="2" mt="2"> アプリケーションの不具合や想定されていないデータが入力されたなどの原因が考えられます。 入力内容を変えて再試行すると解消する場合があります。 </Text> <Heading as="h2" size="3" mt="5"> 受信電文 </Heading> <CopyableText mono mt="3"> {JSON.stringify(error.data, null, 2)} </CopyableText> <Text as="p" color="gray" size="1" mt="3"> 上記は問い合わせの際に必要な情報となります。 </Text> <Flex justify="end" mt="5"> <Dialog.Close> <Button>閉じる</Button> </Dialog.Close> </Flex> </Dialog.Content> </Dialog.Root> ); };
-
-
-
@@ -0,0 +1,40 @@// SPDX-FileCopyrightText: 2024 Shota FUJI <pockawoooh@gmail.com> // SPDX-License-Identifier: AGPL-3.0-only import type { Meta, StoryObj } from "@storybook/react"; import { Callout } from "./NonErrorThrownError.tsx"; export default { component: Callout, args: { title: "エラー", }, } satisfies Meta<typeof Callout>; type Story = StoryObj<typeof Callout>; export const String: Story = { args: { error: "String", }, }; export const Number: Story = { args: { error: 1, }, }; export const Boolean: Story = { args: { error: true, }, }; export const Symbol_: Story = { name: "Symbol", args: { error: Symbol(), }, };
-
-
-
@@ -0,0 +1,68 @@// SPDX-FileCopyrightText: 2024 Shota FUJI <pockawoooh@gmail.com> // SPDX-License-Identifier: AGPL-3.0-only import { Button, Dialog, Heading, Flex, Text } from "@radix-ui/themes"; import { type FC } from "react"; import { CopyableText } from "../../CopyableText.tsx"; import { ErrorCallout, type ErrorCalloutProps } from "../ErrorCallout.tsx"; export interface CalloutProps extends ErrorCalloutProps { error: unknown; } export const Callout: FC<CalloutProps> = ({ error, actions, children, ...rest }) => { return ( <Dialog.Root> <ErrorCallout {...rest} actions={ <> {actions} <Dialog.Trigger> <Button size="1" variant="outline"> 詳細 </Button> </Dialog.Trigger> </> } > {children || "予期せぬエラーが発生しました"} </ErrorCallout> <Dialog.Content> <Dialog.Title>異常実行例外</Dialog.Title> <Dialog.Description size="2"> 何かしらの異常によって処理が中断されましたが、補足されたエラーデータが不正なため詳細を表示できない場合のエラーです。 </Dialog.Description> <Text as="p" size="2" mt="2"> ブラウザの拡張機能がアプリケーションの動作に対して影響を与えている、ネットワークプロキシや不正なネットワーク設定により本来とは異なるアプリケーションコードが読み込まれている、アプリケーションの不具合、といった可能性が考えられます。 </Text> <Heading as="h2" size="3" mt="5"> エラー内容 </Heading> <CopyableText mono mt="3" mb="1"> {typeof error === "string" || typeof error === "boolean" ? JSON.stringify(error) : typeof error === "number" ? error.toString(10) : `<${typeof error}>`} </CopyableText> <Text as="p" color="gray" size="1" mt="3"> 上記は問い合わせの際に必要な情報となります。 </Text> <Flex justify="end" mt="5"> <Dialog.Close> <Button>閉じる</Button> </Dialog.Close> </Flex> </Dialog.Content> </Dialog.Root> ); };
-
-
-
@@ -0,0 +1,29 @@// SPDX-FileCopyrightText: 2024 Shota FUJI <pockawoooh@gmail.com> // SPDX-License-Identifier: AGPL-3.0-only import { create } from "@bufbuild/protobuf"; import { SystemErrorSchema } from "@yamori/proto/yamori/error/v1/system_error_pb.js"; import type { Meta, StoryObj } from "@storybook/react"; import { Callout } from "./SystemError.tsx"; export default { component: Callout, args: { title: "エラー", error: create(SystemErrorSchema, { code: "SAMPLE_ERR", message: "This is sample error message.", }), }, } satisfies Meta<typeof Callout>; type Story = StoryObj<typeof Callout>; export const Defaults: Story = {}; export const NoDetails: Story = { args: { error: create(SystemErrorSchema, {}), }, };
-
-
-
@@ -0,0 +1,81 @@// SPDX-FileCopyrightText: 2024 Shota FUJI <pockawoooh@gmail.com> // SPDX-License-Identifier: AGPL-3.0-only import { isMessage } from "@bufbuild/protobuf"; import { Button, Code, DataList, Dialog, Flex, Text } from "@radix-ui/themes"; import { SystemErrorSchema, type SystemError, } from "@yamori/proto/yamori/error/v1/system_error_pb.js"; import { type FC } from "react"; import { ErrorCallout, type ErrorCalloutProps } from "../ErrorCallout.tsx"; export function is(x: unknown): x is SystemError { return isMessage(x, SystemErrorSchema); } export interface CalloutProps extends ErrorCalloutProps { error: SystemError; } export const Callout: FC<CalloutProps> = ({ error, actions, children, ...rest }) => { return ( <Dialog.Root> <ErrorCallout {...rest} actions={ <> {actions} <Dialog.Trigger> <Button size="1" variant="outline"> 詳細 </Button> </Dialog.Trigger> </> } > {children || ( <> システムエラーが発生しました{error.code ? ` (コード: ${error.code})` : null} </> )} </ErrorCallout> <Dialog.Content> <Dialog.Title>システムエラー</Dialog.Title> <Dialog.Description size="2"> 処理中に予期せぬエラーが発生し、処理が中断された際のエラーです。 </Dialog.Description> <Text as="p" size="2" mt="2"> アプリケーションの不具合やネットワーク障害、データが不正など様々な原因が考えられます。 原因が一時的な場合は再試行することで解消することがあります。 </Text> <DataList.Root mt="5" orientation={{ initial: "vertical", sm: "horizontal" }}> <DataList.Item> <DataList.Label>コード</DataList.Label> <DataList.Value> {error.code ? <Code>{error.code}</Code> : "---"} </DataList.Value> </DataList.Item> <DataList.Item> <DataList.Label>メッセージ</DataList.Label> <DataList.Value>{error.message || "---"}</DataList.Value> </DataList.Item> </DataList.Root> <Text as="p" color="gray" size="1" mt="5"> 上記は問い合わせの際に必要な情報となります。 </Text> <Flex justify="end" mt="5"> <Dialog.Close> <Button>閉じる</Button> </Dialog.Close> </Flex> </Dialog.Content> </Dialog.Root> ); };
-
-
-
@@ -0,0 +1,25 @@// SPDX-FileCopyrightText: 2024 Shota FUJI <pockawoooh@gmail.com> // SPDX-License-Identifier: AGPL-3.0-only import { create } from "@bufbuild/protobuf"; import { WorkspaceSchema } from "@yamori/proto/yamori/workspace/v1/workspace_pb.js"; import type { Meta, StoryObj } from "@storybook/react"; import { Callout } from "./UnhandledMessageError.tsx"; export default { component: Callout, args: { title: "エラー", error: create(WorkspaceSchema, { id: { value: "ws-foo", }, displayName: "Foo", }), }, } satisfies Meta<typeof Callout>; type Story = StoryObj<typeof Callout>; export const Defaults: Story = {};
-
-
-
@@ -0,0 +1,69 @@// SPDX-FileCopyrightText: 2024 Shota FUJI <pockawoooh@gmail.com> // SPDX-License-Identifier: AGPL-3.0-only import { type Message, isMessage } from "@bufbuild/protobuf"; import { Button, Dialog, Flex, Heading, Text } from "@radix-ui/themes"; import { type FC } from "react"; import { CopyableText } from "../../CopyableText.tsx"; import { ErrorCallout, type ErrorCalloutProps } from "../ErrorCallout.tsx"; export function is(x: unknown): x is Message { return isMessage(x); } export interface CalloutProps extends ErrorCalloutProps { error: Message; } export const Callout: FC<CalloutProps> = ({ error, actions, children, ...rest }) => { return ( <Dialog.Root> <ErrorCallout {...rest} actions={ <> {actions} <Dialog.Trigger> <Button size="1" variant="outline"> 詳細 </Button> </Dialog.Trigger> </> } > {children || "未定義のエラーが発生しました"} </ErrorCallout> <Dialog.Content> <Dialog.Title>未定義メッセージエラー</Dialog.Title> <Dialog.Description size="2"> 処理エラーを受信しましたが、エラー内容が事前に定義された内容と異なるため詳細が表示できない場合のエラーです。 </Dialog.Description> <Text as="p" size="2" mt="2"> アプリケーションの不具合となるため問い合わせてください。 </Text> <Heading as="h2" size="3" mt="5"> 受信メッセージ </Heading> <CopyableText mono mt="3"> {JSON.stringify(error, null, 2)} </CopyableText> <Text as="p" color="gray" size="1" mt="3"> 上記は問い合わせの際に必要な情報となります。 </Text> <Flex justify="end" mt="5"> <Dialog.Close> <Button>閉じる</Button> </Dialog.Close> </Flex> </Dialog.Content> </Dialog.Root> ); };
-
-
-
@@ -48,14 +48,8 @@export const SlowLoad: Story = { decorators: [ withMockedBackend([ mock(KeyValueStorageBasedWorkspaceService.method.list, async () => { await new Promise<void>((resolve) => { setTimeout(() => { resolve(); }, 1_000); }); return success; mock(KeyValueStorageBasedWorkspaceService.method.list, () => success, { delayMs: 2_000, }), ]), ],
-
@@ -91,7 +85,9 @@export const ListLoadError: Story = { decorators: [ withMockedBackend([ mock(KeyValueStorageBasedWorkspaceService.method.list, () => systemError), mock(KeyValueStorageBasedWorkspaceService.method.list, () => systemError, { delayMs: 200, }), ]), ], };
-
@@ -99,8 +95,10 @@export const FlakyListLoading: Story = { decorators: [ withMockedBackend([ mock(KeyValueStorageBasedWorkspaceService.method.list, () => Math.random() >= 0.5 ? success : systemError, mock( KeyValueStorageBasedWorkspaceService.method.list, () => (Math.random() >= 0.5 ? success : systemError), { delayMs: 200 }, ), ]), ],
-
-
-
@@ -1,13 +1,15 @@// SPDX-FileCopyrightText: 2024 Shota FUJI <pockawoooh@gmail.com> // SPDX-License-Identifier: AGPL-3.0-only import { ExclamationTriangleIcon } from "@radix-ui/react-icons"; import { Box, Callout, Flex, Spinner, Text } from "@radix-ui/themes"; import { Button, Flex, Spinner, Text } from "@radix-ui/themes"; import { ListRequestSchema } from "@yamori/proto/yamori/workspace/v1/list_request_pb.js"; import { ListResponseSchema } from "@yamori/proto/yamori/workspace/v1/list_response_pb.js"; import { type FC } from "react"; import { useMethodQuery } from "../contexts/Service.tsx"; import { IllegalMessageError } from "../errors/IllegalMessageError.ts"; import { ManagedErrorCallout } from "./ErrorCallout.ts"; import { WorkspaceList } from "./WorkspaceSelectionPage/WorkspaceList.tsx";
-
@@ -26,6 +28,16 @@ },response: { schema: ListResponseSchema, }, mapResponse(resp) { switch (resp.result.case) { case "ok": return resp.result.value; case "systemError": throw resp.result.value; default: throw new IllegalMessageError(resp); } }, }); if (list.status === "pending") {
-
@@ -38,27 +50,38 @@ );} if (!list.data) { // TODO: Handle error return ( <Box> <Text>{String(list.error)}</Text> </Box> <ManagedErrorCallout title="一覧の取得に失敗しました" error={list.error} actions={ <Button size="1" loading={list.isFetching} onClick={() => void list.refetch()}> 再取得 </Button> } /> ); } return ( <Flex direction="column"> <Flex direction="column" gap="5"> {!!list.error && ( <Callout.Root color="red"> <Callout.Icon> <ExclamationTriangleIcon /> </Callout.Icon> <Callout.Text>一覧の取得に失敗しました</Callout.Text> </Callout.Root> <ManagedErrorCallout severity="warning" title="最新の一覧の取得に失敗しました" error={list.error} actions={ <Button size="1" loading={list.isFetching} onClick={() => void list.refetch()} > 再試行 </Button> } /> )} {list.data.result.case === "ok" && ( <WorkspaceList workspaces={list.data.result.value.workspaces} /> )} <WorkspaceList workspaces={list.data.workspaces} /> </Flex> ); };
-
-
-
@@ -10,6 +10,7 @@ toBinary,fromBinary, } from "@bufbuild/protobuf"; import { type QueryClientConfig, QueryClient, QueryClientProvider, type MutationOptions,
-
@@ -31,10 +32,16 @@ export interface ProtoRPCProviderProps {children: ReactNode; rpc: ProtoRPC; config?: QueryClientConfig; } export const ProtoRPCProvider: FC<ProtoRPCProviderProps> = ({ children, rpc }) => { const [queryClient] = useState(() => new QueryClient()); export const ProtoRPCProvider: FC<ProtoRPCProviderProps> = ({ children, rpc, config, }) => { const [queryClient] = useState(() => new QueryClient(config)); return ( <Context.Provider value={rpc}>
-
@@ -55,6 +62,7 @@export interface UseMethodQueryParams< Request extends DescMessage, Response extends DescMessage, Output = MessageShape<Response>, > { readonly service: string; readonly method: string;
-
@@ -68,22 +76,23 @@ readonly response: {readonly schema: Response; }; readonly options?: Omit<QueryOptions<MessageShape<Response>>, "queryKey" | "queryFn">; mapResponse?(response: MessageShape<Response>): Output; readonly options?: Omit<QueryOptions<Output>, "queryKey" | "queryFn">; } export function useMethodQuery< Request extends DescMessage, Response extends DescMessage, Output = MessageShape<Response>, >({ service, method, request, response, mapResponse, options, }: UseMethodQueryParams<Request, Response>): UseQueryResult< MessageShape<Response>, unknown > { }: UseMethodQueryParams<Request, Response, Output>): UseQueryResult<Output, unknown> { const rpc = useRPC(); return useQuery({
-
@@ -96,7 +105,13 @@ method,toBinary(request.schema, create(request.schema, request.data)), ); return fromBinary(response.schema, received); const resp = fromBinary(response.schema, received); if (mapResponse) { return mapResponse(resp); } return resp as Output; }, }); }
-
@@ -104,6 +119,7 @@export interface UseMethodMutationParams< Request extends DescMessage, Response extends DescMessage, Output = MessageShape<Response>, > { readonly service: string; readonly method: string;
-
@@ -116,8 +132,10 @@ readonly response: {readonly schema: Response; }; mapResponse?(response: MessageShape<Response>): Output; readonly options?: Omit< MutationOptions<MessageShape<Response>, unknown, MessageInitShape<Request>>, MutationOptions<Output, unknown, MessageInitShape<Request>>, "mutationFn" >; }
-
@@ -125,14 +143,16 @@export function useMethodMutation< Request extends DescMessage, Response extends DescMessage, Output = MessageShape<Response>, >({ service, method, request, response, mapResponse, options, }: UseMethodMutationParams<Request, Response>): UseMutationResult< MessageShape<Response>, }: UseMethodMutationParams<Request, Response, Output>): UseMutationResult< Output, unknown, MessageInitShape<Request> > {
-
@@ -147,7 +167,13 @@ method,toBinary(request.schema, create(request.schema, req)), ); return fromBinary(response.schema, received); const resp = fromBinary(response.schema, received); if (mapResponse) { return mapResponse(resp); } return resp as Output; }, }); }
-
-
-
@@ -0,0 +1,8 @@// SPDX-FileCopyrightText: 2024 Shota FUJI <pockawoooh@gmail.com> // SPDX-License-Identifier: AGPL-3.0-only export class IllegalMessageError extends Error { constructor(public readonly data: unknown) { super("Received a message that does not comform schema"); } }
-