zodvex 0.2.0 → 0.2.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +366 -380
- package/dist/index.d.mts +218 -82
- package/dist/index.d.ts +218 -82
- package/dist/index.js +125 -205
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +111 -192
- package/dist/index.mjs.map +1 -1
- package/package.json +4 -4
- package/src/builders.ts +310 -0
- package/src/custom.ts +4 -84
- package/src/index.ts +4 -1
- package/src/mapping/core.ts +16 -0
- package/src/tables.ts +34 -32
- package/src/utils.ts +9 -6
- package/src/wrappers.ts +60 -52
package/src/builders.ts
ADDED
|
@@ -0,0 +1,310 @@
|
|
|
1
|
+
import type {
|
|
2
|
+
FunctionVisibility,
|
|
3
|
+
RegisteredAction,
|
|
4
|
+
RegisteredMutation,
|
|
5
|
+
RegisteredQuery
|
|
6
|
+
} from 'convex/server'
|
|
7
|
+
import type { PropertyValidators } from 'convex/values'
|
|
8
|
+
import type { Customization } from 'convex-helpers/server/customFunctions'
|
|
9
|
+
import { z } from 'zod'
|
|
10
|
+
import { type CustomBuilder, customFnBuilder } from './custom'
|
|
11
|
+
import type {
|
|
12
|
+
ExtractCtx,
|
|
13
|
+
ExtractVisibility,
|
|
14
|
+
InferHandlerReturns,
|
|
15
|
+
InferReturns,
|
|
16
|
+
ZodToConvexArgs
|
|
17
|
+
} from './types'
|
|
18
|
+
import { zAction, zMutation, zQuery } from './wrappers'
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* Creates a reusable query builder from a Convex query builder.
|
|
22
|
+
* Returns a builder function that accepts Convex-style config objects with args, handler, and returns.
|
|
23
|
+
*
|
|
24
|
+
* @example
|
|
25
|
+
* ```ts
|
|
26
|
+
* import { query } from './_generated/server'
|
|
27
|
+
* import { zQueryBuilder } from 'zodvex'
|
|
28
|
+
*
|
|
29
|
+
* // Create a reusable builder
|
|
30
|
+
* export const zq = zQueryBuilder(query)
|
|
31
|
+
*
|
|
32
|
+
* // Use it with Convex-style object syntax
|
|
33
|
+
* export const getUser = zq({
|
|
34
|
+
* args: { id: z.string() },
|
|
35
|
+
* handler: async (ctx, { id }) => {
|
|
36
|
+
* return ctx.db.get(id)
|
|
37
|
+
* }
|
|
38
|
+
* })
|
|
39
|
+
* ```
|
|
40
|
+
*/
|
|
41
|
+
export function zQueryBuilder<Builder extends (fn: any) => any>(builder: Builder) {
|
|
42
|
+
return <
|
|
43
|
+
A extends z.ZodTypeAny | Record<string, z.ZodTypeAny>,
|
|
44
|
+
R extends z.ZodTypeAny | undefined = undefined,
|
|
45
|
+
Visibility extends FunctionVisibility = ExtractVisibility<Builder>
|
|
46
|
+
>(config: {
|
|
47
|
+
args?: A
|
|
48
|
+
handler: (
|
|
49
|
+
ctx: ExtractCtx<Builder>,
|
|
50
|
+
args: ZodToConvexArgs<A extends undefined ? Record<string, never> : A>
|
|
51
|
+
) => InferHandlerReturns<R> | Promise<InferHandlerReturns<R>>
|
|
52
|
+
returns?: R
|
|
53
|
+
}): RegisteredQuery<
|
|
54
|
+
Visibility,
|
|
55
|
+
ZodToConvexArgs<A extends undefined ? Record<string, never> : A>,
|
|
56
|
+
Promise<InferReturns<R>>
|
|
57
|
+
> => {
|
|
58
|
+
return zQuery(builder, config.args ?? ({} as any), config.handler, {
|
|
59
|
+
returns: config.returns
|
|
60
|
+
}) as any
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* Creates a reusable mutation builder from a Convex mutation builder.
|
|
66
|
+
* Returns a builder function that accepts Convex-style config objects with args, handler, and returns.
|
|
67
|
+
*
|
|
68
|
+
* @example
|
|
69
|
+
* ```ts
|
|
70
|
+
* import { mutation } from './_generated/server'
|
|
71
|
+
* import { zMutationBuilder } from 'zodvex'
|
|
72
|
+
*
|
|
73
|
+
* // Create a reusable builder
|
|
74
|
+
* export const zm = zMutationBuilder(mutation)
|
|
75
|
+
*
|
|
76
|
+
* // Use it with Convex-style object syntax
|
|
77
|
+
* export const updateUser = zm({
|
|
78
|
+
* args: { id: z.string(), name: z.string() },
|
|
79
|
+
* handler: async (ctx, { id, name }) => {
|
|
80
|
+
* return ctx.db.patch(id, { name })
|
|
81
|
+
* }
|
|
82
|
+
* })
|
|
83
|
+
* ```
|
|
84
|
+
*/
|
|
85
|
+
export function zMutationBuilder<Builder extends (fn: any) => any>(builder: Builder) {
|
|
86
|
+
return <
|
|
87
|
+
A extends z.ZodTypeAny | Record<string, z.ZodTypeAny>,
|
|
88
|
+
R extends z.ZodTypeAny | undefined = undefined,
|
|
89
|
+
Visibility extends FunctionVisibility = ExtractVisibility<Builder>
|
|
90
|
+
>(config: {
|
|
91
|
+
args?: A
|
|
92
|
+
handler: (
|
|
93
|
+
ctx: ExtractCtx<Builder>,
|
|
94
|
+
args: ZodToConvexArgs<A extends undefined ? Record<string, never> : A>
|
|
95
|
+
) => InferHandlerReturns<R> | Promise<InferHandlerReturns<R>>
|
|
96
|
+
returns?: R
|
|
97
|
+
}): RegisteredMutation<
|
|
98
|
+
Visibility,
|
|
99
|
+
ZodToConvexArgs<A extends undefined ? Record<string, never> : A>,
|
|
100
|
+
Promise<InferReturns<R>>
|
|
101
|
+
> => {
|
|
102
|
+
return zMutation(builder, config.args ?? ({} as any), config.handler, {
|
|
103
|
+
returns: config.returns
|
|
104
|
+
}) as any
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
/**
|
|
109
|
+
* Creates a reusable action builder from a Convex action builder.
|
|
110
|
+
* Returns a builder function that accepts Convex-style config objects with args, handler, and returns.
|
|
111
|
+
*
|
|
112
|
+
* @example
|
|
113
|
+
* ```ts
|
|
114
|
+
* import { action } from './_generated/server'
|
|
115
|
+
* import { zActionBuilder } from 'zodvex'
|
|
116
|
+
*
|
|
117
|
+
* // Create a reusable builder
|
|
118
|
+
* export const za = zActionBuilder(action)
|
|
119
|
+
*
|
|
120
|
+
* // Use it with Convex-style object syntax
|
|
121
|
+
* export const sendEmail = za({
|
|
122
|
+
* args: { to: z.string().email(), subject: z.string() },
|
|
123
|
+
* handler: async (ctx, { to, subject }) => {
|
|
124
|
+
* // Send email
|
|
125
|
+
* }
|
|
126
|
+
* })
|
|
127
|
+
* ```
|
|
128
|
+
*/
|
|
129
|
+
export function zActionBuilder<Builder extends (fn: any) => any>(builder: Builder) {
|
|
130
|
+
return <
|
|
131
|
+
A extends z.ZodTypeAny | Record<string, z.ZodTypeAny>,
|
|
132
|
+
R extends z.ZodTypeAny | undefined = undefined,
|
|
133
|
+
Visibility extends FunctionVisibility = ExtractVisibility<Builder>
|
|
134
|
+
>(config: {
|
|
135
|
+
args?: A
|
|
136
|
+
handler: (
|
|
137
|
+
ctx: ExtractCtx<Builder>,
|
|
138
|
+
args: ZodToConvexArgs<A extends undefined ? Record<string, never> : A>
|
|
139
|
+
) => InferHandlerReturns<R> | Promise<InferHandlerReturns<R>>
|
|
140
|
+
returns?: R
|
|
141
|
+
}): RegisteredAction<
|
|
142
|
+
Visibility,
|
|
143
|
+
ZodToConvexArgs<A extends undefined ? Record<string, never> : A>,
|
|
144
|
+
Promise<InferReturns<R>>
|
|
145
|
+
> => {
|
|
146
|
+
return zAction(builder, config.args ?? ({} as any), config.handler, {
|
|
147
|
+
returns: config.returns
|
|
148
|
+
}) as any
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
/**
|
|
153
|
+
* Creates a custom query builder with context injection from a Convex query builder.
|
|
154
|
+
* Allows you to add custom context (like auth, permissions, etc.) to your queries.
|
|
155
|
+
*
|
|
156
|
+
* @example
|
|
157
|
+
* ```ts
|
|
158
|
+
* import { query } from './_generated/server'
|
|
159
|
+
* import { zCustomQueryBuilder, customCtx } from 'zodvex'
|
|
160
|
+
*
|
|
161
|
+
* // Create a builder with auth context
|
|
162
|
+
* export const authQuery = zCustomQueryBuilder(
|
|
163
|
+
* query,
|
|
164
|
+
* customCtx(async (ctx) => {
|
|
165
|
+
* const user = await getUserOrThrow(ctx)
|
|
166
|
+
* return { user }
|
|
167
|
+
* })
|
|
168
|
+
* )
|
|
169
|
+
*
|
|
170
|
+
* // Use it with automatic user injection
|
|
171
|
+
* export const getMyProfile = authQuery({
|
|
172
|
+
* args: {},
|
|
173
|
+
* handler: async (ctx) => {
|
|
174
|
+
* // ctx.user is automatically available
|
|
175
|
+
* return ctx.db.get(ctx.user._id)
|
|
176
|
+
* }
|
|
177
|
+
* })
|
|
178
|
+
* ```
|
|
179
|
+
*/
|
|
180
|
+
export function zCustomQueryBuilder<
|
|
181
|
+
Builder extends (fn: any) => any,
|
|
182
|
+
CustomArgsValidator extends PropertyValidators,
|
|
183
|
+
CustomCtx extends Record<string, any>,
|
|
184
|
+
CustomMadeArgs extends Record<string, any>,
|
|
185
|
+
Visibility extends FunctionVisibility = ExtractVisibility<Builder>,
|
|
186
|
+
ExtraArgs extends Record<string, any> = Record<string, any>
|
|
187
|
+
>(
|
|
188
|
+
query: Builder,
|
|
189
|
+
customization: Customization<any, CustomArgsValidator, CustomCtx, CustomMadeArgs, ExtraArgs>
|
|
190
|
+
): CustomBuilder<
|
|
191
|
+
'query',
|
|
192
|
+
CustomArgsValidator,
|
|
193
|
+
CustomCtx,
|
|
194
|
+
CustomMadeArgs,
|
|
195
|
+
ExtractCtx<Builder>,
|
|
196
|
+
Visibility,
|
|
197
|
+
ExtraArgs
|
|
198
|
+
> {
|
|
199
|
+
return customFnBuilder<any, Builder, CustomArgsValidator, CustomCtx, CustomMadeArgs, ExtraArgs>(
|
|
200
|
+
query as any,
|
|
201
|
+
customization as any
|
|
202
|
+
) as any
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
/**
|
|
206
|
+
* Creates a custom mutation builder with context injection from a Convex mutation builder.
|
|
207
|
+
* Allows you to add custom context (like auth, permissions, etc.) to your mutations.
|
|
208
|
+
*
|
|
209
|
+
* @example
|
|
210
|
+
* ```ts
|
|
211
|
+
* import { mutation } from './_generated/server'
|
|
212
|
+
* import { zCustomMutationBuilder, customCtx } from 'zodvex'
|
|
213
|
+
*
|
|
214
|
+
* // Create a builder with auth context
|
|
215
|
+
* export const authMutation = zCustomMutationBuilder(
|
|
216
|
+
* mutation,
|
|
217
|
+
* customCtx(async (ctx) => {
|
|
218
|
+
* const user = await getUserOrThrow(ctx)
|
|
219
|
+
* return { user }
|
|
220
|
+
* })
|
|
221
|
+
* )
|
|
222
|
+
*
|
|
223
|
+
* // Use it with automatic user injection
|
|
224
|
+
* export const updateProfile = authMutation({
|
|
225
|
+
* args: { name: z.string() },
|
|
226
|
+
* handler: async (ctx, { name }) => {
|
|
227
|
+
* // ctx.user is automatically available
|
|
228
|
+
* await ctx.db.patch(ctx.user._id, { name })
|
|
229
|
+
* }
|
|
230
|
+
* })
|
|
231
|
+
* ```
|
|
232
|
+
*/
|
|
233
|
+
export function zCustomMutationBuilder<
|
|
234
|
+
Builder extends (fn: any) => any,
|
|
235
|
+
CustomArgsValidator extends PropertyValidators,
|
|
236
|
+
CustomCtx extends Record<string, any>,
|
|
237
|
+
CustomMadeArgs extends Record<string, any>,
|
|
238
|
+
Visibility extends FunctionVisibility = ExtractVisibility<Builder>,
|
|
239
|
+
ExtraArgs extends Record<string, any> = Record<string, any>
|
|
240
|
+
>(
|
|
241
|
+
mutation: Builder,
|
|
242
|
+
customization: Customization<any, CustomArgsValidator, CustomCtx, CustomMadeArgs, ExtraArgs>
|
|
243
|
+
): CustomBuilder<
|
|
244
|
+
'mutation',
|
|
245
|
+
CustomArgsValidator,
|
|
246
|
+
CustomCtx,
|
|
247
|
+
CustomMadeArgs,
|
|
248
|
+
ExtractCtx<Builder>,
|
|
249
|
+
Visibility,
|
|
250
|
+
ExtraArgs
|
|
251
|
+
> {
|
|
252
|
+
return customFnBuilder<any, Builder, CustomArgsValidator, CustomCtx, CustomMadeArgs, ExtraArgs>(
|
|
253
|
+
mutation as any,
|
|
254
|
+
customization as any
|
|
255
|
+
) as any
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
/**
|
|
259
|
+
* Creates a custom action builder with context injection from a Convex action builder.
|
|
260
|
+
* Allows you to add custom context (like auth, permissions, etc.) to your actions.
|
|
261
|
+
*
|
|
262
|
+
* @example
|
|
263
|
+
* ```ts
|
|
264
|
+
* import { action } from './_generated/server'
|
|
265
|
+
* import { zCustomActionBuilder, customCtx } from 'zodvex'
|
|
266
|
+
*
|
|
267
|
+
* // Create a builder with auth context
|
|
268
|
+
* export const authAction = zCustomActionBuilder(
|
|
269
|
+
* action,
|
|
270
|
+
* customCtx(async (ctx) => {
|
|
271
|
+
* const identity = await ctx.auth.getUserIdentity()
|
|
272
|
+
* if (!identity) throw new Error('Unauthorized')
|
|
273
|
+
* return { userId: identity.subject }
|
|
274
|
+
* })
|
|
275
|
+
* )
|
|
276
|
+
*
|
|
277
|
+
* // Use it with automatic auth injection
|
|
278
|
+
* export const sendEmail = authAction({
|
|
279
|
+
* args: { to: z.string().email() },
|
|
280
|
+
* handler: async (ctx, { to }) => {
|
|
281
|
+
* // ctx.userId is automatically available
|
|
282
|
+
* await sendEmailService(to, ctx.userId)
|
|
283
|
+
* }
|
|
284
|
+
* })
|
|
285
|
+
* ```
|
|
286
|
+
*/
|
|
287
|
+
export function zCustomActionBuilder<
|
|
288
|
+
Builder extends (fn: any) => any,
|
|
289
|
+
CustomArgsValidator extends PropertyValidators,
|
|
290
|
+
CustomCtx extends Record<string, any>,
|
|
291
|
+
CustomMadeArgs extends Record<string, any>,
|
|
292
|
+
Visibility extends FunctionVisibility = ExtractVisibility<Builder>,
|
|
293
|
+
ExtraArgs extends Record<string, any> = Record<string, any>
|
|
294
|
+
>(
|
|
295
|
+
action: Builder,
|
|
296
|
+
customization: Customization<any, CustomArgsValidator, CustomCtx, CustomMadeArgs, ExtraArgs>
|
|
297
|
+
): CustomBuilder<
|
|
298
|
+
'action',
|
|
299
|
+
CustomArgsValidator,
|
|
300
|
+
CustomCtx,
|
|
301
|
+
CustomMadeArgs,
|
|
302
|
+
ExtractCtx<Builder>,
|
|
303
|
+
Visibility,
|
|
304
|
+
ExtraArgs
|
|
305
|
+
> {
|
|
306
|
+
return customFnBuilder<any, Builder, CustomArgsValidator, CustomCtx, CustomMadeArgs, ExtraArgs>(
|
|
307
|
+
action as any,
|
|
308
|
+
customization as any
|
|
309
|
+
) as any
|
|
310
|
+
}
|
package/src/custom.ts
CHANGED
|
@@ -162,7 +162,7 @@ export type CustomBuilder<
|
|
|
162
162
|
>
|
|
163
163
|
}
|
|
164
164
|
|
|
165
|
-
function customFnBuilder<
|
|
165
|
+
export function customFnBuilder<
|
|
166
166
|
Ctx extends Record<string, any>,
|
|
167
167
|
Builder extends (fn: any) => any,
|
|
168
168
|
CustomArgsValidator extends PropertyValidators,
|
|
@@ -220,10 +220,9 @@ function customFnBuilder<
|
|
|
220
220
|
handleZodValidationError(parsed.error, 'args')
|
|
221
221
|
}
|
|
222
222
|
const finalCtx = { ...ctx, ...(added?.ctx ?? {}) }
|
|
223
|
-
const
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
}
|
|
223
|
+
const baseArgs = parsed.data as Record<string, unknown>
|
|
224
|
+
const addedArgs = (added?.args as Record<string, unknown>) ?? {}
|
|
225
|
+
const finalArgs = { ...baseArgs, ...addedArgs }
|
|
227
226
|
const ret = await handler(finalCtx, finalArgs)
|
|
228
227
|
if (returns && !fn.skipConvexValidation) {
|
|
229
228
|
let validated: any
|
|
@@ -343,85 +342,6 @@ export function zCustomQuery<
|
|
|
343
342
|
>(query as any, customization as any) as any
|
|
344
343
|
}
|
|
345
344
|
|
|
346
|
-
// Strict variants: infer ctx directly from builder for precise db typing
|
|
347
|
-
export function zStrictQuery<
|
|
348
|
-
Builder extends (fn: any) => any,
|
|
349
|
-
CustomArgsValidator extends PropertyValidators,
|
|
350
|
-
CustomCtx extends Record<string, any>,
|
|
351
|
-
CustomMadeArgs extends Record<string, any>,
|
|
352
|
-
Visibility extends FunctionVisibility = ExtractVisibility<Builder>,
|
|
353
|
-
ExtraArgs extends Record<string, any> = Record<string, any>
|
|
354
|
-
>(
|
|
355
|
-
query: Builder,
|
|
356
|
-
// Note: Customization from convex-helpers requires Ctx extends Record<string, any>.
|
|
357
|
-
// We accept `any` here to avoid overly constraining Convex Ctx types while
|
|
358
|
-
// still threading the precise ctx type to the resulting builder.
|
|
359
|
-
customization: Customization<any, CustomArgsValidator, CustomCtx, CustomMadeArgs, ExtraArgs>
|
|
360
|
-
): CustomBuilder<
|
|
361
|
-
'query',
|
|
362
|
-
CustomArgsValidator,
|
|
363
|
-
CustomCtx,
|
|
364
|
-
CustomMadeArgs,
|
|
365
|
-
ExtractCtx<Builder>,
|
|
366
|
-
Visibility,
|
|
367
|
-
ExtraArgs
|
|
368
|
-
> {
|
|
369
|
-
return customFnBuilder<any, Builder, CustomArgsValidator, CustomCtx, CustomMadeArgs, ExtraArgs>(
|
|
370
|
-
query as any,
|
|
371
|
-
customization as any
|
|
372
|
-
) as any
|
|
373
|
-
}
|
|
374
|
-
|
|
375
|
-
export function zStrictMutation<
|
|
376
|
-
Builder extends (fn: any) => any,
|
|
377
|
-
CustomArgsValidator extends PropertyValidators,
|
|
378
|
-
CustomCtx extends Record<string, any>,
|
|
379
|
-
CustomMadeArgs extends Record<string, any>,
|
|
380
|
-
Visibility extends FunctionVisibility = ExtractVisibility<Builder>,
|
|
381
|
-
ExtraArgs extends Record<string, any> = Record<string, any>
|
|
382
|
-
>(
|
|
383
|
-
mutation: Builder,
|
|
384
|
-
customization: Customization<any, CustomArgsValidator, CustomCtx, CustomMadeArgs, ExtraArgs>
|
|
385
|
-
): CustomBuilder<
|
|
386
|
-
'mutation',
|
|
387
|
-
CustomArgsValidator,
|
|
388
|
-
CustomCtx,
|
|
389
|
-
CustomMadeArgs,
|
|
390
|
-
ExtractCtx<Builder>,
|
|
391
|
-
Visibility,
|
|
392
|
-
ExtraArgs
|
|
393
|
-
> {
|
|
394
|
-
return customFnBuilder<any, Builder, CustomArgsValidator, CustomCtx, CustomMadeArgs, ExtraArgs>(
|
|
395
|
-
mutation as any,
|
|
396
|
-
customization as any
|
|
397
|
-
) as any
|
|
398
|
-
}
|
|
399
|
-
|
|
400
|
-
export function zStrictAction<
|
|
401
|
-
Builder extends (fn: any) => any,
|
|
402
|
-
CustomArgsValidator extends PropertyValidators,
|
|
403
|
-
CustomCtx extends Record<string, any>,
|
|
404
|
-
CustomMadeArgs extends Record<string, any>,
|
|
405
|
-
Visibility extends FunctionVisibility = ExtractVisibility<Builder>,
|
|
406
|
-
ExtraArgs extends Record<string, any> = Record<string, any>
|
|
407
|
-
>(
|
|
408
|
-
action: Builder,
|
|
409
|
-
customization: Customization<any, CustomArgsValidator, CustomCtx, CustomMadeArgs, ExtraArgs>
|
|
410
|
-
): CustomBuilder<
|
|
411
|
-
'action',
|
|
412
|
-
CustomArgsValidator,
|
|
413
|
-
CustomCtx,
|
|
414
|
-
CustomMadeArgs,
|
|
415
|
-
ExtractCtx<Builder>,
|
|
416
|
-
Visibility,
|
|
417
|
-
ExtraArgs
|
|
418
|
-
> {
|
|
419
|
-
return customFnBuilder<any, Builder, CustomArgsValidator, CustomCtx, CustomMadeArgs, ExtraArgs>(
|
|
420
|
-
action as any,
|
|
421
|
-
customization as any
|
|
422
|
-
) as any
|
|
423
|
-
}
|
|
424
|
-
|
|
425
345
|
// Overload 1: With constraint - preferred to preserve DataModel types
|
|
426
346
|
export function zCustomMutation<
|
|
427
347
|
CustomArgsValidator extends PropertyValidators,
|
package/src/index.ts
CHANGED
|
@@ -1,3 +1,6 @@
|
|
|
1
|
+
// Re-export customCtx from convex-helpers for convenience
|
|
2
|
+
export { customCtx } from 'convex-helpers/server/customFunctions'
|
|
3
|
+
export * from './builders'
|
|
1
4
|
export * from './codec'
|
|
2
5
|
export * from './custom'
|
|
3
6
|
export * from './ids'
|
|
@@ -6,4 +9,4 @@ export * from './registry'
|
|
|
6
9
|
export * from './tables'
|
|
7
10
|
export * from './types'
|
|
8
11
|
export * from './utils'
|
|
9
|
-
|
|
12
|
+
// wrappers are internal-only - use builders instead
|
package/src/mapping/core.ts
CHANGED
|
@@ -22,6 +22,11 @@ function zodToConvexInternal<Z extends z.ZodTypeAny>(
|
|
|
22
22
|
zodValidator: Z,
|
|
23
23
|
visited: Set<z.ZodTypeAny> = new Set()
|
|
24
24
|
): ConvexValidatorFromZod<Z, 'required'> {
|
|
25
|
+
// Guard against undefined/null validators (can happen with { field: undefined } in args)
|
|
26
|
+
if (!zodValidator) {
|
|
27
|
+
return v.any() as ConvexValidatorFromZod<Z, 'required'>
|
|
28
|
+
}
|
|
29
|
+
|
|
25
30
|
// Detect circular references to prevent infinite recursion
|
|
26
31
|
if (visited.has(zodValidator)) {
|
|
27
32
|
return v.any() as ConvexValidatorFromZod<Z, 'required'>
|
|
@@ -72,6 +77,10 @@ function zodToConvexInternal<Z extends z.ZodTypeAny>(
|
|
|
72
77
|
// 3. Future-proof: Zod's internal structure is stable; instanceof checks can miss custom types
|
|
73
78
|
// 4. Precision: def.type distinguishes between semantically different types (date vs number)
|
|
74
79
|
// This private API access is intentional and necessary for comprehensive type coverage.
|
|
80
|
+
//
|
|
81
|
+
// Compatibility: This code relies on the internal `.def.type` property of ZodType.
|
|
82
|
+
// This structure has been stable across Zod v3.x and v4.x. If upgrading Zod major versions,
|
|
83
|
+
// verify that `.def.type` is still present and unchanged.
|
|
75
84
|
const defType = (actualValidator as any).def?.type
|
|
76
85
|
|
|
77
86
|
switch (defType) {
|
|
@@ -268,6 +277,13 @@ function zodToConvexInternal<Z extends z.ZodTypeAny>(
|
|
|
268
277
|
default:
|
|
269
278
|
// For any unrecognized def.type, return v.any()
|
|
270
279
|
// No instanceof fallbacks - keep it simple and performant
|
|
280
|
+
if (process.env.NODE_ENV !== 'production') {
|
|
281
|
+
console.warn(
|
|
282
|
+
`[zodvex] Unrecognized Zod type "${defType}" encountered. Falling back to v.any().`,
|
|
283
|
+
'Schema:',
|
|
284
|
+
actualValidator
|
|
285
|
+
)
|
|
286
|
+
}
|
|
271
287
|
convexValidator = v.any()
|
|
272
288
|
break
|
|
273
289
|
}
|
package/src/tables.ts
CHANGED
|
@@ -2,8 +2,7 @@ import type { GenericId } from 'convex/values'
|
|
|
2
2
|
import { Table } from 'convex-helpers/server'
|
|
3
3
|
import { z } from 'zod'
|
|
4
4
|
import { zid } from './ids'
|
|
5
|
-
import { type ConvexValidatorFromZodFieldsAuto,
|
|
6
|
-
import { mapDateFieldToNumber } from './utils'
|
|
5
|
+
import { type ConvexValidatorFromZodFieldsAuto, zodToConvexFields } from './mapping'
|
|
7
6
|
|
|
8
7
|
// Helper to create a Zod schema for a Convex document
|
|
9
8
|
export function zodDoc<
|
|
@@ -44,7 +43,29 @@ export function zodDocOrNull<
|
|
|
44
43
|
* This architectural decision is important for projects that rely heavily on type safety and
|
|
45
44
|
* developer experience, as it avoids the type erasure that can occur when using ZodObject directly.
|
|
46
45
|
*
|
|
47
|
-
* Returns
|
|
46
|
+
* Returns the Table definition along with Zod schemas for documents and arrays.
|
|
47
|
+
*
|
|
48
|
+
* @param name - The table name
|
|
49
|
+
* @param shape - A raw object mapping field names to Zod validators
|
|
50
|
+
* @returns A Table with attached shape, zDoc schema, and docArray helper
|
|
51
|
+
*
|
|
52
|
+
* @example
|
|
53
|
+
* ```ts
|
|
54
|
+
* const Users = zodTable('users', {
|
|
55
|
+
* name: z.string(),
|
|
56
|
+
* email: z.string().email(),
|
|
57
|
+
* age: z.number().optional()
|
|
58
|
+
* })
|
|
59
|
+
*
|
|
60
|
+
* // Use in schema
|
|
61
|
+
* export default defineSchema({ users: Users.table })
|
|
62
|
+
*
|
|
63
|
+
* // Use for return types
|
|
64
|
+
* export const getUsers = zQuery(query, {},
|
|
65
|
+
* async (ctx) => ctx.db.query('users').collect(),
|
|
66
|
+
* { returns: Users.docArray }
|
|
67
|
+
* )
|
|
68
|
+
* ```
|
|
48
69
|
*/
|
|
49
70
|
export function zodTable<TableName extends string, Shape extends Record<string, z.ZodTypeAny>>(
|
|
50
71
|
name: TableName,
|
|
@@ -56,10 +77,17 @@ export function zodTable<TableName extends string, Shape extends Record<string,
|
|
|
56
77
|
// Create the Table from convex-helpers with explicit type
|
|
57
78
|
const table = Table<ConvexValidatorFromZodFieldsAuto<Shape>, TableName>(name, convexFields)
|
|
58
79
|
|
|
59
|
-
//
|
|
80
|
+
// Create zDoc schema with system fields
|
|
81
|
+
const zDoc = zodDoc(name, z.object(shape))
|
|
82
|
+
|
|
83
|
+
// Create docArray helper for return types
|
|
84
|
+
const docArray = z.array(zDoc)
|
|
85
|
+
|
|
86
|
+
// Attach everything for comprehensive usage
|
|
60
87
|
return Object.assign(table, {
|
|
61
88
|
shape,
|
|
62
|
-
zDoc
|
|
89
|
+
zDoc,
|
|
90
|
+
docArray
|
|
63
91
|
}) as typeof table & {
|
|
64
92
|
shape: Shape
|
|
65
93
|
zDoc: z.ZodObject<
|
|
@@ -68,32 +96,6 @@ export function zodTable<TableName extends string, Shape extends Record<string,
|
|
|
68
96
|
_creationTime: z.ZodNumber
|
|
69
97
|
}
|
|
70
98
|
>
|
|
99
|
+
docArray: z.ZodArray<typeof zDoc>
|
|
71
100
|
}
|
|
72
101
|
}
|
|
73
|
-
|
|
74
|
-
// Keep the old implementation available for backward compatibility
|
|
75
|
-
export function zodTableWithDocs<T extends z.ZodObject<any>, TableName extends string>(
|
|
76
|
-
name: TableName,
|
|
77
|
-
schema: T
|
|
78
|
-
) {
|
|
79
|
-
// Use zodToConvexFields with proper types - pass the shape for type preservation
|
|
80
|
-
const convexFields = zodToConvexFields(schema.shape)
|
|
81
|
-
|
|
82
|
-
// Simplified: only convert dates at top level to avoid deep recursion
|
|
83
|
-
const shape = getObjectShape(schema)
|
|
84
|
-
const mapped: Record<string, any> = {}
|
|
85
|
-
|
|
86
|
-
for (const [k, field] of Object.entries(shape)) {
|
|
87
|
-
mapped[k] = mapDateFieldToNumber(field as z.ZodTypeAny)
|
|
88
|
-
}
|
|
89
|
-
const docSchema = z.object({
|
|
90
|
-
...mapped,
|
|
91
|
-
_id: zid(name),
|
|
92
|
-
_creationTime: z.number()
|
|
93
|
-
})
|
|
94
|
-
const docArray = z.array(docSchema)
|
|
95
|
-
|
|
96
|
-
const base = Table(name, convexFields)
|
|
97
|
-
// Return with docSchema and docArray for backward compatibility
|
|
98
|
-
return { ...base, schema, docSchema, docArray }
|
|
99
|
-
}
|
package/src/utils.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { ConvexError } from 'convex/values'
|
|
2
2
|
import { z } from 'zod'
|
|
3
|
+
import { getObjectShape } from './mapping'
|
|
3
4
|
|
|
4
5
|
export function pick<T extends Record<string, any>, K extends keyof T>(
|
|
5
6
|
obj: T,
|
|
@@ -97,15 +98,15 @@ function toKeys(mask: Mask): string[] {
|
|
|
97
98
|
return Object.keys(mask).filter(k => !!(mask as any)[k])
|
|
98
99
|
}
|
|
99
100
|
|
|
100
|
-
|
|
101
|
-
|
|
101
|
+
/**
|
|
102
|
+
* Returns a plain shape object containing only the selected fields.
|
|
103
|
+
* Accepts either a ZodObject or a raw shape object.
|
|
104
|
+
*/
|
|
102
105
|
export function pickShape(
|
|
103
106
|
schemaOrShape: z.ZodObject<any> | Record<string, any>,
|
|
104
107
|
mask: Mask
|
|
105
108
|
): Record<string, any> {
|
|
106
109
|
const keys = toKeys(mask)
|
|
107
|
-
// Import getObjectShape lazily to avoid circular dependency
|
|
108
|
-
const { getObjectShape } = require('./mapping')
|
|
109
110
|
const shape =
|
|
110
111
|
schemaOrShape instanceof z.ZodObject ? getObjectShape(schemaOrShape) : schemaOrShape || {}
|
|
111
112
|
|
|
@@ -121,9 +122,11 @@ export function safePick(schema: z.ZodObject<any>, mask: Mask): z.ZodObject<any>
|
|
|
121
122
|
return z.object(pickShape(schema, mask))
|
|
122
123
|
}
|
|
123
124
|
|
|
124
|
-
|
|
125
|
+
/**
|
|
126
|
+
* Convenience: omit a set of keys by building the complement.
|
|
127
|
+
* Avoids using Zod's .omit() which can cause type depth issues.
|
|
128
|
+
*/
|
|
125
129
|
export function safeOmit(schema: z.ZodObject<any>, mask: Mask): z.ZodObject<any> {
|
|
126
|
-
const { getObjectShape } = require('./mapping')
|
|
127
130
|
const shape = getObjectShape(schema)
|
|
128
131
|
const omit = new Set(toKeys(mask))
|
|
129
132
|
const keep = Object.keys(shape).filter(k => !omit.has(k))
|