-
-
Notifications
You must be signed in to change notification settings - Fork 1.2k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Add z.literal.template()
#1786
Add z.literal.template()
#1786
Conversation
❌ Deploy Preview for guileless-rolypoly-866f8a failed.
|
Infer works thus far. Going to bed, will continue tomorrow. 💤 |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
LGTM! 😎
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@igalklebanov thanks! This is some awesome functionality 🎉 . I added a couple of things I found but overall this is great!
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
👍 ! Looks good to me. Just one thought on the new error classes but its not a blocker for this to be merged IMO. Thank you again for your timely fixes 🙏
@maxArturo Thank you for the thorough review and great feedback! 💪 |
Any plans to add this? |
A quick dirty implementation: import { z } from "zod";
// this is already present in zod codebase
type Writeable<T> = { -readonly [P in keyof T]: T[P] };
type _TemplateLiteralPartOutput =
| string
| number
| boolean
| bigint
| null
| undefined;
type _TemplateLiteralPartInput =
| _TemplateLiteralPartOutput
| z.ZodType<_TemplateLiteralPartOutput>;
type _MapPart<T extends _TemplateLiteralPartInput> = T extends z.ZodTypeAny
? T["_output"]
: T;
type _ConcatParts<T extends _TemplateLiteralPartInput[]> = T extends [
infer THead extends _TemplateLiteralPartInput,
...infer TTail extends _TemplateLiteralPartInput[]
]
? `${_MapPart<THead>}${_ConcatParts<TTail>}`
: "";
function templateLiteral<T extends readonly _TemplateLiteralPartInput[]>(
parts: T
): _ConcatParts<Writeable<T>> {
throw "implementation here";
}
const a = templateLiteral([
`hello `,
z.string(),
"! I am ",
z.number(),
" years old",
] as const); |
@olehmisar Yeah. Read @colinhacks's comments here (which I agree with). |
Damn, looks like its failing some tests now. Will fix that later today. EDIT: didn't handle case insensitive regular expressions ( |
Hey @igalklebanov, any updates on this? Great work btw. 😄 Are you still considering @olehmisar 's API? |
None.
Never considered. It goes against the author's current design principles. |
Thanks! I think I misread the comment regarding the other API. Anything stopping this from being merged? Looks like everything is passing (except prettier check on README) |
I was looking for exactly a feature like this. Is there something blocking this PR, or just lack of bandwidth to review it? Note: in the meantime I was able to use Edit: I might be holding it wrong, but I'm having some trouble with |
@colinhacks Can this be merged? |
As time goes on I feel like this would be more and more useful for us every day. I've now got tons of cases where we use templated strings to differentiate different types of ids such as built-in vs custom entites, or for type-safe hierarchies (or both): type ContentTypeId = "Link" | "Video" | "Custom:${string}"
type ContentId = "${ContentTypeId}:${string}"
type CategoryId = "Sponsor" | "Resource" | "Custom:${string}"
type SubCategoryId = "${CategoryId}:${string}" Manually managing |
This is a tough one. For starters, this isn't the API I would advocate for - we can use tagged template literals here to make the API more isomorphic with the represented type. I've done some experiments and I believe this is workable. z.template`asdf${z.string()}${z.number()}`; That said, this is a lot of code and complexity (2k LOC) for a pretty niche feature. I see all the upvotes/reactions, but the total numbers are still fairly small. This also falls into the set of features (like z.discriminatedUnion) that are inelegant to implement, because they require a bunch of switch-like logic over multiple Zod subclasses to implement correctly. All told I'm not super enthusiastic about this in the short term. Zod 4 will have better treeshakability characteristics, which changes the calculus on whether to merge obscure-ish features like this. I'll leave this open and try to keep people posted as Zod 4 comes along. |
@colinhacks Your suggested API is cleaner for sure. Agree with all points. Excited about v4! How do you infer the literal text outside the interpolated positions? |
Co-authored-by: Max Arturo <5713763+maxArturo@users.noreply.github.com>
Co-authored-by: Max Arturo <5713763+maxArturo@users.noreply.github.com>
Co-authored-by: Max Arturo <5713763+maxArturo@users.noreply.github.com>
Co-authored-by: Max Arturo <5713763+maxArturo@users.noreply.github.com>
Co-authored-by: Max Arturo <5713763+maxArturo@users.noreply.github.com>
Co-authored-by: Max Arturo <5713763+maxArturo@users.noreply.github.com>
3f306c7
to
01065e8
Compare
Oops, yeah the tagged template literal API won't work. I got excited that the interpolations could be inferred but apparently the literal parts can't (as Igal said). Bummer. I just rebased this onto v4 and made some changes. cc @igalklebanov z.literal.template([ "asdf", z.number() ]);
|
Gonna merge this into Igal, amazing work on this! 🙌 |
[EDIT by @colinhacks]
This PR has been updated to support an array-based API:
The parsing remains early identical. The OP's original comment is below. Thanks Igal!
Hey 👋
Closes #419.
This PR adds
ZodTemplateLiteral
for building template literals. Should be used when a consumer wants to explictly get a template literal typescript type / union.A typescript template literal consists of string literal types and types in interpolated positions (the
${}
slots).Thus this new class has the following methods:
.interpolated(type)
accepts zod types with a string literal fitting output type (basically primitives)..literal(value)
accepts primitives. A shorthand for.interpolated(z.literal(value))
.These methods append the argument to the accumulated template literal.
Validation is regular expression based.
API:
(see unit tests for more examples).