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: have typings based on the inputs of a procedure #3311

Closed
1 task
iduuck opened this issue Nov 29, 2022 · 4 comments
Closed
1 task

feat: have typings based on the inputs of a procedure #3311

iduuck opened this issue Nov 29, 2022 · 4 comments
Labels

Comments

@iduuck
Copy link
Contributor

iduuck commented Nov 29, 2022

Describe the feature you'd like to request

It's very hard to describe, but I have a strongly typed codebase, and I have methods, that are returning something based on their parameters.

I want to see the same happening with trpc. At this point, wherever my code is using trpc, I am getting back a "less strongly typed" version of the functions, because it is returning a union, instead of a definitive return value of the function I have been calling in the procedure.

Describe the solution you'd like to see

Let's say, I have a function like this:

interface ConvertedValues {
	[key: string]: string
}

interface ConvertedDependencies extends ConvertedValues {}

function convertValuesToObject<
	B extends boolean
>(values: Value[], includeDependencies: B): B extends true
	? { values: ConvertedValues, dependencies: ConvertedDependencies }
	: { values: ConvertedValues } {
	// Do some stuff to return the specified things here.
}

This function, is what I want to call in the procedure, and based on the input, I want to get back the right type, and not a union of both possible return types.

Describe alternate solutions

Non, as of now.

Additional information

No response

👨‍👧‍👦 Contributing

  • 🙋‍♂️ Yes, I'd be down to file a PR implementing this feature!
@KATT
Copy link
Member

KATT commented Nov 29, 2022

I think this requires "higher kinded types" unfortunately:

In this case, you'll have to split it up into two procedures, one of each part of the union.

@KATT KATT added the blocked label Nov 29, 2022
@iduuck
Copy link
Contributor Author

iduuck commented Nov 30, 2022

Splitting this up into two procedures ended up being my "alternate solution" for this. Thanks though. I will have a look if I can optimize trpc a little to support those cases, but I think unfortunately my skills are limited when it comes to too complex types.

@KATT
Copy link
Member

KATT commented Nov 30, 2022

I frankly think it's impossible with how TypeScript currently works and with us using runtime validators. If we instead used compile-time magic to add validators & assert the types based on a TypeScript-generic, it could be possible, but we're not going down that route

@KATT KATT closed this as not planned Won't fix, can't repro, duplicate, stale Nov 30, 2022
@iduuck
Copy link
Contributor Author

iduuck commented Dec 8, 2022

Ok, so. I dig in this a little and found a way to make this work (with a little hacky-hacky going on).

The fix I did was not to build another procedure, but to build a custom hook based upon the hook helpers trpc is exposing.

I am not able to share a lot of the source code, since the very most is confidential, but this is what I am able to share:

Entrypoint

const objects = {
	object1: { ...someConfiguration } as const,
	object2: { ...someConfiguration } as const,
} as const;

type ObjectIds = keyof typeof objects;

Procedure

answers: protectedProcedure
  .input(
    z.object({
      objectId: z.string(),
    }),
  )
  .query(async function ({ input, ctx }) {
    if (!isValidObjectId(input.objectId))
      return { answers: {}, dependencies: {} };

    const {
      [input.objectId]: { answers, dependencies },
    } = await findAndFormatObjectAnswersBatch([input.objectId], ctx.userId);

    return { answers, dependencies };
  }),

So, basically, what I wanted to achieve is, having a hook that is typed based upon which objectId I am going to add to the params. This (with a normal function) can be achieved by infering types.

Hook

// @note: `ObjectIds` is a `keyof` type based on the configuration object of all objects.

export function useAnswers<ObjectId extends ObjectIds>(
  objectId: ObjectId,
) {
  return trpc.objects.answers.useQuery<
    inferProcedureOutput<"objects.answers">,
    { answers: inferAnswers<ObjectId>; ... }
  >({ objectId });
}

With typing the useQuery with some custom types, I am able to achieve the inference, based on the input.

@github-actions github-actions bot locked as resolved and limited conversation to collaborators Dec 22, 2022
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
Projects
None yet
Development

No branches or pull requests

2 participants