-
Notifications
You must be signed in to change notification settings - Fork 10
/
zod-trpc-utils.ts
117 lines (111 loc) · 3.8 KB
/
zod-trpc-utils.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
/* eslint-disable @typescript-eslint/no-unsafe-return */
import * as R from 'remeda'
import type {
CreateProcedureWithInputOutputParser,
inferProcedureFromOptions,
} from '@trpc/server/dist/declarations/src/internals/procedure'
import type {
ProcedureRecord,
Router,
} from '@trpc/server/dist/declarations/src/router'
import type {TRPCErrorShape} from '@trpc/server/dist/declarations/src/rpc'
import type {Subscription} from '@trpc/server/dist/declarations/src/subscription'
import {z} from 'zod'
import type {PickRequiredKeys} from './type-utils'
import type {AnyZFunction, ZFunction} from './zod-function-utils'
import {isZFunction} from './zod-function-utils'
/* eslint-disable @typescript-eslint/no-explicit-any */
type _AnyZFunction = ZFunction<any, any>
export type ZFunctionMap = Record<string, _AnyZFunction | unknown>
type ProcedureRecordFromZMap<
TInputContext,
TContext,
TMeta extends Record<string, any>,
TMap extends ZFunctionMap,
> = PickRequiredKeys<{
[k in keyof TMap]: TMap[k] extends _AnyZFunction
? inferProcedureFromOptions<
TInputContext,
CreateProcedureWithInputOutputParser<
TContext,
TMeta,
TMap[k]['parameters']['_input'],
TMap[k]['parameters']['_output'],
TMap[k]['returnType']['_input'],
TMap[k]['returnType']['_output']
>
>
: undefined
}>
type AnyProcedureRecord<TInputContext, TContext, TParsedInput> =
ProcedureRecord<TInputContext, TContext, any, any, TParsedInput>
export function routerFromZFunctionMap<
TInputContext,
TContext,
TMeta extends Record<string, any>,
TQueries extends AnyProcedureRecord<TInputContext, TContext, any>,
TMutations extends AnyProcedureRecord<TInputContext, TContext, any>,
TSubscriptions extends AnyProcedureRecord<
TInputContext,
TContext,
Subscription
>,
TErrorShape extends TRPCErrorShape<number>,
TMap extends ZFunctionMap,
>(
inputRouter: Router<
TInputContext,
TContext,
TMeta,
TQueries,
TMutations,
TSubscriptions,
TErrorShape
>,
functionMap: TMap,
): Router<
TInputContext,
TContext,
TMeta,
TQueries & ProcedureRecordFromZMap<TInputContext, TContext, TMeta, TMap>,
TMutations & ProcedureRecordFromZMap<TInputContext, TContext, TMeta, TMap>,
TSubscriptions,
TErrorShape
> {
return R.pipe(
functionMap,
R.toPairs,
R.filter((pair): pair is [string, AnyZFunction] => isZFunction(pair[1])),
R.flatMap(([name, fn]) =>
(['query', 'mutation'] as const).map((m) => [m, name, fn] as const),
),
R.reduce(
(router, [method, name, fn]) =>
// query and mutation have compatible inputs but typechecker doesn't know it https://share.cleanshot.com/if9Avt
// Mutation is generally the right type, but query is easier to use https://github.com/trpc/trpc/discussions/1638
// So we will support both by default
router[method as 'query'](name, {
// Better to handle pre-processing here given that router input can come from anywhere
// Whether cli or http request and it is hard to control. However fn parameters
// are always going to be arrays, so let's pre-process
// Need to think about whether it makes sense to keep the logic inside cliFromRouter
input: preprocessArgsTuple(fn.parameters),
output: fn.returnType,
resolve: ({input}) => fn.impl(...input),
}),
inputRouter,
),
) as any
}
export function preprocessArgsTuple<
T extends z.ZodTuple<[z.ZodTypeAny, ...z.ZodTypeAny[]] | []>,
>(schema: T) {
return z.preprocess((i) => {
const ret = R.pipe(Array.isArray(i) ? i : [i], (arr) => [
...arr.slice(0, schema.items.length),
...new Array(Math.max(schema.items.length - arr.length, 0)),
])
// console.log('Preprocessed', ret)
return ret
}, schema)
}