zodvex 0.3.2 → 0.4.1
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 +75 -0
- package/dist/__type-tests__/infer-returns.d.ts +2 -0
- package/dist/__type-tests__/infer-returns.d.ts.map +1 -0
- package/dist/__type-tests__/zodTable-inference.d.ts +2 -0
- package/dist/__type-tests__/zodTable-inference.d.ts.map +1 -0
- package/dist/builders.d.ts +173 -0
- package/dist/builders.d.ts.map +1 -0
- package/dist/codec.d.ts +11 -0
- package/dist/codec.d.ts.map +1 -0
- package/dist/custom.d.ts +147 -0
- package/dist/custom.d.ts.map +1 -0
- package/dist/ids.d.ts +23 -0
- package/dist/ids.d.ts.map +1 -0
- package/dist/index.d.ts +11 -599
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +203 -58
- package/dist/index.js.map +1 -1
- package/dist/mapping/core.d.ts +5 -0
- package/dist/mapping/core.d.ts.map +1 -0
- package/dist/mapping/handlers/enum.d.ts +4 -0
- package/dist/mapping/handlers/enum.d.ts.map +1 -0
- package/dist/mapping/handlers/index.d.ts +5 -0
- package/dist/mapping/handlers/index.d.ts.map +1 -0
- package/dist/mapping/handlers/nullable.d.ts +7 -0
- package/dist/mapping/handlers/nullable.d.ts.map +1 -0
- package/dist/mapping/handlers/record.d.ts +4 -0
- package/dist/mapping/handlers/record.d.ts.map +1 -0
- package/dist/mapping/handlers/union.d.ts +5 -0
- package/dist/mapping/handlers/union.d.ts.map +1 -0
- package/dist/mapping/index.d.ts +4 -0
- package/dist/mapping/index.d.ts.map +1 -0
- package/dist/mapping/types.d.ts +43 -0
- package/dist/mapping/types.d.ts.map +1 -0
- package/dist/mapping/utils.d.ts +6 -0
- package/dist/mapping/utils.d.ts.map +1 -0
- package/dist/registry.d.ts +110 -0
- package/dist/registry.d.ts.map +1 -0
- package/dist/results.d.ts +126 -0
- package/dist/results.d.ts.map +1 -0
- package/dist/tables.d.ts +214 -0
- package/dist/tables.d.ts.map +1 -0
- package/dist/transform/index.d.ts +26 -0
- package/dist/transform/index.d.ts.map +1 -0
- package/dist/transform/index.js +442 -0
- package/dist/transform/index.js.map +1 -0
- package/dist/transform/transform.d.ts +47 -0
- package/dist/transform/transform.d.ts.map +1 -0
- package/dist/transform/traverse.d.ts +62 -0
- package/dist/transform/traverse.d.ts.map +1 -0
- package/dist/transform/types.d.ts +115 -0
- package/dist/transform/types.d.ts.map +1 -0
- package/dist/types.d.ts +29 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/utils.d.ts +55 -0
- package/dist/utils.d.ts.map +1 -0
- package/dist/wrappers.d.ts +22 -0
- package/dist/wrappers.d.ts.map +1 -0
- package/package.json +13 -6
- package/src/__type-tests__/infer-returns.ts +24 -0
- package/src/__type-tests__/zodTable-inference.ts +5 -5
- package/src/custom.ts +205 -28
- package/src/index.ts +1 -0
- package/src/mapping/core.ts +23 -11
- package/src/mapping/types.ts +6 -2
- package/src/results.ts +110 -0
- package/src/tables.ts +306 -28
- package/src/transform/index.ts +38 -0
- package/src/transform/transform.ts +409 -0
- package/src/transform/traverse.ts +320 -0
- package/src/transform/types.ts +128 -0
- package/src/types.ts +3 -2
- package/src/utils.ts +35 -0
- package/src/wrappers.ts +10 -19
package/src/custom.ts
CHANGED
|
@@ -16,7 +16,133 @@ import { z } from 'zod'
|
|
|
16
16
|
import { fromConvexJS, toConvexJS } from './codec'
|
|
17
17
|
import { type ZodValidator, zodToConvex, zodToConvexFields } from './mapping'
|
|
18
18
|
import type { ExtractCtx, ExtractVisibility } from './types'
|
|
19
|
-
import { handleZodValidationError, pick } from './utils'
|
|
19
|
+
import { handleZodValidationError, pick, validateReturns } from './utils'
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Hooks for observing the function execution (side effects, no return value).
|
|
23
|
+
*/
|
|
24
|
+
export type CustomizationHooks = {
|
|
25
|
+
/** Called after successful execution with access to ctx, args, and result */
|
|
26
|
+
onSuccess?: (info: {
|
|
27
|
+
ctx: unknown
|
|
28
|
+
args: Record<string, unknown>
|
|
29
|
+
result: unknown
|
|
30
|
+
}) => void | Promise<void>
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* Transforms for modifying data in the function flow.
|
|
35
|
+
*/
|
|
36
|
+
export type CustomizationTransforms = {
|
|
37
|
+
/** Transform args after validation but before handler receives them */
|
|
38
|
+
input?: (args: unknown, schema: z.ZodTypeAny) => unknown | Promise<unknown>
|
|
39
|
+
/** Transform the output after validation but before wire encoding */
|
|
40
|
+
output?: (result: unknown, schema: z.ZodTypeAny) => unknown | Promise<unknown>
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* Result returned from a customization input function.
|
|
45
|
+
* Separates Convex concepts (ctx, args) from hooks (side effects) and transforms (data modifications).
|
|
46
|
+
*/
|
|
47
|
+
export type CustomizationResult<
|
|
48
|
+
CustomCtx extends Record<string, any> = Record<string, any>,
|
|
49
|
+
CustomArgs extends Record<string, any> = Record<string, any>
|
|
50
|
+
> = {
|
|
51
|
+
/** Custom context to merge with base context */
|
|
52
|
+
ctx?: CustomCtx
|
|
53
|
+
/** Custom args to merge with parsed args */
|
|
54
|
+
args?: CustomArgs
|
|
55
|
+
/** Hooks for observing execution (side effects) */
|
|
56
|
+
hooks?: CustomizationHooks
|
|
57
|
+
/** Transforms for modifying the data flow */
|
|
58
|
+
transforms?: CustomizationTransforms
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
/**
|
|
62
|
+
* Extended input result that includes hooks and transforms.
|
|
63
|
+
* This is what the input function returns internally.
|
|
64
|
+
*/
|
|
65
|
+
export type CustomizationInputResult<
|
|
66
|
+
OutCtx extends Record<string, any>,
|
|
67
|
+
OutArgs extends Record<string, any>
|
|
68
|
+
> = {
|
|
69
|
+
ctx: OutCtx
|
|
70
|
+
args: OutArgs
|
|
71
|
+
hooks?: CustomizationHooks
|
|
72
|
+
transforms?: CustomizationTransforms
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
/**
|
|
76
|
+
* Customization type that supports hooks and transforms.
|
|
77
|
+
* This extends convex-helpers' Customization pattern to include
|
|
78
|
+
* hooks (side effects) and transforms (data modifications).
|
|
79
|
+
*
|
|
80
|
+
* Use customCtxWithHooks() to create instances of this type.
|
|
81
|
+
*/
|
|
82
|
+
export type CustomizationWithHooks<
|
|
83
|
+
InCtx extends Record<string, any>,
|
|
84
|
+
OutCtx extends Record<string, any> = Record<string, any>,
|
|
85
|
+
OutArgs extends Record<string, any> = Record<string, any>,
|
|
86
|
+
ExtraArgs extends Record<string, any> = Record<string, any>
|
|
87
|
+
> = {
|
|
88
|
+
args: Record<string, never>
|
|
89
|
+
input: (
|
|
90
|
+
ctx: InCtx,
|
|
91
|
+
args?: Record<string, unknown>,
|
|
92
|
+
extra?: ExtraArgs
|
|
93
|
+
) =>
|
|
94
|
+
| Promise<CustomizationInputResult<OutCtx, OutArgs>>
|
|
95
|
+
| CustomizationInputResult<OutCtx, OutArgs>
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
/**
|
|
99
|
+
* A helper for defining a Customization with full support for hooks and transforms.
|
|
100
|
+
* Use this instead of customCtx when you need onSuccess, transforms.input, transforms.output, etc.
|
|
101
|
+
*
|
|
102
|
+
* @example
|
|
103
|
+
* ```ts
|
|
104
|
+
* const secureMutation = zCustomMutationBuilder(
|
|
105
|
+
* mutation,
|
|
106
|
+
* customCtxWithHooks(async (ctx: MutationCtx) => {
|
|
107
|
+
* const securityCtx = await getSecurityContext(ctx)
|
|
108
|
+
* return {
|
|
109
|
+
* ctx: { securityCtx },
|
|
110
|
+
* hooks: {
|
|
111
|
+
* onSuccess: ({ result }) => console.log('Mutation returned:', result),
|
|
112
|
+
* },
|
|
113
|
+
* transforms: {
|
|
114
|
+
* // Transform incoming args (e.g., wire format → runtime objects)
|
|
115
|
+
* input: (args, schema) => transformIncomingArgs(args, securityCtx),
|
|
116
|
+
* // Transform outgoing result (e.g., runtime objects → wire format)
|
|
117
|
+
* output: (result, schema) => transformOutgoingResult(result, securityCtx),
|
|
118
|
+
* },
|
|
119
|
+
* }
|
|
120
|
+
* })
|
|
121
|
+
* )
|
|
122
|
+
* ```
|
|
123
|
+
*/
|
|
124
|
+
export function customCtxWithHooks<
|
|
125
|
+
InCtx extends Record<string, any>,
|
|
126
|
+
OutCtx extends Record<string, any> = Record<string, any>,
|
|
127
|
+
OutArgs extends Record<string, any> = Record<string, any>
|
|
128
|
+
>(
|
|
129
|
+
fn: (
|
|
130
|
+
ctx: InCtx
|
|
131
|
+
) => Promise<CustomizationResult<OutCtx, OutArgs>> | CustomizationResult<OutCtx, OutArgs>
|
|
132
|
+
): CustomizationWithHooks<InCtx, OutCtx, OutArgs> {
|
|
133
|
+
return {
|
|
134
|
+
args: {},
|
|
135
|
+
input: async (ctx: InCtx): Promise<CustomizationInputResult<OutCtx, OutArgs>> => {
|
|
136
|
+
const result = await fn(ctx)
|
|
137
|
+
return {
|
|
138
|
+
ctx: result.ctx ?? ({} as OutCtx),
|
|
139
|
+
args: result.args ?? ({} as OutArgs),
|
|
140
|
+
hooks: result.hooks,
|
|
141
|
+
transforms: result.transforms
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
}
|
|
20
146
|
|
|
21
147
|
// Type helpers for args transformation (from zodV3 example)
|
|
22
148
|
type OneArgArray<ArgsObject extends DefaultFunctionArgs = DefaultFunctionArgs> = [ArgsObject]
|
|
@@ -26,12 +152,14 @@ type NullToUndefinedOrNull<T> = T extends null ? T | undefined | void : T
|
|
|
26
152
|
type Returns<T> = Promise<NullToUndefinedOrNull<T>> | NullToUndefinedOrNull<T>
|
|
27
153
|
|
|
28
154
|
// The return value before it's been validated: returned by the handler
|
|
155
|
+
// Uses z.output since the handler produces the internal representation (e.g., Date),
|
|
156
|
+
// which is then encoded to wire format (e.g., string) before sending to the client
|
|
29
157
|
type ReturnValueInput<ReturnsValidator extends z.ZodTypeAny | ZodValidator | void> = [
|
|
30
158
|
ReturnsValidator
|
|
31
159
|
] extends [z.ZodTypeAny]
|
|
32
|
-
? Returns<z.
|
|
160
|
+
? Returns<z.output<ReturnsValidator>>
|
|
33
161
|
: [ReturnsValidator] extends [ZodValidator]
|
|
34
|
-
? Returns<z.
|
|
162
|
+
? Returns<z.output<z.ZodObject<ReturnsValidator>>>
|
|
35
163
|
: any
|
|
36
164
|
|
|
37
165
|
// The return value after it's been validated: returned to the client
|
|
@@ -171,7 +299,9 @@ export function customFnBuilder<
|
|
|
171
299
|
ExtraArgs extends Record<string, any> = Record<string, any>
|
|
172
300
|
>(
|
|
173
301
|
builder: Builder,
|
|
174
|
-
customization:
|
|
302
|
+
customization:
|
|
303
|
+
| Customization<Ctx, CustomArgsValidator, CustomCtx, CustomMadeArgs, ExtraArgs>
|
|
304
|
+
| CustomizationWithHooks<Ctx, CustomCtx, CustomMadeArgs, ExtraArgs>
|
|
175
305
|
) {
|
|
176
306
|
const customInput = customization.input ?? NoOp.input
|
|
177
307
|
const inputArgs = customization.args ?? NoOp.args
|
|
@@ -213,6 +343,13 @@ export function customFnBuilder<
|
|
|
213
343
|
args: convexArgs,
|
|
214
344
|
...returnValidator,
|
|
215
345
|
handler: async (ctx: Ctx, allArgs: any) => {
|
|
346
|
+
// Cast justification: customInput expects ObjectType<CustomArgsValidator>, but pick()
|
|
347
|
+
// returns Partial<T>. The cast is safe because inputArgs keys are derived from
|
|
348
|
+
// CustomArgsValidator at the type level. The 'added' result is typed as 'any' because
|
|
349
|
+
// it may include hooks/transforms from CustomizationWithHooks which aren't in the
|
|
350
|
+
// convex-helpers Customization type.
|
|
351
|
+
// TODO: Create a type-safe pickArgs<T>() helper that preserves the ObjectType<T>
|
|
352
|
+
// return type when the keys are statically known from the validator.
|
|
216
353
|
const added: any = await customInput(
|
|
217
354
|
ctx,
|
|
218
355
|
pick(allArgs, Object.keys(inputArgs)) as any,
|
|
@@ -229,18 +366,30 @@ export function customFnBuilder<
|
|
|
229
366
|
const finalCtx = { ...ctx, ...(added?.ctx ?? {}) }
|
|
230
367
|
const baseArgs = parsed.data as Record<string, unknown>
|
|
231
368
|
const addedArgs = (added?.args as Record<string, unknown>) ?? {}
|
|
232
|
-
|
|
369
|
+
let finalArgs = { ...baseArgs, ...addedArgs }
|
|
370
|
+
|
|
371
|
+
// Apply input transform if provided (after validation, before handler)
|
|
372
|
+
if (added?.transforms?.input) {
|
|
373
|
+
finalArgs = (await added.transforms.input(finalArgs, argsSchema)) as Record<
|
|
374
|
+
string,
|
|
375
|
+
unknown
|
|
376
|
+
>
|
|
377
|
+
}
|
|
378
|
+
|
|
233
379
|
const ret = await handler(finalCtx, finalArgs)
|
|
234
380
|
// Always run Zod return validation when returns schema is provided
|
|
235
381
|
if (returns) {
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
382
|
+
// Apply output transform BEFORE validation (converts internal format → wire format)
|
|
383
|
+
// This allows class instances (e.g., SensitiveField) to be converted to plain objects
|
|
384
|
+
// before validation processes them
|
|
385
|
+
const preTransformed = added?.transforms?.output
|
|
386
|
+
? await added.transforms.output(ret, returns as z.ZodTypeAny)
|
|
387
|
+
: ret
|
|
388
|
+
|
|
389
|
+
// Validate using encode (for codecs) with fallback to parse (for transforms)
|
|
390
|
+
const validated = validateReturns(returns as z.ZodTypeAny, preTransformed)
|
|
391
|
+
if (added?.hooks?.onSuccess) {
|
|
392
|
+
await added.hooks.onSuccess({
|
|
244
393
|
ctx,
|
|
245
394
|
args: parsed.data,
|
|
246
395
|
result: validated
|
|
@@ -248,8 +397,8 @@ export function customFnBuilder<
|
|
|
248
397
|
}
|
|
249
398
|
return toConvexJS(returns as z.ZodTypeAny, validated)
|
|
250
399
|
}
|
|
251
|
-
if (added?.onSuccess) {
|
|
252
|
-
await added.onSuccess({ ctx, args: parsed.data, result: ret })
|
|
400
|
+
if (added?.hooks?.onSuccess) {
|
|
401
|
+
await added.hooks.onSuccess({ ctx, args: parsed.data, result: ret })
|
|
253
402
|
}
|
|
254
403
|
return ret
|
|
255
404
|
}
|
|
@@ -259,6 +408,9 @@ export function customFnBuilder<
|
|
|
259
408
|
args: inputArgs,
|
|
260
409
|
...returnValidator,
|
|
261
410
|
handler: async (ctx: Ctx, allArgs: any) => {
|
|
411
|
+
// Cast justification: Same as above - customInput expects ObjectType<CustomArgsValidator>
|
|
412
|
+
// but pick() returns Partial<T>. Safe because inputArgs keys match CustomArgsValidator.
|
|
413
|
+
// TODO: Create a type-safe pickArgs<T>() helper (see comment in with-args path above).
|
|
262
414
|
const added: any = await customInput(
|
|
263
415
|
ctx,
|
|
264
416
|
pick(allArgs, Object.keys(inputArgs)) as any,
|
|
@@ -267,23 +419,36 @@ export function customFnBuilder<
|
|
|
267
419
|
const finalCtx = { ...ctx, ...(added?.ctx ?? {}) }
|
|
268
420
|
const baseArgs = allArgs as Record<string, unknown>
|
|
269
421
|
const addedArgs = (added?.args as Record<string, unknown>) ?? {}
|
|
270
|
-
|
|
422
|
+
let finalArgs = { ...baseArgs, ...addedArgs }
|
|
423
|
+
|
|
424
|
+
// Apply input transform if provided (even without args schema)
|
|
425
|
+
// Note: schema is z.unknown() in no-args path, transform should handle this
|
|
426
|
+
if (added?.transforms?.input) {
|
|
427
|
+
finalArgs = (await added.transforms.input(finalArgs, z.unknown())) as Record<
|
|
428
|
+
string,
|
|
429
|
+
unknown
|
|
430
|
+
>
|
|
431
|
+
}
|
|
432
|
+
|
|
271
433
|
const ret = await handler(finalCtx, finalArgs)
|
|
272
434
|
// Always run Zod return validation when returns schema is provided
|
|
273
435
|
if (returns) {
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
436
|
+
// Apply output transform BEFORE validation (converts internal format → wire format)
|
|
437
|
+
// This allows class instances (e.g., SensitiveField) to be converted to plain objects
|
|
438
|
+
// before validation processes them
|
|
439
|
+
const preTransformed = added?.transforms?.output
|
|
440
|
+
? await added.transforms.output(ret, returns as z.ZodTypeAny)
|
|
441
|
+
: ret
|
|
442
|
+
|
|
443
|
+
// Validate using encode (for codecs) with fallback to parse (for transforms)
|
|
444
|
+
const validated = validateReturns(returns as z.ZodTypeAny, preTransformed)
|
|
445
|
+
if (added?.hooks?.onSuccess) {
|
|
446
|
+
await added.hooks.onSuccess({ ctx, args: allArgs, result: validated })
|
|
282
447
|
}
|
|
283
448
|
return toConvexJS(returns as z.ZodTypeAny, validated)
|
|
284
449
|
}
|
|
285
|
-
if (added?.onSuccess) {
|
|
286
|
-
await added.onSuccess({ ctx, args: allArgs, result: ret })
|
|
450
|
+
if (added?.hooks?.onSuccess) {
|
|
451
|
+
await added.hooks.onSuccess({ ctx, args: allArgs, result: ret })
|
|
287
452
|
}
|
|
288
453
|
return ret
|
|
289
454
|
}
|
|
@@ -343,8 +508,16 @@ export function zCustomQuery<
|
|
|
343
508
|
query: QueryBuilder<any, Visibility>,
|
|
344
509
|
customization: Customization<any, CustomArgsValidator, CustomCtx, CustomMadeArgs, ExtraArgs>
|
|
345
510
|
) {
|
|
346
|
-
//
|
|
347
|
-
//
|
|
511
|
+
// Cast justification: This is the TypeScript overload implementation pattern. The function
|
|
512
|
+
// has two overloads (with/without DataModel constraint) that provide precise types to callers.
|
|
513
|
+
// The implementation must satisfy both overloads, which requires a broader signature.
|
|
514
|
+
// The 'as any' casts allow the implementation to delegate to customFnBuilder without
|
|
515
|
+
// TypeScript complaining about the generic parameter differences between overloads.
|
|
516
|
+
// This is type-safe because: (1) callers only see the overload signatures which are strict,
|
|
517
|
+
// (2) the runtime behavior is identical regardless of which overload matched.
|
|
518
|
+
// TODO: Consider using a conditional type or branded types to create a single signature
|
|
519
|
+
// that satisfies both overloads without casts. Alternatively, accept this as idiomatic
|
|
520
|
+
// TypeScript for overloaded functions and keep the casts.
|
|
348
521
|
return customFnBuilder<
|
|
349
522
|
any,
|
|
350
523
|
typeof query,
|
|
@@ -388,6 +561,8 @@ export function zCustomMutation<
|
|
|
388
561
|
mutation: Builder,
|
|
389
562
|
customization: Customization<any, CustomArgsValidator, CustomCtx, CustomMadeArgs, ExtraArgs>
|
|
390
563
|
) {
|
|
564
|
+
// Cast justification: Same overload implementation pattern as zCustomQuery.
|
|
565
|
+
// See detailed comment there. Type safety is enforced by the overload signature above.
|
|
391
566
|
return customFnBuilder<any, Builder, CustomArgsValidator, CustomCtx, CustomMadeArgs, ExtraArgs>(
|
|
392
567
|
mutation as any,
|
|
393
568
|
customization as any
|
|
@@ -427,6 +602,8 @@ export function zCustomAction<
|
|
|
427
602
|
action: Builder,
|
|
428
603
|
customization: Customization<any, CustomArgsValidator, CustomCtx, CustomMadeArgs, ExtraArgs>
|
|
429
604
|
) {
|
|
605
|
+
// Cast justification: Same overload implementation pattern as zCustomQuery.
|
|
606
|
+
// See detailed comment there. Type safety is enforced by the overload signature above.
|
|
430
607
|
return customFnBuilder<any, Builder, CustomArgsValidator, CustomCtx, CustomMadeArgs, ExtraArgs>(
|
|
431
608
|
action as any,
|
|
432
609
|
customization as any
|
package/src/index.ts
CHANGED
package/src/mapping/core.ts
CHANGED
|
@@ -184,20 +184,32 @@ function zodToConvexInternal<Z extends z.ZodTypeAny>(
|
|
|
184
184
|
}
|
|
185
185
|
case 'transform':
|
|
186
186
|
case 'pipe': {
|
|
187
|
-
// Check for
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
if (metadata?.brand && metadata?.originalSchema) {
|
|
195
|
-
// For branded types created by our zBrand function, use the original schema
|
|
196
|
-
convexValidator = zodToConvexInternal(metadata.originalSchema, visited)
|
|
187
|
+
// Check for native Zod v4 codec first (z.codec())
|
|
188
|
+
// Codecs have def.type='pipe' but are specifically for bidirectional transforms
|
|
189
|
+
// Use the input schema (wire format) for Convex validation
|
|
190
|
+
if (actualValidator instanceof z.ZodCodec) {
|
|
191
|
+
const inputSchema = (actualValidator as any).def?.in
|
|
192
|
+
if (inputSchema && inputSchema instanceof z.ZodType) {
|
|
193
|
+
convexValidator = zodToConvexInternal(inputSchema, visited)
|
|
197
194
|
} else {
|
|
198
|
-
// For non-registered transforms, return v.any()
|
|
199
195
|
convexValidator = v.any()
|
|
200
196
|
}
|
|
197
|
+
} else {
|
|
198
|
+
// Check for registered codec from the registry
|
|
199
|
+
const codec = findBaseCodec(actualValidator)
|
|
200
|
+
if (codec) {
|
|
201
|
+
convexValidator = codec.toValidator(actualValidator)
|
|
202
|
+
} else {
|
|
203
|
+
// Check for brand metadata
|
|
204
|
+
const metadata = registryHelpers.getMetadata(actualValidator)
|
|
205
|
+
if (metadata?.brand && metadata?.originalSchema) {
|
|
206
|
+
// For branded types created by our zBrand function, use the original schema
|
|
207
|
+
convexValidator = zodToConvexInternal(metadata.originalSchema, visited)
|
|
208
|
+
} else {
|
|
209
|
+
// For non-registered transforms, return v.any()
|
|
210
|
+
convexValidator = v.any()
|
|
211
|
+
}
|
|
212
|
+
}
|
|
201
213
|
}
|
|
202
214
|
break
|
|
203
215
|
}
|
package/src/mapping/types.ts
CHANGED
|
@@ -129,7 +129,9 @@ type ConvexValidatorFromZodBase<Z extends z.ZodTypeAny> =
|
|
|
129
129
|
? VAny<'required'>
|
|
130
130
|
: Z extends z.ZodUnknown
|
|
131
131
|
? VAny<'required'>
|
|
132
|
-
:
|
|
132
|
+
: Z extends z.ZodCodec<infer A extends z.ZodTypeAny, any>
|
|
133
|
+
? ConvexValidatorFromZodBase<A> // Use input schema (wire format) for Convex
|
|
134
|
+
: VAny<'required'>
|
|
133
135
|
|
|
134
136
|
// Main type mapper with constraint system
|
|
135
137
|
export type ConvexValidatorFromZod<
|
|
@@ -216,7 +218,9 @@ export type ConvexValidatorFromZod<
|
|
|
216
218
|
Constraint,
|
|
217
219
|
string
|
|
218
220
|
>
|
|
219
|
-
:
|
|
221
|
+
: Z extends z.ZodCodec<infer A extends z.ZodTypeAny, any>
|
|
222
|
+
? ConvexValidatorFromZod<A, Constraint> // Use input schema (wire format) for Convex
|
|
223
|
+
: VAny<'required'>
|
|
220
224
|
|
|
221
225
|
type ConvexValidatorFromZodFields<
|
|
222
226
|
T extends { [key: string]: any },
|
package/src/results.ts
ADDED
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
import { z } from 'zod'
|
|
2
|
+
|
|
3
|
+
// ============================================================================
|
|
4
|
+
// Types
|
|
5
|
+
// ============================================================================
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Result type for mutations that return data on success.
|
|
9
|
+
*/
|
|
10
|
+
export type MutationResult<T> = { success: true; data: T } | { success: false; error: string }
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Result type for mutations that don't return data (void operations).
|
|
14
|
+
*/
|
|
15
|
+
export type VoidMutationResult = { success: true } | { success: false; error: string }
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Error structure for form validation results.
|
|
19
|
+
*/
|
|
20
|
+
export type FormError = {
|
|
21
|
+
formErrors: string[]
|
|
22
|
+
fieldErrors: Record<string, string[]>
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* Result type for form submissions with field-level error support.
|
|
27
|
+
* Preserves submitted data on failure for form re-population.
|
|
28
|
+
*/
|
|
29
|
+
export type FormResult<TData> =
|
|
30
|
+
| { success: true; data: TData }
|
|
31
|
+
| { success: false; data: TData; error: FormError }
|
|
32
|
+
|
|
33
|
+
// ============================================================================
|
|
34
|
+
// Helper Functions
|
|
35
|
+
// ============================================================================
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* Create a success result with data.
|
|
39
|
+
* @example success({ id: '123' })
|
|
40
|
+
*/
|
|
41
|
+
export const success = <T>(data: T) => ({ success: true, data }) as const
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* Create a failure result with an error message.
|
|
45
|
+
* @example failure('Not found')
|
|
46
|
+
*/
|
|
47
|
+
export const failure = (error: string) => ({ success: false, error }) as const
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* Create a void success result (no data).
|
|
51
|
+
* @example ok()
|
|
52
|
+
*/
|
|
53
|
+
export const ok = () => ({ success: true }) as const
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* Create a form success result with data.
|
|
57
|
+
* @example formSuccess({ email: 'user@example.com' })
|
|
58
|
+
*/
|
|
59
|
+
export const formSuccess = <T>(data: T) => ({ success: true, data }) as const
|
|
60
|
+
|
|
61
|
+
/**
|
|
62
|
+
* Create a form failure result with data and errors.
|
|
63
|
+
* @example formFailure({ email: 'bad' }, { formErrors: [], fieldErrors: { email: ['Invalid'] } })
|
|
64
|
+
*/
|
|
65
|
+
export const formFailure = <T>(data: T, error: FormError) =>
|
|
66
|
+
({ success: false, data, error }) as const
|
|
67
|
+
|
|
68
|
+
// ============================================================================
|
|
69
|
+
// Zod Schemas for `returns` validation
|
|
70
|
+
// ============================================================================
|
|
71
|
+
|
|
72
|
+
/**
|
|
73
|
+
* Zod schema for MutationResult<T>.
|
|
74
|
+
* Use in `returns` option to validate mutation responses.
|
|
75
|
+
* @example zMutationResult(z.object({ id: zid('users') }))
|
|
76
|
+
*/
|
|
77
|
+
export const zMutationResult = <T extends z.ZodTypeAny>(dataSchema: T) =>
|
|
78
|
+
z.discriminatedUnion('success', [
|
|
79
|
+
z.object({ success: z.literal(true), data: dataSchema }),
|
|
80
|
+
z.object({ success: z.literal(false), error: z.string() })
|
|
81
|
+
])
|
|
82
|
+
|
|
83
|
+
/**
|
|
84
|
+
* Zod schema for VoidMutationResult.
|
|
85
|
+
* Use in `returns` option for void mutations.
|
|
86
|
+
* @example returns: zVoidMutationResult
|
|
87
|
+
*/
|
|
88
|
+
export const zVoidMutationResult = z.discriminatedUnion('success', [
|
|
89
|
+
z.object({ success: z.literal(true) }),
|
|
90
|
+
z.object({ success: z.literal(false), error: z.string() })
|
|
91
|
+
])
|
|
92
|
+
|
|
93
|
+
/**
|
|
94
|
+
* Zod schema for FormError.
|
|
95
|
+
*/
|
|
96
|
+
export const zFormError = z.object({
|
|
97
|
+
formErrors: z.array(z.string()),
|
|
98
|
+
fieldErrors: z.record(z.string(), z.array(z.string()))
|
|
99
|
+
})
|
|
100
|
+
|
|
101
|
+
/**
|
|
102
|
+
* Zod schema for FormResult<T>.
|
|
103
|
+
* Use in `returns` option for form submissions.
|
|
104
|
+
* @example zFormResult(z.object({ email: z.string() }))
|
|
105
|
+
*/
|
|
106
|
+
export const zFormResult = <T extends z.ZodTypeAny>(dataSchema: T) =>
|
|
107
|
+
z.discriminatedUnion('success', [
|
|
108
|
+
z.object({ success: z.literal(true), data: dataSchema }),
|
|
109
|
+
z.object({ success: z.literal(false), data: dataSchema, error: zFormError })
|
|
110
|
+
])
|