Skip to content
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

feat: flavored types #950

Open
wants to merge 1 commit into
base: main
Choose a base branch
from
Open

feat: flavored types #950

wants to merge 1 commit into from

Conversation

vktrl
Copy link

@vktrl vktrl commented Dec 2, 2024

Less strict version of brand. The code is basically the same, just a couple of more test cases.

@vktrl vktrl changed the title Flavored types feat: flavored types Dec 2, 2024
@fabian-hiller
Copy link
Owner

Thank you for creating this PR. Can you explain the pros and cons of flavor vs. brand? What are the use cases for flavor? Do other schema libraries have the same or a similar feature in addition to brand?

@fabian-hiller fabian-hiller self-assigned this Dec 4, 2024
@fabian-hiller fabian-hiller added enhancement New feature or request question Further information is requested labels Dec 4, 2024
@vktrl
Copy link
Author

vktrl commented Dec 4, 2024

Thank you for creating this PR. Can you explain the pros and cons of flavor vs. brand? What are the use cases for flavor? Do other schema libraries have the same or a similar feature in addition to brand?

I'm not aware of other validation libraries using flavored types in addition to branded ones, but there's a database schema library called kanel that makes use of them. The purpose there is to avoid mixing up ids of different entities, but not require the use of a validation library or custom functions for casting new ids to be used as arguments.

I prefer using flavored types for ids and ISO timestamps because all the existing values are more or less guaranteed to be flavored from either validated API inputs or database queries. There's a loss in ergonomics for no or very little extra safety when branded types force me to implement wrapped functions a la createUserUuidv4() or createUuidv4('UserId') or toIsoTimestamp(new Date()). There are some more examples in this blog post.

tl;dr - flavored types don't get in my way, but will stop me from mixing up validated data (and all my data is validated.) In critical places I use runtime assertions and/or branded types.

@fabian-hiller
Copy link
Owner

Is the only difference that the FlavorSymbol key is optional while the BrandSymbol key is required?

@vktrl
Copy link
Author

vktrl commented Dec 5, 2024

Is the only difference that the FlavorSymbol key is optional while the BrandSymbol key is required?

That's right.

@fabian-hiller
Copy link
Owner

fabian-hiller commented Dec 5, 2024

Am I understanding correctly that an unflavored type can be passed on to a flavored type and that's the difference too brand and the idea behind flavor? Here is an example:

type A = string & { foo?: 'A' };
type B = string & { foo?: 'B' };

function createA(): A {
  return 'hello';
}

const test1: A = createA();       // This works
const test2: A = 'hello';         // This works (but would not work with `brand`)
const test3: string = createA();  // This works
const test4: B = createA();       // But this does not work

@vktrl
Copy link
Author

vktrl commented Dec 5, 2024

Yes, I added similar examples to the docs here.

In essence:

  • Flavored<string, 'a'> is a valid string and vice versa
  • Flavored<string, 'b'> is not a valid Flavored<string, 'a'>

@fabian-hiller
Copy link
Owner

In general, I like the idea of a flavor action. I am just careful not to make the library too complicated. It will be very important to document the difference to brand well so that users do not get confused. Do you have an alternative name for this action?

@vktrl
Copy link
Author

vktrl commented Dec 7, 2024

Maybe weakBrand? Could even use the same symbol for convenient interop.

@fabian-hiller
Copy link
Owner

Here is a list of names with Brand as a suffix:

  • weakBrand
  • softBrand
  • partialBrand
  • optionalBrand

And here is a list of completely new and independent names:

  • flavor
  • mark
  • tag
  • flag
  • tint

What's your favorite so far?

@vktrl
Copy link
Author

vktrl commented Dec 8, 2024

I like weakBrand the most + using the same symbol. I'll update the PR if you're ok with that.

@fabian-hiller
Copy link
Owner

I am not sure we should use the same symbol. This could cause problems when people extend existing schemas that end up using both actions. Let me think about the name a bit more. I'm not sure what's best yet.

@vktrl
Copy link
Author

vktrl commented Dec 8, 2024

I'm not sure about that. Having interop and having the ability to easily make schemas more or less strict sounds useful and convenient. Extending schemas is explicit and messing up the type by (re)branding or (re)flavoring it (possibly with a different name) would possible no matter what the feature is called or the symbol used.

@fabian-hiller
Copy link
Owner

Thanks for your feedback. I will have to investigate this before moving forward. I may prioritize our v1 RC first before doing this.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
enhancement New feature or request question Further information is requested
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants