diff --git a/packages/pl-api/lib/client.ts b/packages/pl-api/lib/client.ts index 9e423ed819..ad955093b6 100644 --- a/packages/pl-api/lib/client.ts +++ b/packages/pl-api/lib/client.ts @@ -201,7 +201,7 @@ class PlApiClient { baseURL: string; #accessToken?: string; - #instance: Instance = instanceSchema.parse({}); + #instance: Instance = v.parse(instanceSchema, {}); public request = request.bind(this) as typeof request; public features: Features = getFeatures(this.#instance); #socket?: { @@ -2714,9 +2714,9 @@ class PlApiClient { return v.parse(v.array(v.object({ week: v.string(), - statuses: z.coerce.string(), - logins: z.coerce.string(), - registrations: z.coerce.string(), + statuses: v.pipe(v.unknown(), v.transform(String)), + logins: v.pipe(v.unknown(), v.transform(String)), + registrations: v.pipe(v.unknown(), v.transform(String)), })), response.json); }, diff --git a/packages/pl-api/lib/entities/account.ts b/packages/pl-api/lib/entities/account.ts index 5af5a0e2d0..c9816f3b99 100644 --- a/packages/pl-api/lib/entities/account.ts +++ b/packages/pl-api/lib/entities/account.ts @@ -7,7 +7,7 @@ import { roleSchema } from './role'; import { coerceObject, dateSchema, filteredArray } from './utils'; const filterBadges = (tags?: string[]) => - tags?.filter(tag => tag.startsWith('badge:')).map(tag => roleSchema.parse({ id: tag, name: tag.replace(/^badge:/, '') })); + tags?.filter(tag => tag.startsWith('badge:')).map(tag => v.parse(roleSchema, { id: tag, name: tag.replace(/^badge:/, '') })); const preprocessAccount = (account: any) => { if (!account?.acct) return null; @@ -112,7 +112,7 @@ const baseAccountSchema = v.object({ deactivated: v.fallback(v.optional(v.boolean()), undefined), location: v.fallback(v.optional(v.string()), undefined), - local: z.boolean().optional().catch(false), + local: v.fallback(v.optional(v.boolean()), false), avatar_description: v.fallback(v.string(), ''), enable_rss: v.fallback(v.boolean(), false), @@ -142,7 +142,7 @@ type Account = v.InferOutput & WithMoved; const accountSchema: z.ZodType = untypedAccountSchema as any; const untypedCredentialAccountSchema = z.preprocess(preprocessAccount, accountWithMovedAccountSchema.extend({ - source: v.object({ + source: v.fallback(v.nullable(v.object({ note: v.fallback(v.string(), ''), fields: filteredArray(fieldSchema), privacy: v.picklist(['public', 'unlisted', 'private', 'direct']), @@ -150,15 +150,15 @@ const untypedCredentialAccountSchema = z.preprocess(preprocessAccount, accountWi language: v.fallback(v.nullable(v.string()), null), follow_requests_count: z.number().int().nonnegative().catch(0), - show_role: z.boolean().optional().nullable().catch(undefined), - no_rich_text: z.boolean().optional().nullable().catch(undefined), + show_role: v.fallback(v.nullable(v.optional(v.boolean())), undefined), + no_rich_text: v.fallback(v.nullable(v.optional(v.boolean())), undefined), discoverable: v.fallback(v.optional(v.boolean()), undefined), actor_type: v.fallback(v.optional(v.string()), undefined), show_birthday: v.fallback(v.optional(v.boolean()), undefined), - }).nullable().catch(null), + })), null), role: v.fallback(v.nullable(roleSchema), null), - settings_store: v.record(v.string(), v.any()).optional().catch(undefined), + settings_store: v.fallback(v.optional(v.record(v.string(), v.any())), undefined), chat_token: v.fallback(v.optional(v.string()), undefined), allow_following_move: v.fallback(v.optional(v.boolean()), undefined), unread_conversation_count: v.fallback(v.optional(v.number()), undefined), diff --git a/packages/pl-api/lib/entities/admin/account.ts b/packages/pl-api/lib/entities/admin/account.ts index 8b829ae865..74fc5e1cc6 100644 --- a/packages/pl-api/lib/entities/admin/account.ts +++ b/packages/pl-api/lib/entities/admin/account.ts @@ -22,9 +22,9 @@ const adminAccountSchema = z.preprocess((account: any) => { email: account.email, invite_request: account.registration_reason, role: account.roles?.is_admin - ? roleSchema.parse({ name: 'Admin' }) + ? v.parse(roleSchema, { name: 'Admin' }) : account.roles?.moderator - ? roleSchema.parse({ name: 'Moderator ' }) : + ? v.parse(roleSchema, { name: 'Moderator ' }) : null, confirmed: account.is_confirmed, approved: account.is_approved, diff --git a/packages/pl-api/lib/entities/admin/cohort.ts b/packages/pl-api/lib/entities/admin/cohort.ts index af4cdcdfdb..5275ee50a6 100644 --- a/packages/pl-api/lib/entities/admin/cohort.ts +++ b/packages/pl-api/lib/entities/admin/cohort.ts @@ -4,7 +4,7 @@ import * as v from 'valibot'; const adminCohortSchema = v.object({ period: z.string().datetime({ offset: true }), frequency: v.picklist(['day', 'month']), - data: z.array(v.object({ + data: v.array(v.object({ date: z.string().datetime({ offset: true }), rate: v.number(), value: v.pipe(v.number(), v.integer()), diff --git a/packages/pl-api/lib/entities/admin/domain.ts b/packages/pl-api/lib/entities/admin/domain.ts index 67d6253add..2eb5e6a492 100644 --- a/packages/pl-api/lib/entities/admin/domain.ts +++ b/packages/pl-api/lib/entities/admin/domain.ts @@ -2,7 +2,7 @@ import * as v from 'valibot'; const adminDomainSchema = v.object({ domain: v.fallback(v.string(), ''), - id: z.coerce.string(), + id: v.pipe(v.unknown(), v.transform(String)), public: v.fallback(v.boolean(), false), resolves: v.fallback(v.boolean(), false), last_checked_at: z.string().datetime().catch(''), diff --git a/packages/pl-api/lib/entities/admin/email-domain-block.ts b/packages/pl-api/lib/entities/admin/email-domain-block.ts index d3c57e179b..5c5e8ecd68 100644 --- a/packages/pl-api/lib/entities/admin/email-domain-block.ts +++ b/packages/pl-api/lib/entities/admin/email-domain-block.ts @@ -8,9 +8,9 @@ const adminEmailDomainBlockSchema = v.object({ domain: v.string(), created_at: dateSchema, history: v.array(v.object({ - day: z.coerce.string(), - accounts: z.coerce.string(), - uses: z.coerce.string(), + day: v.pipe(v.unknown(), v.transform(String)), + accounts: v.pipe(v.unknown(), v.transform(String)), + uses: v.pipe(v.unknown(), v.transform(String)), })), }); diff --git a/packages/pl-api/lib/entities/admin/measure.ts b/packages/pl-api/lib/entities/admin/measure.ts index f1c9cb8dec..87ccc44f2e 100644 --- a/packages/pl-api/lib/entities/admin/measure.ts +++ b/packages/pl-api/lib/entities/admin/measure.ts @@ -4,12 +4,12 @@ import * as v from 'valibot'; const adminMeasureSchema = v.object({ key: v.string(), unit: v.fallback(v.nullable(v.string()), null), - total: z.coerce.number(), + total: v.pipe(v.unknown(), v.transform(Number)), human_value: v.fallback(v.optional(v.string()), undefined), - previous_total: z.coerce.string().optional().catch(undefined), - data: z.array(v.object({ + previous_total: v.fallback(v.optional(v.pipe(v.unknown(), v.transform(String))), undefined), + data: v.array(v.object({ date: z.string().datetime({ offset: true }), - value: z.coerce.string(), + value: v.pipe(v.unknown(), v.transform(String)), })), }); diff --git a/packages/pl-api/lib/entities/admin/moderation-log-entry.ts b/packages/pl-api/lib/entities/admin/moderation-log-entry.ts index 86ce010757..81300d5c3b 100644 --- a/packages/pl-api/lib/entities/admin/moderation-log-entry.ts +++ b/packages/pl-api/lib/entities/admin/moderation-log-entry.ts @@ -2,7 +2,7 @@ import * as v from 'valibot'; /** @see {@link https://docs.pleroma.social/backend/development/API/admin_api/#get-apiv1pleromaadminmoderation_log} */ const adminModerationLogEntrySchema = v.object({ - id: z.coerce.string(), + id: v.pipe(v.unknown(), v.transform(String)), data: v.fallback(v.record(v.string(), v.any()), {}), time: v.fallback(v.number(), 0), message: v.fallback(v.string(), ''), diff --git a/packages/pl-api/lib/entities/backup.ts b/packages/pl-api/lib/entities/backup.ts index 9c4acc3d80..ddc10ee0ff 100644 --- a/packages/pl-api/lib/entities/backup.ts +++ b/packages/pl-api/lib/entities/backup.ts @@ -4,7 +4,7 @@ import { dateSchema, mimeSchema } from './utils'; /** @see {@link https://docs.pleroma.social/backend/development/API/pleroma_api/#post-apiv1pleromabackups} */ const backupSchema = v.object({ - id: z.coerce.string(), + id: v.pipe(v.unknown(), v.transform(String)), contentType: mimeSchema, file_size: v.fallback(v.number(), 0), inserted_at: dateSchema, diff --git a/packages/pl-api/lib/entities/bookmark-folder.ts b/packages/pl-api/lib/entities/bookmark-folder.ts index edd634cf8a..d07c780046 100644 --- a/packages/pl-api/lib/entities/bookmark-folder.ts +++ b/packages/pl-api/lib/entities/bookmark-folder.ts @@ -1,7 +1,7 @@ import * as v from 'valibot'; const bookmarkFolderSchema = v.object({ - id: z.coerce.string(), + id: v.pipe(v.unknown(), v.transform(String)), name: v.fallback(v.string(), ''), emoji: v.fallback(v.nullable(v.string()), null), emoji_url: v.fallback(v.nullable(v.string()), null), diff --git a/packages/pl-api/lib/entities/context.ts b/packages/pl-api/lib/entities/context.ts index 75c32d786d..37449629bf 100644 --- a/packages/pl-api/lib/entities/context.ts +++ b/packages/pl-api/lib/entities/context.ts @@ -4,8 +4,8 @@ import { statusSchema } from './status'; /** @see {@link https://docs.joinmastodon.org/entities/Context/} */ const contextSchema = v.object({ - ancestors: z.array(statusSchema), - descendants: z.array(statusSchema), + ancestors: v.array(statusSchema), + descendants: v.array(statusSchema), }); type Context = v.InferOutput; diff --git a/packages/pl-api/lib/entities/directory/category.ts b/packages/pl-api/lib/entities/directory/category.ts index 605d8ab0e6..c9dbca386d 100644 --- a/packages/pl-api/lib/entities/directory/category.ts +++ b/packages/pl-api/lib/entities/directory/category.ts @@ -2,7 +2,7 @@ import * as v from 'valibot'; const directoryCategorySchema = v.object({ category: v.string(), - servers_count: v.fallback(v.nullable(z.coerce.number()), null), + servers_count: v.fallback(v.nullable(v.pipe(v.unknown(), v.transform(Number))), null), }); type DirectoryCategory = v.InferOutput; diff --git a/packages/pl-api/lib/entities/directory/language.ts b/packages/pl-api/lib/entities/directory/language.ts index 2c0c330a6b..3468733259 100644 --- a/packages/pl-api/lib/entities/directory/language.ts +++ b/packages/pl-api/lib/entities/directory/language.ts @@ -3,7 +3,7 @@ import * as v from 'valibot'; const directoryLanguageSchema = v.object({ locale: v.string(), language: v.string(), - servers_count: v.fallback(v.nullable(z.coerce.number()), null), + servers_count: v.fallback(v.nullable(v.pipe(v.unknown(), v.transform(Number))), null), }); type DirectoryLanguage = v.InferOutput; diff --git a/packages/pl-api/lib/entities/directory/server.ts b/packages/pl-api/lib/entities/directory/server.ts index 3c25e44bb8..ef8ea1b126 100644 --- a/packages/pl-api/lib/entities/directory/server.ts +++ b/packages/pl-api/lib/entities/directory/server.ts @@ -4,13 +4,13 @@ const directoryServerSchema = v.object({ domain: v.string(), version: v.string(), description: v.string(), - languages: z.array(v.string()), + languages: v.array(v.string()), region: v.string(), - categories: z.array(v.string()), - proxied_thumbnail: v.fallback(v.nullable(z.string().url()), null), + categories: v.array(v.string()), + proxied_thumbnail: v.fallback(v.nullable(v.pipe(v.string(), v.url())), null), blurhash: v.fallback(v.nullable(v.string()), null), - total_users: z.coerce.number(), - last_week_users: z.coerce.number(), + total_users: v.pipe(v.unknown(), v.transform(Number)), + last_week_users: v.pipe(v.unknown(), v.transform(Number)), approval_required: v.boolean(), language: v.string(), category: v.string(), diff --git a/packages/pl-api/lib/entities/directory/statistics-period.ts b/packages/pl-api/lib/entities/directory/statistics-period.ts index f5c909c162..e482643294 100644 --- a/packages/pl-api/lib/entities/directory/statistics-period.ts +++ b/packages/pl-api/lib/entities/directory/statistics-period.ts @@ -2,9 +2,9 @@ import * as v from 'valibot'; const directoryStatisticsPeriodSchema = v.object({ period: z.string().date(), - server_count: v.fallback(v.nullable(z.coerce.number()), null), - user_count: v.fallback(v.nullable(z.coerce.number()), null), - active_user_count: v.fallback(v.nullable(z.coerce.number()), null), + server_count: v.fallback(v.nullable(v.pipe(v.unknown(), v.transform(Number))), null), + user_count: v.fallback(v.nullable(v.pipe(v.unknown(), v.transform(Number))), null), + active_user_count: v.fallback(v.nullable(v.pipe(v.unknown(), v.transform(Number))), null), }); type DirectoryStatisticsPeriod = v.InferOutput; diff --git a/packages/pl-api/lib/entities/emoji-reaction.ts b/packages/pl-api/lib/entities/emoji-reaction.ts index 8b66d12f9d..0e46529fc5 100644 --- a/packages/pl-api/lib/entities/emoji-reaction.ts +++ b/packages/pl-api/lib/entities/emoji-reaction.ts @@ -10,10 +10,11 @@ const baseEmojiReactionSchema = v.object({ url: v.fallback(v.undefined(), undefined), static_url: v.fallback(v.undefined(), undefined), accounts: filteredArray(accountSchema), - account_ids: filteredArray(v.string()).catch([]), + account_ids: v.fallback(filteredArray(v.string()), []), }); -const customEmojiReactionSchema = baseEmojiReactionSchema.extend({ +const customEmojiReactionSchema = v.object({ + ...baseEmojiReactionSchema.entries, name: v.string(), url: v.pipe(v.string(), v.url()), static_url: v.pipe(v.string(), v.url()), @@ -27,7 +28,7 @@ const emojiReactionSchema = z.preprocess((reaction: any) => reaction ? { static_url: reaction.url, account_ids: reaction.accounts?.map((account: any) => account?.id), ...reaction, -} : null, baseEmojiReactionSchema.or(customEmojiReactionSchema)); +} : null, v.union([baseEmojiReactionSchema, customEmojiReactionSchema]); type EmojiReaction = v.InferOutput; diff --git a/packages/pl-api/lib/entities/group.ts b/packages/pl-api/lib/entities/group.ts index ee8f8bed85..1e08846966 100644 --- a/packages/pl-api/lib/entities/group.ts +++ b/packages/pl-api/lib/entities/group.ts @@ -13,11 +13,11 @@ const groupSchema = v.object({ emojis: filteredArray(customEmojiSchema), header: v.fallback(v.string(), ''), header_static: v.fallback(v.string(), ''), - id: z.coerce.string(), + id: v.pipe(v.unknown(), v.transform(String)), locked: v.fallback(v.boolean(), false), membership_required: v.fallback(v.boolean(), false), members_count: v.fallback(v.number(), 0), - owner: v.object({ id: v.string() }).nullable().catch(null), + owner: v.fallback(v.nullable(v.object({ id: v.string() })), null), note: z.string().transform(note => note === '

' ? '' : note).catch(''), relationship: v.fallback(v.nullable(groupRelationshipSchema), null), // Dummy field to be overwritten later statuses_visibility: v.fallback(v.string(), 'public'), diff --git a/packages/pl-api/lib/entities/instance.ts b/packages/pl-api/lib/entities/instance.ts index 4a1ebdb401..d289ec4d86 100644 --- a/packages/pl-api/lib/entities/instance.ts +++ b/packages/pl-api/lib/entities/instance.ts @@ -36,7 +36,7 @@ const instanceV1ToV2 = (data: any) => { uri, urls, ...instance - } = instanceV1Schema.parse(data); + } = v.parse(instanceV1Schema, data); return { ...instance, @@ -196,14 +196,14 @@ const pleromaSchema = coerceObject({ multitenancy: coerceObject({ domains: v.optional(v.array( v.object({ - domain: z.coerce.string(), + domain: v.pipe(v.unknown(), v.transform(String)), id: v.string(), public: v.fallback(v.boolean(), false), }), )), enabled: v.fallback(v.boolean(), false), }), - post_formats: z.string().array().optional().catch(undefined), + post_formats: v.fallback(v.optional(v.array(v.string())), undefined), restrict_unauthenticated: coerceObject({ activities: coerceObject({ local: v.fallback(v.boolean(), false), @@ -222,8 +222,8 @@ const pleromaSchema = coerceObject({ translation: coerceObject({ allow_remote: v.fallback(v.boolean(), true), allow_unauthenticated: v.fallback(v.boolean(), false), - source_languages: z.string().array().optional().catch(undefined), - target_languages: z.string().array().optional().catch(undefined), + source_languages: v.fallback(v.optional(v.array(v.string())), undefined), + target_languages: v.fallback(v.optional(v.array(v.string())), undefined), }), }), oauth_consumer_strategies: v.fallback(v.array(v.string()), []), diff --git a/packages/pl-api/lib/entities/list.ts b/packages/pl-api/lib/entities/list.ts index 2a0cac1c47..2ffc350a45 100644 --- a/packages/pl-api/lib/entities/list.ts +++ b/packages/pl-api/lib/entities/list.ts @@ -2,7 +2,7 @@ import * as v from 'valibot'; /** @see {@link https://docs.joinmastodon.org/entities/List/} */ const listSchema = v.object({ - id: z.coerce.string(), + id: v.pipe(v.unknown(), v.transform(String)), title: v.string(), replies_policy: v.fallback(v.optional(v.string()), undefined), exclusive: v.fallback(v.optional(v.boolean()), undefined), diff --git a/packages/pl-api/lib/entities/marker.ts b/packages/pl-api/lib/entities/marker.ts index 07bb579e3d..9576a98035 100644 --- a/packages/pl-api/lib/entities/marker.ts +++ b/packages/pl-api/lib/entities/marker.ts @@ -9,7 +9,7 @@ const markerSchema = z.preprocess((marker: any) => marker ? ({ last_read_id: v.string(), version: v.pipe(v.number(), v.integer()), updated_at: dateSchema, - unread_count: z.number().int().optional().catch(undefined), + unread_count: v.fallback(v.optional(v.pipe(v.number(), v.integer())), undefined), })); /** @see {@link https://docs.joinmastodon.org/entities/Marker/} */ diff --git a/packages/pl-api/lib/entities/media-attachment.ts b/packages/pl-api/lib/entities/media-attachment.ts index be24ac5065..341378f8a0 100644 --- a/packages/pl-api/lib/entities/media-attachment.ts +++ b/packages/pl-api/lib/entities/media-attachment.ts @@ -19,7 +19,7 @@ const baseAttachmentSchema = v.object({ type: v.string(), url: v.fallback(v.pipe(v.string(), v.url()), ''), preview_url: v.fallback(v.pipe(v.string(), v.url()), ''), - remote_url: v.fallback(v.nullable(z.string().url()), null), + remote_url: v.fallback(v.nullable(v.pipe(v.string(), v.url())), null), description: v.fallback(v.string(), ''), blurhash: v.fallback(v.nullable(blurhashSchema), null), @@ -29,11 +29,12 @@ const baseAttachmentSchema = v.object({ const imageMetaSchema = v.object({ width: v.number(), height: v.number(), - size: z.string().regex(/\d+x\d+$/).nullable().catch(null), + size: v.fallback(v.nullable(v.pipe(v.string(), v.regex(/\d+x\d+$/))), null), aspect: v.fallback(v.nullable(v.number()), null), }); -const imageAttachmentSchema = baseAttachmentSchema.extend({ +const imageAttachmentSchema = v.object({ + ...baseAttachmentSchema.entries, type: v.literal('image'), meta: v.fallback(v.object({ original: v.fallback(v.optional(imageMetaSchema), undefined), @@ -45,7 +46,8 @@ const imageAttachmentSchema = baseAttachmentSchema.extend({ }), {}), }); -const videoAttachmentSchema = baseAttachmentSchema.extend({ +const videoAttachmentSchema = v.object({ + ...baseAttachmentSchema.entries, type: v.literal('video'), meta: v.fallback(v.object({ duration: v.fallback(v.optional(v.number()), undefined), @@ -58,7 +60,8 @@ const videoAttachmentSchema = baseAttachmentSchema.extend({ }), {}), }); -const gifvAttachmentSchema = baseAttachmentSchema.extend({ +const gifvAttachmentSchema = v.object({ + ...baseAttachmentSchema.entries, type: v.literal('gifv'), meta: v.fallback(v.object({ duration: v.fallback(v.optional(v.number()), undefined), @@ -66,7 +69,8 @@ const gifvAttachmentSchema = baseAttachmentSchema.extend({ }), {}), }); -const audioAttachmentSchema = baseAttachmentSchema.extend({ +const audioAttachmentSchema = v.object({ + ...baseAttachmentSchema.entries, type: v.literal('audio'), meta: v.fallback(v.object({ duration: v.fallback(v.optional(v.number()), undefined), @@ -83,7 +87,8 @@ const audioAttachmentSchema = baseAttachmentSchema.extend({ }), {}), }); -const unknownAttachmentSchema = baseAttachmentSchema.extend({ +const unknownAttachmentSchema = v.object({ + ...baseAttachmentSchema.entries, type: v.literal('unknown'), }); @@ -96,7 +101,7 @@ const mediaAttachmentSchema = z.preprocess((data: any) => { preview_url: data.url, ...data, }; -}, z.discriminatedUnion('type', [ +}, v.variant('type', [ imageAttachmentSchema, videoAttachmentSchema, gifvAttachmentSchema, diff --git a/packages/pl-api/lib/entities/notification-request.ts b/packages/pl-api/lib/entities/notification-request.ts index 75372ec7bc..a03ace66e6 100644 --- a/packages/pl-api/lib/entities/notification-request.ts +++ b/packages/pl-api/lib/entities/notification-request.ts @@ -10,7 +10,7 @@ const notificationRequestSchema = v.object({ created_at: dateSchema, updated_at: dateSchema, account: accountSchema, - notifications_count: z.coerce.string(), + notifications_count: v.pipe(v.unknown(), v.transform(String)), last_status: v.fallback(v.optional(statusSchema), undefined), }); diff --git a/packages/pl-api/lib/entities/poll.ts b/packages/pl-api/lib/entities/poll.ts index 1862db1181..d95c7802c4 100644 --- a/packages/pl-api/lib/entities/poll.ts +++ b/packages/pl-api/lib/entities/poll.ts @@ -17,10 +17,10 @@ const pollSchema = v.object({ expires_at: v.fallback(v.nullable(z.string().datetime()), null), id: v.string(), multiple: v.fallback(v.boolean(), false), - options: z.array(pollOptionSchema).min(2), + options: v.array(pollOptionSchema).min(2), voters_count: v.fallback(v.number(), 0), votes_count: v.fallback(v.number(), 0), - own_votes: v.fallback(v.nullable(z.number()).nonempty(), null), + own_votes: v.fallback(v.nullable(v.array(v.number())).nonempty(), null), voted: v.fallback(v.boolean(), false), non_anonymous: v.fallback(v.boolean(), false), diff --git a/packages/pl-api/lib/entities/scheduled-status.ts b/packages/pl-api/lib/entities/scheduled-status.ts index a0ce78995f..c844c2fc47 100644 --- a/packages/pl-api/lib/entities/scheduled-status.ts +++ b/packages/pl-api/lib/entities/scheduled-status.ts @@ -9,19 +9,19 @@ const scheduledStatusSchema = v.object({ scheduled_at: z.string().datetime({ offset: true }), params: v.object({ text: v.fallback(v.nullable(v.string()), null), - poll: v.object({ - options: z.array(v.string()), - expires_in: z.coerce.string(), + poll: v.fallback(v.nullable(v.object({ + options: v.array(v.string()), + expires_in: v.pipe(v.unknown(), v.transform(String)), multiple: v.fallback(v.optional(v.boolean()), undefined), hide_totals: v.fallback(v.optional(v.boolean()), undefined), - }).nullable().catch(null), + })), null), media_ids: v.fallback(v.nullable(v.string()), null), - sensitive: v.fallback(v.nullable(z.coerce.boolean()), null), + sensitive: v.fallback(v.nullable(v.pipe(v.unknown(), v.transform(Boolean))), null), spoiler_text: v.fallback(v.nullable(v.string()), null), - visibility: z.string().catch('public'), + visibility: v.fallback(v.string(), 'public'), in_reply_to_id: v.fallback(v.nullable(v.string()), null), language: v.fallback(v.nullable(v.string()), null), - application_id: v.fallback(v.nullable(z.number().int()), null), + application_id: v.fallback(v.nullable(v.pipe(v.number(), v.integer())), null), scheduled_at: v.fallback(v.nullable(z.string().datetime({ offset: true })), null), idempotency: v.fallback(v.nullable(v.string()), null), with_rate_limit: v.fallback(v.boolean(), false), diff --git a/packages/pl-api/lib/entities/scrobble.ts b/packages/pl-api/lib/entities/scrobble.ts index 1dc838ee98..a2766e704a 100644 --- a/packages/pl-api/lib/entities/scrobble.ts +++ b/packages/pl-api/lib/entities/scrobble.ts @@ -6,7 +6,7 @@ const scrobbleSchema = z.preprocess((scrobble: any) => scrobble ? { external_link: scrobble.externalLink, ...scrobble, } : null, v.object({ - id: z.coerce.string(), + id: v.pipe(v.unknown(), v.transform(String)), account: accountSchema, created_at: z.string().datetime({ offset: true }), title: v.string(), diff --git a/packages/pl-api/lib/entities/status-edit.ts b/packages/pl-api/lib/entities/status-edit.ts index 0820e79111..aacd65c411 100644 --- a/packages/pl-api/lib/entities/status-edit.ts +++ b/packages/pl-api/lib/entities/status-edit.ts @@ -9,14 +9,14 @@ import { dateSchema, filteredArray } from './utils'; const statusEditSchema = v.object({ content: v.fallback(v.string(), ''), spoiler_text: v.fallback(v.string(), ''), - sensitive: z.coerce.boolean(), + sensitive: v.pipe(v.unknown(), v.transform(Boolean)), created_at: dateSchema, account: accountSchema, - poll: v.object({ - options: z.array(v.object({ + poll: v.fallback(v.nullable(v.object({ + options: v.array(v.object({ title: v.string(), })), - }).nullable().catch(null), + })), null), media_attachments: filteredArray(mediaAttachmentSchema), emojis: filteredArray(customEmojiSchema), }); diff --git a/packages/pl-api/lib/entities/status.ts b/packages/pl-api/lib/entities/status.ts index 8d31969651..a013c44be9 100644 --- a/packages/pl-api/lib/entities/status.ts +++ b/packages/pl-api/lib/entities/status.ts @@ -42,13 +42,13 @@ const baseStatusSchema = v.object({ created_at: dateSchema, account: accountSchema, content: v.fallback(v.string(), ''), - visibility: z.string().catch('public'), - sensitive: z.coerce.boolean(), + visibility: v.fallback(v.string(), 'public'), + sensitive: v.pipe(v.unknown(), v.transform(Boolean)), spoiler_text: v.fallback(v.string(), ''), media_attachments: filteredArray(mediaAttachmentSchema), application: v.fallback(v.nullable(v.object({ name: v.string(), - website: v.fallback(v.nullable(z.string().url()), null), + website: v.fallback(v.nullable(v.pipe(v.string(), v.url())), null), })), null), mentions: filteredArray(mentionSchema), tags: filteredArray(tagSchema), @@ -64,11 +64,11 @@ const baseStatusSchema = v.object({ language: v.fallback(v.nullable(v.string()), null), text: v.fallback(v.nullable(v.string()), null), edited_at: v.fallback(v.nullable(z.string().datetime()), null), - favourited: z.coerce.boolean(), - reblogged: z.coerce.boolean(), - muted: z.coerce.boolean(), - bookmarked: z.coerce.boolean(), - pinned: z.coerce.boolean(), + favourited: v.pipe(v.unknown(), v.transform(Boolean)), + reblogged: v.pipe(v.unknown(), v.transform(Boolean)), + muted: v.pipe(v.unknown(), v.transform(Boolean)), + bookmarked: v.pipe(v.unknown(), v.transform(Boolean)), + pinned: v.pipe(v.unknown(), v.transform(Boolean)), filtered: filteredArray(filterResultSchema), approval_status: v.fallback(v.nullable(v.picklist(['pending', 'approval', 'rejected'])), null), group: v.fallback(v.nullable(groupSchema), null), @@ -97,7 +97,7 @@ const baseStatusSchema = v.object({ spoiler_text_map: v.fallback(v.nullable(v.record(v.string(), v.string())), null), dislikes_count: v.fallback(v.number(), 0), - disliked: z.coerce.boolean().catch(false), + disliked: v.fallback(v.pipe(v.unknown(), v.transform(Boolean)), false), interaction_policy: interactionPolicySchema, }); diff --git a/packages/pl-api/lib/entities/streaming-event.ts b/packages/pl-api/lib/entities/streaming-event.ts index cdd3ffabf4..45a9f6531f 100644 --- a/packages/pl-api/lib/entities/streaming-event.ts +++ b/packages/pl-api/lib/entities/streaming-event.ts @@ -25,54 +25,64 @@ const followRelationshipUpdateSchema = v.object({ type FollowRelationshipUpdate = v.InferOutput; const baseStreamingEventSchema = v.object({ - stream: z.array(v.string()).catch([]), + stream: v.fallback(v.array(v.string()), []), }); -const statusStreamingEventSchema = baseStreamingEventSchema.extend({ +const statusStreamingEventSchema = v.object({ + ...baseStreamingEventSchema.entries, event: v.picklist(['update', 'status.update']), payload: z.preprocess((payload: any) => JSON.parse(payload), statusSchema), }); -const stringStreamingEventSchema = baseStreamingEventSchema.extend({ +const stringStreamingEventSchema = v.object({ + ...baseStreamingEventSchema.entries, event: v.picklist(['delete', 'announcement.delete']), payload: v.string(), }); -const notificationStreamingEventSchema = baseStreamingEventSchema.extend({ +const notificationStreamingEventSchema = v.object({ + ...baseStreamingEventSchema.entries, event: v.literal('notification'), payload: z.preprocess((payload: any) => JSON.parse(payload), notificationSchema), }); -const emptyStreamingEventSchema = baseStreamingEventSchema.extend({ +const emptyStreamingEventSchema = v.object({ + ...baseStreamingEventSchema.entries, event: v.literal('filters_changed'), }); -const conversationStreamingEventSchema = baseStreamingEventSchema.extend({ +const conversationStreamingEventSchema = v.object({ + ...baseStreamingEventSchema.entries, event: v.literal('conversation'), payload: z.preprocess((payload: any) => JSON.parse(payload), conversationSchema), }); -const announcementStreamingEventSchema = baseStreamingEventSchema.extend({ +const announcementStreamingEventSchema = v.object({ + ...baseStreamingEventSchema.entries, event: v.literal('announcement'), payload: z.preprocess((payload: any) => JSON.parse(payload), announcementSchema), }); -const announcementReactionStreamingEventSchema = baseStreamingEventSchema.extend({ +const announcementReactionStreamingEventSchema = v.object({ + ...baseStreamingEventSchema.entries, event: v.literal('announcement.reaction'), payload: z.preprocess((payload: any) => JSON.parse(payload), announcementReactionSchema), }); -const chatUpdateStreamingEventSchema = baseStreamingEventSchema.extend({ +const chatUpdateStreamingEventSchema = v.object({ + ...baseStreamingEventSchema.entries, event: v.literal('chat_update'), payload: z.preprocess((payload: any) => JSON.parse(payload), chatSchema), }); -const followRelationshipsUpdateStreamingEventSchema = baseStreamingEventSchema.extend({ +const followRelationshipsUpdateStreamingEventSchema = v.object({ + ...baseStreamingEventSchema.entries, event: v.literal('follow_relationships_update'), payload: z.preprocess((payload: any) => JSON.parse(payload), followRelationshipUpdateSchema), }); -const respondStreamingEventSchema = baseStreamingEventSchema.extend({ +const respondStreamingEventSchema = v.object({ + ...baseStreamingEventSchema.entries, event: v.literal('respond'), payload: z.preprocess((payload: any) => JSON.parse(payload), v.object({ type: v.string(), @@ -80,7 +90,8 @@ const respondStreamingEventSchema = baseStreamingEventSchema.extend({ })), }); -const markerStreamingEventSchema = baseStreamingEventSchema.extend({ +const markerStreamingEventSchema = v.object({ + ...baseStreamingEventSchema.entries, event: v.literal('marker'), payload: z.preprocess((payload: any) => JSON.parse(payload), markersSchema), }); @@ -89,7 +100,7 @@ const markerStreamingEventSchema = baseStreamingEventSchema.extend({ const streamingEventSchema: z.ZodType = z.preprocess((event: any) => ({ ...event, event: event.event?.replace(/^pleroma:/, ''), -}), z.discriminatedUnion('event', [ +}), v.variant('event', [ statusStreamingEventSchema, stringStreamingEventSchema, notificationStreamingEventSchema, diff --git a/packages/pl-api/lib/entities/suggestion.ts b/packages/pl-api/lib/entities/suggestion.ts index 1ea79fb536..c45513455e 100644 --- a/packages/pl-api/lib/entities/suggestion.ts +++ b/packages/pl-api/lib/entities/suggestion.ts @@ -31,7 +31,7 @@ const suggestionSchema = z.preprocess((suggestion: any) => { return suggestion; }, v.object({ source: v.fallback(v.nullable(v.string()), null), - sources: z.array(v.string()).catch([]), + sources: v.fallback(v.array(v.string()), []), account: accountSchema, })); diff --git a/packages/pl-api/lib/entities/tag.ts b/packages/pl-api/lib/entities/tag.ts index 8c35bcc3be..60fe97ce7c 100644 --- a/packages/pl-api/lib/entities/tag.ts +++ b/packages/pl-api/lib/entities/tag.ts @@ -1,9 +1,9 @@ import * as v from 'valibot'; const historySchema = v.object({ - day: z.coerce.number(), - accounts: z.coerce.number(), - uses: z.coerce.number(), + 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} */ diff --git a/packages/pl-api/lib/entities/translation.ts b/packages/pl-api/lib/entities/translation.ts index 4ccc310d14..40eeee6a00 100644 --- a/packages/pl-api/lib/entities/translation.ts +++ b/packages/pl-api/lib/entities/translation.ts @@ -4,7 +4,7 @@ import { filteredArray } from './utils'; const translationPollSchema = v.object({ id: v.string(), - options: z.array(v.object({ + options: v.array(v.object({ title: v.string(), })), }); diff --git a/packages/pl-api/lib/entities/web-push-subscription.ts b/packages/pl-api/lib/entities/web-push-subscription.ts index 5632ae40da..6ed56360b0 100644 --- a/packages/pl-api/lib/entities/web-push-subscription.ts +++ b/packages/pl-api/lib/entities/web-push-subscription.ts @@ -2,9 +2,9 @@ import * as v from 'valibot'; /** @see {@link https://docs.joinmastodon.org/entities/WebPushSubscription/} */ const webPushSubscriptionSchema = v.object({ - id: z.coerce.string(), + id: v.pipe(v.unknown(), v.transform(String)), endpoint: v.string(), - alerts: v.record(v.string(), z.boolean()), + alerts: v.record(v.string(), v.boolean()), server_key: v.string(), });