T
tRPC

Is it possible to narrow an output schema if the query optionally doesn't return all fields?

Is it possible to narrow an output schema if the query optionally doesn't return all fields?

Ppatrick3/6/2023
I have a router procedure that has an input schema that has an optional filter that changes the shape of the data to only include those fields (like sql select), but those records will fail the output schema which contains all of the fields as required. is there a way to construct a .output() specification that narrows the type, possibly using z.partial() so that these partial "rows" will pass output validation?
Nnlucas3/6/2023
union or discriminatedUnion probably
Ppatrick3/6/2023
export function makeRouter<Schema extends z.ZodSchema>(
tableName: string,
schema: Schema
) {
const client = getTableClient(tableName);
return router({
list: procedure
.input(
z
.object({
select: z.string().array().optional()
})
.optional()
)
// how do i set the output schema to .pick only the fields that are passed into query?
.output(schema.array() as z.ZodArray<Schema>)
.query(async ({ input: options }) => {
// if options.select is set here, the result will not have all of the required fields in schema
return await queryRows(client, options);
})
});
}
export function makeRouter<Schema extends z.ZodSchema>(
tableName: string,
schema: Schema
) {
const client = getTableClient(tableName);
return router({
list: procedure
.input(
z
.object({
select: z.string().array().optional()
})
.optional()
)
// how do i set the output schema to .pick only the fields that are passed into query?
.output(schema.array() as z.ZodArray<Schema>)
.query(async ({ input: options }) => {
// if options.select is set here, the result will not have all of the required fields in schema
return await queryRows(client, options);
})
});
}
this is querying Azure Table Storage. options.select narrows the columns returned to the list specified. the use case is someone wants to display a <Select> of just the id and name fields from an entity that may have many more columns in the zod schema. e.g. const { data } = api.entity.list.useQuery({ select: ["id", "name"] }); are the input fields available to the .output() step?
Nnlucas3/6/2023
RIGHT sorry I did misunderstand You might be best to leave output blank and do this in your query itself Output is just a way to define a static validator, but you could build a Zod validator at runtime based on the input and validate the output if needed, or just set up some plain TS types which are built from the input
Ppatrick3/6/2023
oh! that sounds promising... so is there a zod method to construct a narrowed type?
Nnlucas3/6/2023
For hints, take a look at how Zod handles Pick and Omit, it has some TS under the hood which does this, but also yes it has those methods
Ppatrick3/6/2023
so as you can see above, i've declared my schema as a generic that extends z.ZodSchema... but it doesn't have .pick().. have i chosen the wrong type?
Nnlucas3/6/2023
You’re close But you don’t need to be casting your Zod type Just extend from ZodType and let the inference do the rest Or ZodObject possibly in your case
Ppatrick3/6/2023
function makeRouter<Schema extends z.AnyZodObject>( tableName: string, schema: Schema, ... like this?
Nnlucas3/6/2023
That should give you access to the methods you want yes
Ppatrick3/6/2023
then just split on if there are selected fields and call parse separately? .query(async ({ input: options }) => { const result = await queryRows(client, options); if (options?.select) { const picker = options.select.reduce<Record<string, true>>((a, f) => { a[f] = true; return a; }, {}); const resultSchema = schema.pick(picker).array(); resultSchema.parse(result); } else { schema.array().parse(result); } return result; }),
Nnlucas3/6/2023
Looks like you’re on the right track yep 👍 How typescript infers this might be something to refine a bit But you’ll figure out fast if it’s an issue! 😆
Ppatrick3/6/2023
thanks for the tips! ugh. some of my schema inputs are discriminated unions and ZodDiscriminatedUnion is not assignable to parameter of type 'AnyZodObject'.
Nnlucas3/6/2023
You are probably doing something inadvisable Might have to simplify you use case, or pass a Zod factory method instead so you can pull up the pick/omit to where the unions get created But this does all smell a bit like you’re trying to rewrite GraphQL and Zod isn’t really a great choice for this
Ppatrick3/6/2023
not coming from GraphQL... just trying to store discriminated unions in the same azure table... this is where i ended up with the discriminated union.
import { z } from "zod";

const baseTableEntitySchema = z.object({
id: z.string().uuid().default("00000000-0000-0000-0000-000000000000").describe("the generated uuid rowKey.")
});

const baseCollectorSchema = z
.object({
name: z.string().min(1).describe("display name of collector")
})
.merge(baseTableEntitySchema);

const kustoParametersSchema = z.object({
query: z.string().min(1).describe("the kql query")
});
const kSchema = z
.object({
type: z.literal("kusto"),
parameters: kustoParametersSchema
})
.merge(baseCollectorSchema);

const metricsParametersSchema = z.object({
namespace: z.string().min(1).describe("the Azure metrics namespace")
});
const mSchema = z
.object({
type: z.literal("metrics"),
parameters: metricsParametersSchema
})
.merge(baseCollectorSchema);

const collectorSchema = z.discriminatedUnion("type", [mSchema, kSchema]);
import { z } from "zod";

const baseTableEntitySchema = z.object({
id: z.string().uuid().default("00000000-0000-0000-0000-000000000000").describe("the generated uuid rowKey.")
});

const baseCollectorSchema = z
.object({
name: z.string().min(1).describe("display name of collector")
})
.merge(baseTableEntitySchema);

const kustoParametersSchema = z.object({
query: z.string().min(1).describe("the kql query")
});
const kSchema = z
.object({
type: z.literal("kusto"),
parameters: kustoParametersSchema
})
.merge(baseCollectorSchema);

const metricsParametersSchema = z.object({
namespace: z.string().min(1).describe("the Azure metrics namespace")
});
const mSchema = z
.object({
type: z.literal("metrics"),
parameters: metricsParametersSchema
})
.merge(baseCollectorSchema);

const collectorSchema = z.discriminatedUnion("type", [mSchema, kSchema]);
it's this last schema that i can't call .pick on... because it's no longer a schema? it it because of this? https://github.com/colinhacks/zod/issues/1768
Nnlucas3/6/2023
I’m sure what you want to do can be done, you’ll just have to figure out some patterns which let you achieve it, probably a lot of inverted control plugged into the router factory I’ve done some similar stuff at work and it’s a pain to achieve, not really so easy to help with in a text chat

Looking for more? Join the community!

T
tRPC

Is it possible to narrow an output schema if the query optionally doesn't return all fields?

Join Server
Recommended Posts
Fetching different server url than defined in configIs it possible to access the reactQuery instance and fetch different server url? I would like to useinput using z.or not working properlyi have an input like this let input = z.object({ name: z.string().optional() }).or(z.object({ How can I disable batching with fastify adapter?I cant seem to find a way to disable batching for my server, and this link doesnt help me much httpsIssue with monorepo architecture ant tRPCHi, we had an issue with batched requests that if we batch some requests they produce a TRPCClientErUsing tRPC in CRON jobsHey everyone, this might be a very stupid question, but is it possible to use tRPC inside a CRON joasync createContext for Express AdapterBeen debugging an odd behavior for the past hour, it seems like that an async function does not workIs it possible to split the router definition with the imlementation?I want to define the server router(input\output\meta) in a separate package from the server package Cache not working for `useQuery`I have a query like this: ```js const { data: article, isFetching } = api.public.getArticle.useQueryZod File Upload Validation with Open-Api Support?Hi guys, anyone know how to validate file upload with zod and get also open-api support?Zod validation and open-api support for File on the server?Hi guys, anyone know how to validate a File upload using zod? and also have open-api support?is context cached?If I put an object on the context that represents the User record from my database... ``` export asyJSON inferred router output not matchingHello. I have a procedure query which is returning Json from a postgresql db using Prisma. The type Best way to implement input based validation on a router procedureHi guys, bit of a noob. I have already created a 'protectedProcedure', ensuring the user is logged [Help] Turbo shared typesI have a turborepo with two apps (web and embed). web is a t3 stack and embed is a create-react-app.Cache SSG helper responseI'm using `createProxySSGHelpers` in GSSP with `ssr: false` in the global config. I trying to cacheInput is too big for a single dispatchI decided to try tRPC for my Crypto analytics dashboard. However, I'm having a hard time passing thetypesafe permissionsHi, So I wanted to infer all the procedures from my router recursively & assign a permission (stringawaiting for procedure & logging the response.Hi, I was wondering if there is a way to handle the return object via the post-middleware's? I know createCaller Dependency Injection in Middleware ctx ?`createCaller` makes it really easy to inject dependencies via anything that's created during the `cbest practices for organizing routes/procedures?i'm trying to find some practices/styles in which people generally define routes with trpc. currentl