Skip to content

fix: weaken internal group types to strengthen return/onCancel types #245

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

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

Conversation

43081j
Copy link
Collaborator

@43081j 43081j commented Mar 7, 2025

This changes a few things.

First of all, trying to infer T while defining T currently doesn't work unless there's a member without the results arg. This is a known limitation that may or may not be fixed in typescript one day.

Until that happens, if ever, this dumbs down the type of results to be a Record<PropertyKey, unknown>. This means within a function you lose strong typing, but the return type will now always be correct.

Secondly, T is actually the resulting already-awaited shape. This means we do not need Awaited<T[K]> since T[K] is already the awaited type.

For example:

type ActualType = {
  foo: number;
  bar: number;
};
group<ActualType>({
  foo: () => Promise.resolve(303),
  bar: () => Promise.resolve(808)
});

You can see the ActualType never needed Awaited<T[K]> on each type since it is already the final result.

Importantly this does weak types within the callbacks/functions. so we need to decide between these options:

  1. leave things as is. sometimes but not often, results is correctly typed and the return type is too
  2. merge this PR, results is weakly typed but everything else is always correctly typed

This changes a few things.

First of all, trying to infer `T` while defining `T` currently doesn't
work unless there's a member without the `results` arg. This is a known
limitation that may or may not be fixed in typescript one day.

Until that happens, if ever, this dumbs down the type of `results` to be
a `Record<PropertyKey, unknown>`. This means _within a function_ you
lose strong typing, but the return type will now always be correct.

Secondly, `T` is actually the resulting already-awaited shape. This
means we do not need `Awaited<T[K]>` since `T[K]` is already the awaited
type.

For example:

```ts
type ActualType = {
  foo: number;
  bar: number;
};
group<ActualType>({
  foo: () => Promise.resolve(303),
  bar: () => Promise.resolve(808)
});
```

You can see the `ActualType` never needed `Awaited<T>` on each type
since it is already the final result.
Copy link

changeset-bot bot commented Mar 7, 2025

⚠️ No Changeset found

Latest commit: 3e7a7de

Merging this PR will not cause a version bump for any packages. If these changes should not result in a new version, you're good to go. If these changes should result in a version bump, you need to add a changeset.

This PR includes no changesets

When changesets are added to this PR, you'll see the packages that this PR includes changesets for and the associated semver types

Click here to learn what changesets are, and how to add one.

Click here if you're a maintainer who wants to add a changeset to this PR

@43081j 43081j requested a review from natemoo-re March 7, 2025 14:31
Copy link

pkg-pr-new bot commented Mar 7, 2025

Open in Stackblitz@example/basic@example/changesets

npm i https://pkg.pr.new/bombshell-dev/clack/@clack/core@245
npm i https://pkg.pr.new/bombshell-dev/clack/@clack/prompts@245

commit: 3e7a7de

@43081j
Copy link
Collaborator Author

43081j commented Mar 7, 2025

some examples before this PR:

// Correct return type, correct `results` type in 2nd function
group({
  foo: () => {
    return Promise.resolve(303);
  },
  bar: ({results}) => {
    results.foo; // works, its a number!

    return Promise.resolve(808);
  }
});

// Incorrect return type of {}, incorrect `results` type in both functions of {}
group({
  foo: ({results}) => {
    results.bar; // doesn't work, {} has no bar property

    return Promise.resolve(303);
  },
  bar: ({results}) => {
    results.foo; // doesn't work, {} has no foo property

    return Promise.resolve(808)
  }
});

examples after this PR:

// Correct return type, weak `results` type in functions
group({
  foo: () => {
    return Promise.resolve(303);
  },
  bar: ({results}) => {
    results.foo; // works, but its unknown!

    return Promise.resolve(808);
  }
});

// Correct return type, weak `results` type in both functions
group({
  foo: ({results}) => {
    results.bar; // works but is unknown!

    return Promise.resolve(303);
  },
  bar: ({results}) => {
    results.foo; // works but is unknown!

    return Promise.resolve(808)
  }
});

@MacFJA
Copy link
Contributor

MacFJA commented Mar 8, 2025

Another approach is using class: Typescript Playground

Usage screenshot

image

With this:

  • each step only knows previous steps result
  • the "on cancel" action has a partial of the steps' type,
  • the resulting type is the complete type

But the DX changes, it's not just a simple function with 2 parameters, but a class instance with multiple functions calls

@43081j
Copy link
Collaborator Author

43081j commented Mar 9, 2025

Yes that pretty much works just because of the method chaining. But I don't think we can change the dx so drastically and inconsistently with the other functions

So we're still left with "weak types inside, but correct everywhere" or "strong types everywhere, but incorrect in most situations"

@natemoo-re
Copy link
Member

Hmm I'm still not sure where I fall on this... we are looking ahead to a stable v1, so I think it would be okay to introduce a new approach for grouping prompts and deprecate group() until removal in v2. I know we're on a v0, so it's technically okay to rip the band-aid off, but that doesn't sit well with me.

@43081j
Copy link
Collaborator Author

43081j commented Mar 31, 2025

that makes sense to me

introduce a new interface we can more easily type too and just drop the old one in 2.x

we can have a think about the shape of that on discord

@MacFJA
Copy link
Contributor

MacFJA commented Apr 21, 2025

Related to #139 (for the chaining implementation)

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

Successfully merging this pull request may close these issues.

3 participants