zod icon indicating copy to clipboard operation
zod copied to clipboard

[Feature request] Marking a field as deprecated

Open chrskerr opened this issue 2 years ago • 2 comments

Use case

I am creating an API using Zod and TRPC, and I can't guarantee that all of the clients will upgrade immediately upon deploy (e.g. long opened tabs).

I would like to deprecate fields out of the TRCP input or output schema without forcing an immediate client refresh; and I didn't spot an approach to doing this in the docs.

If I have missed any easier approach, please let me know :)

Proposal

A deprecated method which removes the key from the input, or output, types based on the setting (naming just a first-thought, not a suggestion).

  • input:
    • optional in validation
    • is not in the input type
    • is in the output type
  • output:
    • same as before in validation (optional or required)
    • is in the input type
    • is not in the output type

Why not just temporarily use optional

  • For input types, it will not prevent consumers from continuing to attempt to supply this data. A two step deprecation allows all consumer code to be updated (and type checked) before updating the server code.
  • For output types, it ensures that no consumers are still expecting this field before the server stops sending it entirely

In both situations, deprecating for a situation-appropriate amount of time helps to avoid API mismatch.

Examples

export const appRouter = t.router({
  hello: publicProcedure
    .input(
      z.object({
        // consumers of this API route will not see `name` in the input types
        // but it is still available here to be responded to for old consumers
        name: z.string().deprecated('input'),
      }),
    )
    .query((opts) => {
      const name = opts.input.name;
      if (name) { /* do the old version */ }
      else { /* do the new version */ } 
      return {
        greeting: "Hello",
      };
    }),
});
export const appRouter = t.router({
  // this method is being deprecated, so new consumers will see `{}` as the output type
  // it will still work for older consumers, allowing time for all clients to update to the latest build
  // this specific situation may be better managed at a TRPC level as well
  hello: publicProcedure
    .input(
      z.object({
        name: z.string().deprecated('input'),
      }),
    )
    .output(
      z.object({
        greeting: z.string().deprecated('output'),
      }),
    )
    .query((opts) => {
      const name = opts.input.name;
      return {
        greeting: "Hello",
      };
    }),
});

chrskerr avatar Aug 01 '23 01:08 chrskerr

How's this comming along?

okikio avatar Mar 23 '24 07:03 okikio

I'd settle for even just a type annotation as a starting point. Would you accept a PR?

jthrilly avatar Aug 21 '24 18:08 jthrilly