Z
Zod•3mo ago
Steve

Steve - I was wondering if there is a certain w...

I was wondering if there is a certain way to further optimize the .refine. And if possible, also improve the typing of it. Basically also wondering I would be able to type TipTapTextDto['marks'] In a way that I define it to be an array of TipTapMarkDto but also it to have at least one IdentifierMarkDto, no matter the order in which the TipTapMarkDto present themselves.
import { z } from 'zod'

const SimpleMarkDto = <T extends string>(typeName: T) =>
z.object({
type: z.literal(typeName),
})

const IdentifierMarkDto = z
.object({
type: z.literal('identifier'),
attrs: z.object({
identifier: z.string(),
}),
})
.strict()

const BoldMarkDto = SimpleMarkDto('bold')
const ItalicMarkDto = SimpleMarkDto('italic')
const StrikeMarkDto = SimpleMarkDto('strike')
const CodeMarkDto = SimpleMarkDto('code')
const SubscriptMarkDto = SimpleMarkDto('subscript')
const SuperscriptMarkDto = SimpleMarkDto('superscript')
const UnderlineMarkDto = SimpleMarkDto('underline')

export const TipTapMarkDto = z.union([
SuperscriptMarkDto,
SubscriptMarkDto,
CodeMarkDto,
StrikeMarkDto,
BoldMarkDto,
ItalicMarkDto,
UnderlineMarkDto,
IdentifierMarkDto,
])
export type TipTapMarkDto = z.infer<typeof TipTapMarkDto>

export const TipTapTextDto = z
.object({
type: z.literal('text'),
text: z.string(),
marks: z.array(TipTapMarkDto).default([]),
})
.strict()
.refine((dto) => dto.marks.some((mark) => mark.type === 'identifier'), {
message: 'Should have an identifier',
})
export type TipTapTextDto = z.infer<typeof TipTapTextDto>
import { z } from 'zod'

const SimpleMarkDto = <T extends string>(typeName: T) =>
z.object({
type: z.literal(typeName),
})

const IdentifierMarkDto = z
.object({
type: z.literal('identifier'),
attrs: z.object({
identifier: z.string(),
}),
})
.strict()

const BoldMarkDto = SimpleMarkDto('bold')
const ItalicMarkDto = SimpleMarkDto('italic')
const StrikeMarkDto = SimpleMarkDto('strike')
const CodeMarkDto = SimpleMarkDto('code')
const SubscriptMarkDto = SimpleMarkDto('subscript')
const SuperscriptMarkDto = SimpleMarkDto('superscript')
const UnderlineMarkDto = SimpleMarkDto('underline')

export const TipTapMarkDto = z.union([
SuperscriptMarkDto,
SubscriptMarkDto,
CodeMarkDto,
StrikeMarkDto,
BoldMarkDto,
ItalicMarkDto,
UnderlineMarkDto,
IdentifierMarkDto,
])
export type TipTapMarkDto = z.infer<typeof TipTapMarkDto>

export const TipTapTextDto = z
.object({
type: z.literal('text'),
text: z.string(),
marks: z.array(TipTapMarkDto).default([]),
})
.strict()
.refine((dto) => dto.marks.some((mark) => mark.type === 'identifier'), {
message: 'Should have an identifier',
})
export type TipTapTextDto = z.infer<typeof TipTapTextDto>
Solution:
You could use something like a brand to indicate to the consumer that it has been verified to contain a certain element, but the type system still wouldn't know how to encode that information, so you'd still have to code defensively
Jump to solution
5 Replies
Steve
SteveOP•3mo ago
The first optimization of this is ofcourse rewriting it to this:
export const TipTapTextDto = z
.object({
type: z.literal('text'),
text: z.string(),
marks: z
.array(TipTapMarkDto)
.default([])
.refine((marks) => marks.some((mark) => mark.type === 'identifier'), {
message: 'Should have an identifier',
}),
})
.strict()

export type TipTapTextDto = z.infer<typeof TipTapTextDto>
export const TipTapTextDto = z
.object({
type: z.literal('text'),
text: z.string(),
marks: z
.array(TipTapMarkDto)
.default([])
.refine((marks) => marks.some((mark) => mark.type === 'identifier'), {
message: 'Should have an identifier',
}),
})
.strict()

export type TipTapTextDto = z.infer<typeof TipTapTextDto>
Scott Trinh
Scott Trinh•3mo ago
In a way that I define it to be an array of TipTapMarkDto but also it to have at least one IdentifierMarkDto, no matter the order in which the TipTapMarkDto present themselves.
No 😅 . If you don't know exactly where it is (like first, or last, or third, etc) you cannot encode that invariant in the type itself. That's basically a feature of a dependent type system and TypeScript doesn't have a real dependent type system.
Solution
Scott Trinh
Scott Trinh•3mo ago
You could use something like a brand to indicate to the consumer that it has been verified to contain a certain element, but the type system still wouldn't know how to encode that information, so you'd still have to code defensively
Steve
SteveOP•3mo ago
😦 too bad
Scott Trinh
Scott Trinh•3mo ago
I guess one work around here is to use a combination of a runtime check with a branded type, and then write a function to extract the known value from the array that makes a type assertion. Or you can skip the branding and make a falliable function that will return the value if it exists or throw.
Want results from more Discord servers?
Add your server