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

Features to make libraries extend/play nice with ts-pattern #166

Open
WoLfulus opened this issue Jun 10, 2023 · 3 comments
Open

Features to make libraries extend/play nice with ts-pattern #166

WoLfulus opened this issue Jun 10, 2023 · 3 comments
Labels
enhancement New feature or request

Comments

@WoLfulus
Copy link

Is your feature request related to a problem? Please describe.

I found myself having to do a bunch of digging and workarounds to be able to "extend" ts-pattern to make my library able to play nice with it. I'm doing something similar to rust error handling, and I wanted to support cases like this:

class Response {}
class SuccessResponse extends Response {}
class JsonResponse extends SuccessResponse {}
class TextResponse extends SuccessResponse {}
class ErrorResponse extends Response {}
class AuthenticationError extends ErrorResponse {}

// ...

const res = await request('...');

const handled = match(res)
  .with(Ok(JsonResponse), handleJson)
  .with(Ok(TextResponse), handleText)
  .with(Ok(SuccessResponse), handleUnknownSuccess)
  .with(Ok(P.any), handleUnknownSuccessType)
  .with(Ok(), handleEmptySuccess)
  .with(Err(AuthenticationError), handleSuccess)
  .with(Err(ErrorResponse), handleUnknownError)
  .with(Err(P.any), handleUnknownErrorType)
  .with(Err(), handleEmptyErr)
  .otherwise(() => false);
  

I did manage to make this "work", but with a lot of workarounds.

Describe the solution you'd like

  • Be able to create custom patterns (+ play nice with selects if the custom accepts custom patterns as inputs)
    • Make it easier to check if a value is a pattern/guard
    • Export symbols so they can be used by custom patterns and guards
    • Create symbols with Symbol.for() instead of Symbol()
      • I had a case where I had two versions of ts-pattern installed by the package manager, and patterns from one version didn't work with the other one.
    • Provide the interface and a way to easily create custom pattern matchers
      • Be able to easily select/compute what to return from those custom pattern matchers
    • Be able to P.select() with symbol keys to avoid clashing with end user strings

Describe alternatives you've considered

I'm sure there might be an easier way to do this, but this is what I came up with:

https://codesandbox.io/p/sandbox/extend-ts-pattern-nly6vh

I did a bunch of workarounds to get to what I expected the usage to be:

https://codesandbox.io/p/sandbox/extend-ts-pattern-nly6vh?file=%2Fsrc%2Findex.test.ts%3A32%2C1-53%2C1

I'm still unable to create my own custom guards that lets the user select a different subset of information easily:

https://codesandbox.io/p/sandbox/extend-ts-pattern-nly6vh?file=%2Fsrc%2Findex.test.ts%3A75%2C1-82%2C1

Even though it works close to what I expect, it will all fall apart if you strong type "res" to anything other than any when calling match() (for example res: Response, which is the base class)

@WoLfulus WoLfulus added the enhancement New feature or request label Jun 10, 2023
@WoLfulus
Copy link
Author

Thanks for v5! It addresses some of the isseus I had, but ending with multiple instances of matcher symbol is still a huge problem. I couldn't debug enough to see where the issue comes from, but I'm using next.js with ts-pattern and my librart as a dependency (mine has ts-pattern as a peer dependency). They both targets the same ts-pattern version and pnpm reports only 1 installation, so I think it's a bundler problem in this case.

I'm unable to create a pattern inside my package and use it inside the next.js project because ts-pattern won't be able to check for the [matcher] symbol, and I'd like to avoid having to inject ts-pattern instance into my package, since it completely breaks DX I'm hoping for :(

Anyways, I think turning matcher into a Symbol.for() would help in this specific case.

@gvergnaud
Copy link
Owner

Thanks for opening this issue @WoLfulus

I didn't realize there would be a problem when using two concurrent versions of ts-pattern in the same codebase. Using Symbol.for(...) makes sense to me, I'll change this in the next patch version.

I'd love to make TS-Pattern better support custom matchers too. As you've noticed, v5 includes an experimental version of it, but I'd like to make it fully support P.select and beta test it with a few people before I start considering it stable. If you're interested in giving feedback, I can let you know when I have a version I'm satisfied with :)

@WoLfulus
Copy link
Author

I'd love to give it a shot! I understand that what I'm asking isn't something a regular user normally does, but it cleaned up a lot of my codebases, and I'm trying to keep native support to it in all my libraries. What I'm asking are just some of the hard parts I had to build ugly workarounds to make it work :P

Let me know how to proceed and I'm more than happy to help!

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

No branches or pull requests

2 participants