From 905e1626a4154bcd1e61b8cd4e17500accd54c08 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?marcin=20miko=C5=82ajczak?= Date: Wed, 16 Oct 2024 17:30:05 +0200 Subject: [PATCH] pl-fe: wip valibot migration MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: marcin mikołajczak --- packages/pl-api/lib/entities/tag.ts | 4 ++-- packages/pl-fe/src/actions/auth.ts | 3 ++- packages/pl-fe/src/actions/external-auth.ts | 3 ++- .../src/api/hooks/admin/useAnnouncements.ts | 5 +++-- .../api/hooks/announcements/useAnnouncements.ts | 5 +++-- .../src/api/hooks/groups/useGroups.test.ts | 3 ++- .../hooks/settings/useInteractionPolicies.ts | 3 ++- packages/pl-fe/src/components/preview-card.tsx | 3 ++- .../src/entity-store/hooks/useEntityLookup.ts | 1 + .../src/features/account/components/header.tsx | 5 +++-- .../auth-login/components/login-form.test.tsx | 5 +++-- .../auth-login/components/login-page.test.tsx | 3 ++- .../compose/editor/nodes/image-component.tsx | 3 ++- .../src/features/draft-statuses/builder.tsx | 3 ++- .../features/group/components/group-header.tsx | 5 +++-- .../src/features/scheduled-statuses/builder.tsx | 3 ++- .../compose-event-modal/upload-button.tsx | 1 - .../src/features/ui/util/global-hotkeys.tsx | 1 - .../features/ui/util/pending-status-builder.ts | 3 ++- packages/pl-fe/src/jest/factory.ts | 17 +++++++++-------- packages/pl-fe/src/jest/mock-stores.tsx | 5 +++-- packages/pl-fe/src/normalizers/poll.ts | 1 - packages/pl-fe/src/normalizers/status.ts | 3 ++- packages/pl-fe/src/queries/chats.ts | 3 ++- packages/pl-fe/src/reducers/accounts-meta.ts | 15 +++------------ packages/pl-fe/src/reducers/auth.ts | 7 ++++--- packages/pl-fe/src/reducers/instance.ts | 5 +++-- packages/pl-fe/src/utils/emoji-reacts.test.ts | 13 +++++++------ packages/pl-fe/src/utils/emoji-reacts.ts | 7 ++++--- 29 files changed, 75 insertions(+), 63 deletions(-) diff --git a/packages/pl-api/lib/entities/tag.ts b/packages/pl-api/lib/entities/tag.ts index c1518bbde..f9010d436 100644 --- a/packages/pl-api/lib/entities/tag.ts +++ b/packages/pl-api/lib/entities/tag.ts @@ -1,10 +1,10 @@ import * as v from 'valibot'; -const historySchema = v.object({ +const historySchema = v.array(v.object({ day: v.pipe(v.unknown(), v.transform(Number)), accounts: v.pipe(v.unknown(), v.transform(Number)), uses: v.pipe(v.unknown(), v.transform(Number)), -}); +})); /** @see {@link https://docs.joinmastodon.org/entities/tag} */ const tagSchema = v.object({ diff --git a/packages/pl-fe/src/actions/auth.ts b/packages/pl-fe/src/actions/auth.ts index a438cef01..7b1d8c986 100644 --- a/packages/pl-fe/src/actions/auth.ts +++ b/packages/pl-fe/src/actions/auth.ts @@ -8,6 +8,7 @@ */ import { credentialAccountSchema, PlApiClient, type CreateAccountParams, type Token } from 'pl-api'; import { defineMessages } from 'react-intl'; +import * as v from 'valibot'; import { createAccount } from 'pl-fe/actions/accounts'; import { createApp } from 'pl-fe/actions/apps'; @@ -157,7 +158,7 @@ const verifyCredentials = (token: string, accountUrl?: string) => if (error?.response?.status === 403 && error?.response?.json?.id) { // The user is waitlisted const account = error.response.json; - const parsedAccount = credentialAccountSchema.parse(error.response.json); + const parsedAccount = v.parse(credentialAccountSchema, error.response.json); dispatch(importFetchedAccount(parsedAccount)); dispatch({ type: VERIFY_CREDENTIALS_SUCCESS, token, account: parsedAccount }); if (account.id === getState().me) dispatch(fetchMeSuccess(parsedAccount)); diff --git a/packages/pl-fe/src/actions/external-auth.ts b/packages/pl-fe/src/actions/external-auth.ts index db8626377..da6f01687 100644 --- a/packages/pl-fe/src/actions/external-auth.ts +++ b/packages/pl-fe/src/actions/external-auth.ts @@ -7,6 +7,7 @@ */ import { instanceSchema, PlApiClient, type Instance } from 'pl-api'; +import * as v from 'valibot'; import { createApp } from 'pl-fe/actions/apps'; import { authLoggedIn, verifyCredentials, switchAccount } from 'pl-fe/actions/auth'; @@ -24,7 +25,7 @@ const fetchExternalInstance = (baseURL: string) => if (error.response?.status === 401) { // Authenticated fetch is enabled. // Continue with a limited featureset. - return instanceSchema.parse({}); + return v.parse(instanceSchema, {}); } else { throw error; } diff --git a/packages/pl-fe/src/api/hooks/admin/useAnnouncements.ts b/packages/pl-fe/src/api/hooks/admin/useAnnouncements.ts index a17eebc1c..38da607ab 100644 --- a/packages/pl-fe/src/api/hooks/admin/useAnnouncements.ts +++ b/packages/pl-fe/src/api/hooks/admin/useAnnouncements.ts @@ -5,6 +5,7 @@ import { type AdminCreateAnnouncementParams, type AdminUpdateAnnouncementParams, } from 'pl-api'; +import * as v from 'valibot'; import { useClient } from 'pl-fe/hooks'; import { normalizeAnnouncement, AdminAnnouncement } from 'pl-fe/normalizers'; @@ -36,7 +37,7 @@ const useAnnouncements = () => { retry: false, onSuccess: (data) => queryClient.setQueryData(['admin', 'announcements'], (prevResult: ReadonlyArray) => - [...prevResult, adminAnnouncementSchema.parse(data)], + [...prevResult, v.parse(adminAnnouncementSchema, data)], ), onSettled: () => userAnnouncements.refetch(), }); @@ -50,7 +51,7 @@ const useAnnouncements = () => { retry: false, onSuccess: (data) => queryClient.setQueryData(['admin', 'announcements'], (prevResult: ReadonlyArray) => - prevResult.map((announcement) => announcement.id === data.id ? adminAnnouncementSchema.parse(data) : announcement), + prevResult.map((announcement) => announcement.id === data.id ? v.parse(adminAnnouncementSchema, data) : announcement), ), onSettled: () => userAnnouncements.refetch(), }); diff --git a/packages/pl-fe/src/api/hooks/announcements/useAnnouncements.ts b/packages/pl-fe/src/api/hooks/announcements/useAnnouncements.ts index 44fda104b..3c30f743b 100644 --- a/packages/pl-fe/src/api/hooks/announcements/useAnnouncements.ts +++ b/packages/pl-fe/src/api/hooks/announcements/useAnnouncements.ts @@ -1,11 +1,12 @@ import { useMutation, useQuery } from '@tanstack/react-query'; import { announcementReactionSchema, type AnnouncementReaction } from 'pl-api'; +import * as v from 'valibot'; import { useClient } from 'pl-fe/hooks'; import { type Announcement, normalizeAnnouncement } from 'pl-fe/normalizers'; import { queryClient } from 'pl-fe/queries/client'; -const updateReaction = (reaction: AnnouncementReaction, count: number, me?: boolean, overwrite?: boolean) => announcementReactionSchema.parse({ +const updateReaction = (reaction: AnnouncementReaction, count: number, me?: boolean, overwrite?: boolean) => v.parse(announcementReactionSchema, { ...reaction, me: typeof me === 'boolean' ? me : reaction.me, count: overwrite ? count : (reaction.count + count), @@ -18,7 +19,7 @@ const updateReactions = (reactions: AnnouncementReaction[], name: string, count: reactions = reactions.map(reaction => reaction.name === name ? updateReaction(reaction, count, me, overwrite) : reaction); } - return [...reactions, updateReaction(announcementReactionSchema.parse({ name }), count, me, overwrite)]; + return [...reactions, updateReaction(v.parse(announcementReactionSchema, { name }), count, me, overwrite)]; }; const useAnnouncements = () => { diff --git a/packages/pl-fe/src/api/hooks/groups/useGroups.test.ts b/packages/pl-fe/src/api/hooks/groups/useGroups.test.ts index e0200b92d..856a1289b 100644 --- a/packages/pl-fe/src/api/hooks/groups/useGroups.test.ts +++ b/packages/pl-fe/src/api/hooks/groups/useGroups.test.ts @@ -1,4 +1,5 @@ import { instanceSchema } from 'pl-api'; +import * as v from 'valibot'; import { __stub } from 'pl-fe/api'; import { buildGroup } from 'pl-fe/jest/factory'; @@ -8,7 +9,7 @@ import { useGroups } from './useGroups'; const group = buildGroup({ id: '1', display_name: 'soapbox' }); const store = { - instance: instanceSchema.parse({ + instance: v.parse(instanceSchema, { version: '3.4.1 (compatible; TruthSocial 1.0.0+unreleased)', }), }; diff --git a/packages/pl-fe/src/api/hooks/settings/useInteractionPolicies.ts b/packages/pl-fe/src/api/hooks/settings/useInteractionPolicies.ts index d85b19c25..aa09e5f5d 100644 --- a/packages/pl-fe/src/api/hooks/settings/useInteractionPolicies.ts +++ b/packages/pl-fe/src/api/hooks/settings/useInteractionPolicies.ts @@ -1,10 +1,11 @@ import { useMutation, useQuery } from '@tanstack/react-query'; import { type InteractionPolicies, interactionPoliciesSchema } from 'pl-api'; +import * as v from 'valibot'; import { useClient, useFeatures, useLoggedIn } from 'pl-fe/hooks'; import { queryClient } from 'pl-fe/queries/client'; -const emptySchema = interactionPoliciesSchema.parse({}); +const emptySchema = v.parse(interactionPoliciesSchema, {}); const useInteractionPolicies = () => { const client = useClient(); diff --git a/packages/pl-fe/src/components/preview-card.tsx b/packages/pl-fe/src/components/preview-card.tsx index dc275ff50..9564fbe1e 100644 --- a/packages/pl-fe/src/components/preview-card.tsx +++ b/packages/pl-fe/src/components/preview-card.tsx @@ -1,6 +1,7 @@ import clsx from 'clsx'; import { type MediaAttachment, type PreviewCard as CardEntity, mediaAttachmentSchema } from 'pl-api'; import React, { useState, useEffect } from 'react'; +import * as v from 'valibot'; import Blurhash from 'pl-fe/components/blurhash'; import { HStack, Stack, Text, Icon } from 'pl-fe/components/ui'; @@ -43,7 +44,7 @@ const PreviewCard: React.FC = ({ const trimmedDescription = trim(card.description, maxDescription); const handlePhotoClick = () => { - const attachment = mediaAttachmentSchema.parse({ + const attachment = v.parse(mediaAttachmentSchema, { id: '', type: 'image', url: card.embed_url, diff --git a/packages/pl-fe/src/entity-store/hooks/useEntityLookup.ts b/packages/pl-fe/src/entity-store/hooks/useEntityLookup.ts index 683077c97..a8dd9d6c8 100644 --- a/packages/pl-fe/src/entity-store/hooks/useEntityLookup.ts +++ b/packages/pl-fe/src/entity-store/hooks/useEntityLookup.ts @@ -1,4 +1,5 @@ import { useEffect, useState } from 'react'; +import * as v from 'valibot'; import { z } from 'zod'; import { useAppDispatch } from 'pl-fe/hooks/useAppDispatch'; diff --git a/packages/pl-fe/src/features/account/components/header.tsx b/packages/pl-fe/src/features/account/components/header.tsx index 6fdcec148..5fd42e744 100644 --- a/packages/pl-fe/src/features/account/components/header.tsx +++ b/packages/pl-fe/src/features/account/components/header.tsx @@ -3,6 +3,7 @@ import { GOTOSOCIAL, MASTODON, mediaAttachmentSchema } from 'pl-api'; import React from 'react'; import { defineMessages, FormattedMessage, useIntl } from 'react-intl'; import { useHistory } from 'react-router-dom'; +import * as v from 'valibot'; import { biteAccount, blockAccount, pinAccount, removeFromFollowers, unblockAccount, unmuteAccount, unpinAccount } from 'pl-fe/actions/accounts'; import { mentionCompose, directCompose } from 'pl-fe/actions/compose'; @@ -240,7 +241,7 @@ const Header: React.FC = ({ account }) => { }; const onAvatarClick = () => { - const avatar = mediaAttachmentSchema.parse({ + const avatar = v.parse(mediaAttachmentSchema, { id: '', type: 'image', url: account.avatar, @@ -256,7 +257,7 @@ const Header: React.FC = ({ account }) => { }; const onHeaderClick = () => { - const header = mediaAttachmentSchema.parse({ + const header = v.parse(mediaAttachmentSchema, { type: 'image', url: account.header, }); diff --git a/packages/pl-fe/src/features/auth-login/components/login-form.test.tsx b/packages/pl-fe/src/features/auth-login/components/login-form.test.tsx index bc985d929..6ed9a7f23 100644 --- a/packages/pl-fe/src/features/auth-login/components/login-form.test.tsx +++ b/packages/pl-fe/src/features/auth-login/components/login-form.test.tsx @@ -1,5 +1,6 @@ import { instanceSchema } from 'pl-api'; import React from 'react'; +import * as v from 'valibot'; import { fireEvent, render, screen } from 'pl-fe/jest/test-helpers'; @@ -9,7 +10,7 @@ describe('', () => { it('renders for Pleroma', () => { const mockFn = vi.fn(); const store = { - instance: instanceSchema.parse({ + instance: v.parse(instanceSchema, { version: '2.7.2 (compatible; Pleroma 2.3.0)', }), }; @@ -22,7 +23,7 @@ describe('', () => { it('renders for Mastodon', () => { const mockFn = vi.fn(); const store = { - instance: instanceSchema.parse({ + instance: v.parse(instanceSchema, { version: '3.0.0', }), }; diff --git a/packages/pl-fe/src/features/auth-login/components/login-page.test.tsx b/packages/pl-fe/src/features/auth-login/components/login-page.test.tsx index 67762b8e7..103172437 100644 --- a/packages/pl-fe/src/features/auth-login/components/login-page.test.tsx +++ b/packages/pl-fe/src/features/auth-login/components/login-page.test.tsx @@ -1,5 +1,6 @@ import { instanceSchema } from 'pl-api'; import React from 'react'; +import * as v from 'valibot'; import { render, screen } from 'pl-fe/jest/test-helpers'; @@ -8,7 +9,7 @@ import LoginPage from './login-page'; describe('', () => { it('renders correctly on load', () => { const store = { - instance: instanceSchema.parse({ + instance: v.parse(instanceSchema, { version: '2.7.2 (compatible; Pleroma 2.3.0)', }), }; diff --git a/packages/pl-fe/src/features/compose/editor/nodes/image-component.tsx b/packages/pl-fe/src/features/compose/editor/nodes/image-component.tsx index c6b79615a..5289ff65a 100644 --- a/packages/pl-fe/src/features/compose/editor/nodes/image-component.tsx +++ b/packages/pl-fe/src/features/compose/editor/nodes/image-component.tsx @@ -26,6 +26,7 @@ import { mediaAttachmentSchema } from 'pl-api'; import * as React from 'react'; import { Suspense, useCallback, useEffect, useRef, useState } from 'react'; import { FormattedMessage, defineMessages, useIntl } from 'react-intl'; +import * as v from 'valibot'; import { HStack, Icon, IconButton } from 'pl-fe/components/ui'; import { useSettings } from 'pl-fe/hooks'; @@ -122,7 +123,7 @@ const ImageComponent = ({ ); const previewImage = () => { - const image = mediaAttachmentSchema.parse({ + const image = v.parse(mediaAttachmentSchema, { id: '', type: 'image', url: src, diff --git a/packages/pl-fe/src/features/draft-statuses/builder.tsx b/packages/pl-fe/src/features/draft-statuses/builder.tsx index 4272897d8..c93f34c3a 100644 --- a/packages/pl-fe/src/features/draft-statuses/builder.tsx +++ b/packages/pl-fe/src/features/draft-statuses/builder.tsx @@ -1,4 +1,5 @@ import { statusSchema } from 'pl-api'; +import * as v from 'valibot'; import { Entities } from 'pl-fe/entity-store/entities'; import { normalizeStatus } from 'pl-fe/normalizers'; @@ -22,7 +23,7 @@ const buildStatus = (state: RootState, draftStatus: DraftStatus) => { const me = state.me as string; const account = state.entities[Entities.ACCOUNTS]?.store[me]; - const status = statusSchema.parse({ + const status = v.parse(statusSchema, { id: 'draft', account, content: draftStatus.text.replace(new RegExp('\n', 'g'), '
'), /* eslint-disable-line no-control-regex */ diff --git a/packages/pl-fe/src/features/group/components/group-header.tsx b/packages/pl-fe/src/features/group/components/group-header.tsx index e1c1f61ea..c0c229db2 100644 --- a/packages/pl-fe/src/features/group/components/group-header.tsx +++ b/packages/pl-fe/src/features/group/components/group-header.tsx @@ -1,6 +1,7 @@ import { mediaAttachmentSchema } from 'pl-api'; import React, { useState } from 'react'; import { defineMessages, useIntl } from 'react-intl'; +import * as v from 'valibot'; import GroupAvatar from 'pl-fe/components/groups/group-avatar'; import { ParsedContent } from 'pl-fe/components/parsed-content'; @@ -52,7 +53,7 @@ const GroupHeader: React.FC = ({ group }) => { } const onAvatarClick = () => { - const avatar = mediaAttachmentSchema.parse({ + const avatar = v.parse(mediaAttachmentSchema, { id: '', type: 'image', url: group.avatar, @@ -68,7 +69,7 @@ const GroupHeader: React.FC = ({ group }) => { }; const onHeaderClick = () => { - const header = mediaAttachmentSchema.parse({ + const header = v.parse(mediaAttachmentSchema, { id: '', type: 'image', url: group.header, diff --git a/packages/pl-fe/src/features/scheduled-statuses/builder.tsx b/packages/pl-fe/src/features/scheduled-statuses/builder.tsx index ed9d7d7df..9d02b7d4b 100644 --- a/packages/pl-fe/src/features/scheduled-statuses/builder.tsx +++ b/packages/pl-fe/src/features/scheduled-statuses/builder.tsx @@ -1,4 +1,5 @@ import { statusSchema, type ScheduledStatus } from 'pl-api'; +import * as v from 'valibot'; import { Entities } from 'pl-fe/entity-store/entities'; import { normalizeStatus } from 'pl-fe/normalizers/status'; @@ -9,7 +10,7 @@ const buildStatus = (state: RootState, scheduledStatus: ScheduledStatus) => { const me = state.me as string; const account = state.entities[Entities.ACCOUNTS]?.store[me]; - const status = statusSchema.parse({ + const status = v.parse(statusSchema, { account, content: scheduledStatus.params.text?.replace(new RegExp('\n', 'g'), '
'), /* eslint-disable-line no-control-regex */ created_at: scheduledStatus.params.scheduled_at, diff --git a/packages/pl-fe/src/features/ui/components/modals/compose-event-modal/upload-button.tsx b/packages/pl-fe/src/features/ui/components/modals/compose-event-modal/upload-button.tsx index 7931b8d32..842ec67a5 100644 --- a/packages/pl-fe/src/features/ui/components/modals/compose-event-modal/upload-button.tsx +++ b/packages/pl-fe/src/features/ui/components/modals/compose-event-modal/upload-button.tsx @@ -29,7 +29,6 @@ const UploadButton: React.FC = ({ disabled, onSelectFile }) => { fileElement.current?.click(); }; - return ( = ({ children, node }) => { return handlers; }, [account?.id]); - return ( {children} diff --git a/packages/pl-fe/src/features/ui/util/pending-status-builder.ts b/packages/pl-fe/src/features/ui/util/pending-status-builder.ts index 4ef68ea39..9143e67a5 100644 --- a/packages/pl-fe/src/features/ui/util/pending-status-builder.ts +++ b/packages/pl-fe/src/features/ui/util/pending-status-builder.ts @@ -1,5 +1,6 @@ import { Map as ImmutableMap, List as ImmutableList } from 'immutable'; import { statusSchema } from 'pl-api'; +import * as v from 'valibot'; import { normalizeStatus } from 'pl-fe/normalizers/status'; import { makeGetAccount } from 'pl-fe/selectors'; @@ -46,7 +47,7 @@ const buildStatus = (state: RootState, pendingStatus: PendingStatus, idempotency visibility: pendingStatus.visibility, }; - return normalizeStatus(statusSchema.parse(status)); + return normalizeStatus(v.parse(statusSchema, status)); }; export { buildStatus }; diff --git a/packages/pl-fe/src/jest/factory.ts b/packages/pl-fe/src/jest/factory.ts index b4000333a..47fcfa3b3 100644 --- a/packages/pl-fe/src/jest/factory.ts +++ b/packages/pl-fe/src/jest/factory.ts @@ -17,6 +17,7 @@ import { type Relationship, type Status, } from 'pl-api'; +import * as v from 'valibot'; import type { PartialDeep } from 'type-fest'; @@ -24,18 +25,18 @@ import type { PartialDeep } from 'type-fest'; // This looks promising but didn't work on my first attempt: https://github.com/anatine/zod-plugins/tree/main/packages/zod-mock const buildAccount = (props: PartialDeep = {}): Account => - accountSchema.parse(Object.assign({ + v.parse(accountSchema, Object.assign({ id: crypto.randomUUID(), url: `https://soapbox.test/users/${crypto.randomUUID()}`, }, props)); const buildCard = (props: PartialDeep = {}): PreviewCard => - previewCardSchema.parse(Object.assign({ + v.parse(previewCardSchema, Object.assign({ url: 'https://soapbox.test', }, props)); const buildGroup = (props: PartialDeep = {}): Group => - groupSchema.parse(Object.assign({ + v.parse(groupSchema, Object.assign({ id: crypto.randomUUID(), owner: { id: crypto.randomUUID(), @@ -43,28 +44,28 @@ const buildGroup = (props: PartialDeep = {}): Group => }, props)); const buildGroupRelationship = (props: PartialDeep = {}): GroupRelationship => - groupRelationshipSchema.parse(Object.assign({ + v.parse(groupRelationshipSchema, Object.assign({ id: crypto.randomUUID(), }, props)); const buildGroupMember = ( props: PartialDeep = {}, accountProps: PartialDeep = {}, -): GroupMember => groupMemberSchema.parse(Object.assign({ +): GroupMember => v.parse(groupMemberSchema, Object.assign({ id: crypto.randomUUID(), account: buildAccount(accountProps), role: GroupRoles.USER, }, props)); -const buildInstance = (props: PartialDeep = {}) => instanceSchema.parse(props); +const buildInstance = (props: PartialDeep = {}) => v.parse(instanceSchema, props); const buildRelationship = (props: PartialDeep = {}): Relationship => - relationshipSchema.parse(Object.assign({ + v.parse(relationshipSchema, Object.assign({ id: crypto.randomUUID(), }, props)); const buildStatus = (props: PartialDeep = {}) => - statusSchema.parse(Object.assign({ + v.parse(statusSchema, Object.assign({ id: crypto.randomUUID(), account: buildAccount(), }, props)); diff --git a/packages/pl-fe/src/jest/mock-stores.tsx b/packages/pl-fe/src/jest/mock-stores.tsx index 01e0c3cf3..7d2b4db4e 100644 --- a/packages/pl-fe/src/jest/mock-stores.tsx +++ b/packages/pl-fe/src/jest/mock-stores.tsx @@ -1,14 +1,15 @@ import { instanceSchema } from 'pl-api'; +import * as v from 'valibot'; import alexJson from 'pl-fe/__fixtures__/pleroma-account.json'; import { buildAccount } from './factory'; /** Store with registrations open. */ -const storeOpen = { instance: instanceSchema.parse({ registrations: true }) }; +const storeOpen = { instance: v.parse(instanceSchema, { registrations: true }) }; /** Store with registrations closed. */ -const storeClosed = { instance: instanceSchema.parse({ registrations: false }) }; +const storeClosed = { instance: v.parse(instanceSchema, { registrations: false }) }; /** Store with a logged-in user. */ const storeLoggedIn = { diff --git a/packages/pl-fe/src/normalizers/poll.ts b/packages/pl-fe/src/normalizers/poll.ts index e7f10488f..ee86a20c6 100644 --- a/packages/pl-fe/src/normalizers/poll.ts +++ b/packages/pl-fe/src/normalizers/poll.ts @@ -1,7 +1,6 @@ import escapeTextContentForBrowser from 'escape-html'; import DOMPurify from 'isomorphic-dompurify'; - import emojify from 'pl-fe/features/emoji'; import { makeEmojiMap } from 'pl-fe/utils/normalizers'; diff --git a/packages/pl-fe/src/normalizers/status.ts b/packages/pl-fe/src/normalizers/status.ts index 18cc8359a..6b24a7c06 100644 --- a/packages/pl-fe/src/normalizers/status.ts +++ b/packages/pl-fe/src/normalizers/status.ts @@ -6,6 +6,7 @@ import escapeTextContentForBrowser from 'escape-html'; import DOMPurify from 'isomorphic-dompurify'; import { type Account as BaseAccount, type Status as BaseStatus, type CustomEmoji, type MediaAttachment, mentionSchema, type Translation } from 'pl-api'; +import * as v from 'valibot'; import emojify from 'pl-fe/features/emoji'; import { unescapeHTML } from 'pl-fe/utils/html'; @@ -111,7 +112,7 @@ const normalizeStatus = (status: BaseStatus & { const hasSelfMention = status.mentions.some(mention => status.account.id === mention.id); if (isSelfReply && !hasSelfMention) { - const selfMention = mentionSchema.parse(status.account); + const selfMention = v.parse(mentionSchema, status.account); mentions = [selfMention, ...mentions]; } diff --git a/packages/pl-fe/src/queries/chats.ts b/packages/pl-fe/src/queries/chats.ts index 4b2d3ec37..e5076b6f0 100644 --- a/packages/pl-fe/src/queries/chats.ts +++ b/packages/pl-fe/src/queries/chats.ts @@ -1,6 +1,7 @@ import { InfiniteData, keepPreviousData, useInfiniteQuery, useMutation, useQuery } from '@tanstack/react-query'; import sumBy from 'lodash/sumBy'; import { type Chat, type ChatMessage as BaseChatMessage, type PaginatedResponse, chatMessageSchema, type Relationship } from 'pl-api'; +import * as v from 'valibot'; import { importFetchedAccount, importFetchedAccounts } from 'pl-fe/actions/importer'; import { ChatWidgetScreens, useChatContext } from 'pl-fe/contexts/chat-context'; @@ -171,7 +172,7 @@ const useChatActions = (chatId: string) => { ...page, items: [ normalizeChatMessage({ - ...chatMessageSchema.parse({ + ...v.parse(chatMessageSchema, { chat_id: variables.chatId, content: variables.content, id: pendingId, diff --git a/packages/pl-fe/src/reducers/accounts-meta.ts b/packages/pl-fe/src/reducers/accounts-meta.ts index 7a0b9f3b9..1349f69ac 100644 --- a/packages/pl-fe/src/reducers/accounts-meta.ts +++ b/packages/pl-fe/src/reducers/accounts-meta.ts @@ -4,11 +4,11 @@ */ import { produce } from 'immer'; -import { Account, accountSchema } from 'pl-api'; import { VERIFY_CREDENTIALS_SUCCESS, AUTH_ACCOUNT_REMEMBER_SUCCESS } from 'pl-fe/actions/auth'; import { ME_FETCH_SUCCESS, ME_PATCH_SUCCESS } from 'pl-fe/actions/me'; +import type { Account, CredentialAccount } from 'pl-api'; import type { AnyAction } from 'redux'; interface AccountMeta { @@ -18,16 +18,8 @@ interface AccountMeta { type State = Record; -const importAccount = (state: State, data: unknown): State => { - const result = accountSchema.safeParse(data); - - if (!result.success) { - return state; - } - - const account = result.data; - - return produce(state, draft => { +const importAccount = (state: State, account: CredentialAccount): State => + produce(state, draft => { const existing = draft[account.id]; draft[account.id] = { @@ -35,7 +27,6 @@ const importAccount = (state: State, data: unknown): State => { source: account.__meta.source ?? existing?.source, }; }); -}; const accounts_meta = (state: Readonly = {}, action: AnyAction): State => { switch (action.type) { diff --git a/packages/pl-fe/src/reducers/auth.ts b/packages/pl-fe/src/reducers/auth.ts index 067909563..48559b8c5 100644 --- a/packages/pl-fe/src/reducers/auth.ts +++ b/packages/pl-fe/src/reducers/auth.ts @@ -1,6 +1,7 @@ import { List as ImmutableList, Map as ImmutableMap, Record as ImmutableRecord, fromJS } from 'immutable'; import trim from 'lodash/trim'; import { applicationSchema, PlApiClient, tokenSchema, type Application, type CredentialAccount, type Token } from 'pl-api'; +import * as v from 'valibot'; import { MASTODON_PRELOAD_IMPORT } from 'pl-fe/actions/preload'; import * as BuildConfig from 'pl-fe/build-config'; @@ -60,8 +61,8 @@ const getLocalState = () => { if (!state) return undefined; return ReducerRecord({ - app: state.app && applicationSchema.parse(state.app), - tokens: ImmutableMap(Object.entries(state.tokens).map(([key, value]) => [key, tokenSchema.parse(value)])), + app: state.app && v.parse(applicationSchema, state.app), + tokens: ImmutableMap(Object.entries(state.tokens).map(([key, value]) => [key, v.parse(tokenSchema, value)])), users: ImmutableMap(Object.entries(state.users).map(([key, value]) => [key, AuthUserRecord(value as any)])), me: state.me, client: new PlApiClient(parseBaseURL(state.me) || backendUrl, state.users[state.me]?.access_token), @@ -237,7 +238,7 @@ const importMastodonPreload = (state: State, data: ImmutableMap) => const accessToken = data.getIn(['meta', 'access_token']) as string; if (validId(accessToken) && validId(accountId) && isURL(accountUrl)) { - state.setIn(['tokens', accessToken], tokenSchema.parse({ + state.setIn(['tokens', accessToken], v.parse(tokenSchema, { access_token: accessToken, account: accountId, me: accountUrl, diff --git a/packages/pl-fe/src/reducers/instance.ts b/packages/pl-fe/src/reducers/instance.ts index 8333b5e1e..f5acfcf33 100644 --- a/packages/pl-fe/src/reducers/instance.ts +++ b/packages/pl-fe/src/reducers/instance.ts @@ -1,6 +1,7 @@ import { produce } from 'immer'; import { Map as ImmutableMap, List as ImmutableList, fromJS } from 'immutable'; import { type Instance, instanceSchema } from 'pl-api'; +import * as v from 'valibot'; import { ADMIN_CONFIG_UPDATE_REQUEST, ADMIN_CONFIG_UPDATE_SUCCESS } from 'pl-fe/actions/admin'; import { INSTANCE_FETCH_FAIL, INSTANCE_FETCH_SUCCESS, InstanceAction } from 'pl-fe/actions/instance'; @@ -10,11 +11,11 @@ import ConfigDB from 'pl-fe/utils/config-db'; import type { AnyAction } from 'redux'; -const initialState: Instance = instanceSchema.parse({}); +const initialState: Instance = v.parse(instanceSchema, {}); const preloadImport = (state: Instance, action: Record, path: string) => { const instance = action.data[path]; - return instance ? instanceSchema.parse(instance) : state; + return instance ? v.parse(instanceSchema, instance) : state; }; const getConfigValue = (instanceConfig: ImmutableMap, key: string) => { diff --git a/packages/pl-fe/src/utils/emoji-reacts.test.ts b/packages/pl-fe/src/utils/emoji-reacts.test.ts index 1eff793b0..3e12a9dc9 100644 --- a/packages/pl-fe/src/utils/emoji-reacts.test.ts +++ b/packages/pl-fe/src/utils/emoji-reacts.test.ts @@ -1,5 +1,6 @@ import { List as ImmutableList, fromJS } from 'immutable'; import { emojiReactionSchema } from 'pl-api'; +import * as v from 'valibot'; import { simulateEmojiReact, @@ -11,7 +12,7 @@ describe('simulateEmojiReact', () => { const emojiReacts = ImmutableList([ { 'count': 2, 'me': false, 'name': '👍', 'url': undefined }, { 'count': 2, 'me': false, 'name': '❤', 'url': undefined }, - ].map((react) => emojiReactionSchema.parse(react))); + ].map((react) => v.parse(emojiReactionSchema, react))); expect(simulateEmojiReact(emojiReacts, '❤')).toEqual(fromJS([ { 'count': 2, 'me': false, 'name': '👍', 'url': undefined }, { 'count': 3, 'me': true, 'name': '❤', 'url': undefined }, @@ -22,7 +23,7 @@ describe('simulateEmojiReact', () => { const emojiReacts = ImmutableList([ { 'count': 2, 'me': false, 'name': '👍', 'url': undefined }, { 'count': 2, 'me': false, 'name': '❤', 'url': undefined }, - ].map((react) => emojiReactionSchema.parse(react))); + ].map((react) => v.parse(emojiReactionSchema, react))); expect(simulateEmojiReact(emojiReacts, '😯')).toEqual(fromJS([ { 'count': 2, 'me': false, 'name': '👍', 'url': undefined }, { 'count': 2, 'me': false, 'name': '❤', 'url': undefined }, @@ -34,7 +35,7 @@ describe('simulateEmojiReact', () => { const emojiReacts = ImmutableList([ { 'count': 2, 'me': false, 'name': '👍', 'url': undefined }, { 'count': 2, 'me': false, 'name': '❤', 'url': undefined }, - ].map((react) => emojiReactionSchema.parse(react))); + ].map((react) => v.parse(emojiReactionSchema, react))); expect(simulateEmojiReact(emojiReacts, 'soapbox', 'https://gleasonator.com/emoji/Gleasonator/soapbox.png')).toEqual(fromJS([ { 'count': 2, 'me': false, 'name': '👍', 'url': undefined }, { 'count': 2, 'me': false, 'name': '❤', 'url': undefined }, @@ -48,7 +49,7 @@ describe('simulateUnEmojiReact', () => { const emojiReacts = ImmutableList([ { 'count': 2, 'me': false, 'name': '👍' }, { 'count': 3, 'me': true, 'name': '❤' }, - ].map((react) => emojiReactionSchema.parse(react))); + ].map((react) => v.parse(emojiReactionSchema, react))); expect(simulateUnEmojiReact(emojiReacts, '❤')).toEqual(fromJS([ { 'count': 2, 'me': false, 'name': '👍' }, { 'count': 2, 'me': false, 'name': '❤' }, @@ -60,7 +61,7 @@ describe('simulateUnEmojiReact', () => { { 'count': 2, 'me': false, 'name': '👍' }, { 'count': 2, 'me': false, 'name': '❤' }, { 'count': 1, 'me': true, 'name': '😯' }, - ].map((react) => emojiReactionSchema.parse(react))); + ].map((react) => v.parse(emojiReactionSchema, react))); expect(simulateUnEmojiReact(emojiReacts, '😯')).toEqual(fromJS([ { 'count': 2, 'me': false, 'name': '👍' }, { 'count': 2, 'me': false, 'name': '❤' }, @@ -72,7 +73,7 @@ describe('simulateUnEmojiReact', () => { { 'count': 2, 'me': false, 'name': '👍' }, { 'count': 2, 'me': false, 'name': '❤' }, { 'count': 1, 'me': true, 'name': 'soapbox', 'url': 'https://gleasonator.com/emoji/Gleasonator/soapbox.png' }, - ].map((react) => emojiReactionSchema.parse(react))); + ].map((react) => v.parse(emojiReactionSchema, react))); expect(simulateUnEmojiReact(emojiReacts, 'soapbox')).toEqual(fromJS([ { 'count': 2, 'me': false, 'name': '👍' }, { 'count': 2, 'me': false, 'name': '❤' }, diff --git a/packages/pl-fe/src/utils/emoji-reacts.ts b/packages/pl-fe/src/utils/emoji-reacts.ts index f93516b6d..d6fffa01d 100644 --- a/packages/pl-fe/src/utils/emoji-reacts.ts +++ b/packages/pl-fe/src/utils/emoji-reacts.ts @@ -1,18 +1,19 @@ import { emojiReactionSchema, type EmojiReaction } from 'pl-api'; +import * as v from 'valibot'; const simulateEmojiReact = (emojiReacts: Array, emoji: string, url?: string) => { const idx = emojiReacts.findIndex(e => e.name === emoji); const emojiReact = emojiReacts[idx]; if (idx > -1 && emojiReact) { - return emojiReacts.map((reaction, id) => id === idx ? emojiReactionSchema.parse({ + return emojiReacts.map((reaction, id) => id === idx ? v.parse(emojiReactionSchema, { ...emojiReact, count: (emojiReact.count || 0) + 1, me: true, url, }) : reaction); } else { - return [...emojiReacts, emojiReactionSchema.parse({ + return [...emojiReacts, v.parse(emojiReactionSchema, { count: 1, me: true, name: emoji, @@ -30,7 +31,7 @@ const simulateUnEmojiReact = (emojiReacts: Array, emoji: string) if (emojiReact) { const newCount = (emojiReact.count || 1) - 1; if (newCount < 1) return emojiReacts.filter((_, id) => id !== idx); - return emojiReacts.map((reaction, id) => id === idx ? emojiReactionSchema.parse({ + return emojiReacts.map((reaction, id) => id === idx ? v.parse(emojiReactionSchema, { ...emojiReact, count: (emojiReact.count || 1) - 1, me: false,