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.
Files changed (73) hide show
  1. package/README.md +75 -0
  2. package/dist/__type-tests__/infer-returns.d.ts +2 -0
  3. package/dist/__type-tests__/infer-returns.d.ts.map +1 -0
  4. package/dist/__type-tests__/zodTable-inference.d.ts +2 -0
  5. package/dist/__type-tests__/zodTable-inference.d.ts.map +1 -0
  6. package/dist/builders.d.ts +173 -0
  7. package/dist/builders.d.ts.map +1 -0
  8. package/dist/codec.d.ts +11 -0
  9. package/dist/codec.d.ts.map +1 -0
  10. package/dist/custom.d.ts +147 -0
  11. package/dist/custom.d.ts.map +1 -0
  12. package/dist/ids.d.ts +23 -0
  13. package/dist/ids.d.ts.map +1 -0
  14. package/dist/index.d.ts +11 -599
  15. package/dist/index.d.ts.map +1 -0
  16. package/dist/index.js +203 -58
  17. package/dist/index.js.map +1 -1
  18. package/dist/mapping/core.d.ts +5 -0
  19. package/dist/mapping/core.d.ts.map +1 -0
  20. package/dist/mapping/handlers/enum.d.ts +4 -0
  21. package/dist/mapping/handlers/enum.d.ts.map +1 -0
  22. package/dist/mapping/handlers/index.d.ts +5 -0
  23. package/dist/mapping/handlers/index.d.ts.map +1 -0
  24. package/dist/mapping/handlers/nullable.d.ts +7 -0
  25. package/dist/mapping/handlers/nullable.d.ts.map +1 -0
  26. package/dist/mapping/handlers/record.d.ts +4 -0
  27. package/dist/mapping/handlers/record.d.ts.map +1 -0
  28. package/dist/mapping/handlers/union.d.ts +5 -0
  29. package/dist/mapping/handlers/union.d.ts.map +1 -0
  30. package/dist/mapping/index.d.ts +4 -0
  31. package/dist/mapping/index.d.ts.map +1 -0
  32. package/dist/mapping/types.d.ts +43 -0
  33. package/dist/mapping/types.d.ts.map +1 -0
  34. package/dist/mapping/utils.d.ts +6 -0
  35. package/dist/mapping/utils.d.ts.map +1 -0
  36. package/dist/registry.d.ts +110 -0
  37. package/dist/registry.d.ts.map +1 -0
  38. package/dist/results.d.ts +126 -0
  39. package/dist/results.d.ts.map +1 -0
  40. package/dist/tables.d.ts +214 -0
  41. package/dist/tables.d.ts.map +1 -0
  42. package/dist/transform/index.d.ts +26 -0
  43. package/dist/transform/index.d.ts.map +1 -0
  44. package/dist/transform/index.js +442 -0
  45. package/dist/transform/index.js.map +1 -0
  46. package/dist/transform/transform.d.ts +47 -0
  47. package/dist/transform/transform.d.ts.map +1 -0
  48. package/dist/transform/traverse.d.ts +62 -0
  49. package/dist/transform/traverse.d.ts.map +1 -0
  50. package/dist/transform/types.d.ts +115 -0
  51. package/dist/transform/types.d.ts.map +1 -0
  52. package/dist/types.d.ts +29 -0
  53. package/dist/types.d.ts.map +1 -0
  54. package/dist/utils.d.ts +55 -0
  55. package/dist/utils.d.ts.map +1 -0
  56. package/dist/wrappers.d.ts +22 -0
  57. package/dist/wrappers.d.ts.map +1 -0
  58. package/package.json +13 -6
  59. package/src/__type-tests__/infer-returns.ts +24 -0
  60. package/src/__type-tests__/zodTable-inference.ts +5 -5
  61. package/src/custom.ts +205 -28
  62. package/src/index.ts +1 -0
  63. package/src/mapping/core.ts +23 -11
  64. package/src/mapping/types.ts +6 -2
  65. package/src/results.ts +110 -0
  66. package/src/tables.ts +306 -28
  67. package/src/transform/index.ts +38 -0
  68. package/src/transform/transform.ts +409 -0
  69. package/src/transform/traverse.ts +320 -0
  70. package/src/transform/types.ts +128 -0
  71. package/src/types.ts +3 -2
  72. package/src/utils.ts +35 -0
  73. package/src/wrappers.ts +10 -19
package/src/tables.ts CHANGED
@@ -5,6 +5,13 @@ import { z } from 'zod'
5
5
  import { zid } from './ids'
6
6
  import { type ConvexValidatorFromZodFieldsAuto, zodToConvex, zodToConvexFields } from './mapping'
7
7
 
8
+ /**
9
+ * Makes all properties of a Zod object shape optional.
10
+ */
11
+ type PartialShape<Shape extends z.ZodRawShape> = {
12
+ [K in keyof Shape]: z.ZodOptional<Shape[K]>
13
+ }
14
+
8
15
  /**
9
16
  * Helper type for Convex system fields added to documents
10
17
  */
@@ -23,6 +30,77 @@ type MapSystemFields<TableName extends string, Options extends readonly z.ZodTyp
23
30
  : Options[K]
24
31
  }
25
32
 
33
+ // ============================================================================
34
+ // Union Helpers - Type-safe utilities for working with Zod unions
35
+ // ============================================================================
36
+
37
+ /**
38
+ * Type guard to check if a schema is a union type (ZodUnion or ZodDiscriminatedUnion).
39
+ */
40
+ export function isZodUnion(
41
+ schema: z.ZodTypeAny
42
+ ): schema is
43
+ | z.ZodUnion<readonly z.ZodTypeAny[]>
44
+ | z.ZodDiscriminatedUnion<readonly z.ZodObject<z.ZodRawShape>[], string> {
45
+ return schema instanceof z.ZodUnion || schema instanceof z.ZodDiscriminatedUnion
46
+ }
47
+
48
+ /**
49
+ * Extracts the options array from a ZodUnion or ZodDiscriminatedUnion.
50
+ * Both union types have an `.options` property, but TypeScript doesn't
51
+ * create a common accessor after instanceof checks.
52
+ *
53
+ * @param schema - A ZodUnion or ZodDiscriminatedUnion schema
54
+ * @returns The array of union variant schemas
55
+ */
56
+ export function getUnionOptions(
57
+ schema:
58
+ | z.ZodUnion<readonly z.ZodTypeAny[]>
59
+ | z.ZodDiscriminatedUnion<readonly z.ZodObject<z.ZodRawShape>[], string>
60
+ ): readonly z.ZodTypeAny[] {
61
+ // Both ZodUnion and ZodDiscriminatedUnion have .options getter
62
+ // This is safe because we've constrained the input type
63
+ return schema.options
64
+ }
65
+
66
+ /**
67
+ * Minimum tuple type required by z.union() - at least 2 elements.
68
+ */
69
+ type UnionTuple<T extends z.ZodTypeAny = z.ZodTypeAny> = readonly [T, T, ...T[]]
70
+
71
+ /**
72
+ * Asserts that an array has at least 2 elements, as required by z.union().
73
+ * Throws an error if the array has fewer than 2 elements.
74
+ *
75
+ * @param options - Array of Zod schemas
76
+ * @throws Error if array has fewer than 2 elements
77
+ */
78
+ export function assertUnionOptions<T extends z.ZodTypeAny>(
79
+ options: readonly T[]
80
+ ): asserts options is UnionTuple<T> {
81
+ if (options.length < 2) {
82
+ throw new Error(
83
+ `z.union() requires at least 2 options, but received ${options.length}. ` +
84
+ 'This indicates an invalid union schema was passed to zodTable().'
85
+ )
86
+ }
87
+ }
88
+
89
+ /**
90
+ * Creates a z.union() from an array of options with runtime validation.
91
+ * Ensures the array has at least 2 elements as required by Zod.
92
+ *
93
+ * @param options - Array of Zod schemas (must have at least 2 elements)
94
+ * @returns A ZodUnion schema
95
+ * @throws Error if array has fewer than 2 elements
96
+ */
97
+ export function createUnionFromOptions<T extends z.ZodTypeAny>(
98
+ options: readonly T[]
99
+ ): z.ZodUnion<UnionTuple<T>> {
100
+ assertUnionOptions(options)
101
+ return z.union(options)
102
+ }
103
+
26
104
  /**
27
105
  * Adds Convex system fields (_id, _creationTime) to a Zod schema.
28
106
  *
@@ -68,8 +146,9 @@ export function addSystemFields<TableName extends string>(
68
146
  schema: z.ZodTypeAny
69
147
  ): z.ZodTypeAny {
70
148
  // Handle union schemas - add system fields to each variant
71
- if (schema instanceof z.ZodUnion || schema instanceof z.ZodDiscriminatedUnion) {
72
- const options = (schema as z.ZodUnion<any>).options.map((variant: z.ZodTypeAny) => {
149
+ if (isZodUnion(schema)) {
150
+ const originalOptions = getUnionOptions(schema)
151
+ const extendedOptions = originalOptions.map((variant: z.ZodTypeAny) => {
73
152
  if (variant instanceof z.ZodObject) {
74
153
  return variant.extend({
75
154
  _id: zid(tableName),
@@ -79,7 +158,7 @@ export function addSystemFields<TableName extends string>(
79
158
  // Non-object variants are returned as-is (shouldn't happen in practice)
80
159
  return variant
81
160
  })
82
- return z.union(options as any)
161
+ return createUnionFromOptions(extendedOptions)
83
162
  }
84
163
 
85
164
  // Handle object schemas
@@ -94,25 +173,57 @@ export function addSystemFields<TableName extends string>(
94
173
  return schema
95
174
  }
96
175
 
97
- // Helper to create a Zod schema for a Convex document
98
- export function zodDoc<
99
- TableName extends string,
100
- Shape extends z.ZodRawShape,
101
- Schema extends z.ZodObject<Shape>
102
- >(
176
+ /**
177
+ * System fields added to Convex documents.
178
+ */
179
+ type DocSystemFields<TableName extends string> = {
180
+ _id: ReturnType<typeof zid<TableName>>
181
+ _creationTime: z.ZodNumber
182
+ }
183
+ /**
184
+ * Type for validators that can be used with Convex's defineTable.
185
+ *
186
+ * Convex's defineTable expects Validator<Record<string, any>, "required", any>,
187
+ * but zodToConvex returns more specific types that TypeScript can't verify are
188
+ * compatible. This type represents validators that produce object documents.
189
+ *
190
+ * @internal
191
+ */
192
+ type TableValidator = Parameters<typeof defineTable>[0]
193
+
194
+ /**
195
+ * Asserts that a Convex validator can be used to define a table.
196
+ *
197
+ * This is needed because zodToConvex returns a specific validator type (like VUnion)
198
+ * that TypeScript can't verify is assignable to defineTable's expected input type,
199
+ * even though all union variants are objects that produce Record<string, any>.
200
+ *
201
+ * The runtime behavior is correct - Convex supports union validators in tables.
202
+ * This is purely a TypeScript limitation with complex mapped types.
203
+ *
204
+ * @internal
205
+ */
206
+ function asTableValidator<V extends { kind: string }>(validator: V): TableValidator {
207
+ return validator as unknown as TableValidator
208
+ }
209
+
210
+ /**
211
+ * Creates a Zod schema for a Convex document with system fields.
212
+ * Uses .extend() to preserve object-level options like .passthrough(), .strict(),
213
+ * .catchall(), and object-level refinements.
214
+ *
215
+ * @param tableName - The Convex table name
216
+ * @param schema - The Zod object schema for user fields
217
+ * @returns A Zod object schema with _id and _creationTime added
218
+ */
219
+ export function zodDoc<TableName extends string, Shape extends z.ZodRawShape>(
103
220
  tableName: TableName,
104
- schema: Schema
105
- ): z.ZodObject<
106
- Shape & {
107
- _id: ReturnType<typeof zid<TableName>>
108
- _creationTime: z.ZodNumber
109
- }
110
- > {
111
- // Use extend to preserve the original schema's type information
221
+ schema: z.ZodObject<Shape>
222
+ ): z.ZodObject<Shape & DocSystemFields<TableName>> {
112
223
  return schema.extend({
113
224
  _id: zid(tableName),
114
225
  _creationTime: z.number()
115
- }) as any
226
+ }) as z.ZodObject<Shape & DocSystemFields<TableName>>
116
227
  }
117
228
 
118
229
  // Helper to create nullable doc schema
@@ -213,6 +324,45 @@ type AddSystemFieldsResult<
213
324
  ? z.ZodDiscriminatedUnion<MapSystemFields<TableName, Options>, Disc>
214
325
  : Schema
215
326
 
327
+ /**
328
+ * Update schema shape: _id required, _creationTime optional, user fields partial
329
+ */
330
+ type UpdateShape<TableName extends string, Shape extends z.ZodRawShape> = {
331
+ _id: ReturnType<typeof zid<TableName>>
332
+ _creationTime: z.ZodOptional<z.ZodNumber>
333
+ } & PartialShape<Shape>
334
+
335
+ /**
336
+ * Maps over union options for update schema.
337
+ * Each variant gets _id required, _creationTime optional, and user fields partial.
338
+ */
339
+ type MapUpdateVariants<TableName extends string, Options extends readonly z.ZodTypeAny[]> = {
340
+ [K in keyof Options]: Options[K] extends z.ZodObject<infer Shape extends z.ZodRawShape>
341
+ ? z.ZodObject<UpdateShape<TableName, Shape>>
342
+ : Options[K]
343
+ }
344
+
345
+ /**
346
+ * Computes the update schema type for a given schema.
347
+ * Includes _id (required), _creationTime (optional), and partial user fields.
348
+ * For unions: each variant gets update shape
349
+ * For objects: the whole object gets update shape
350
+ * For other types: returns as-is
351
+ */
352
+ type UpdateSchemaType<
353
+ TableName extends string,
354
+ Schema extends z.ZodTypeAny
355
+ > = Schema extends z.ZodUnion<infer Options extends readonly z.ZodTypeAny[]>
356
+ ? z.ZodUnion<MapUpdateVariants<TableName, Options>>
357
+ : Schema extends z.ZodDiscriminatedUnion<
358
+ infer Options extends readonly z.ZodObject<z.ZodRawShape>[],
359
+ infer _Disc extends string
360
+ >
361
+ ? z.ZodUnion<MapUpdateVariants<TableName, Options>>
362
+ : Schema extends z.ZodObject<infer Shape extends z.ZodRawShape>
363
+ ? z.ZodObject<UpdateShape<TableName, Shape>>
364
+ : Schema
365
+
216
366
  // Overload 1: Object shape (most common case)
217
367
  export function zodTable<TableName extends string, Shape extends Record<string, z.ZodTypeAny>>(
218
368
  name: TableName,
@@ -233,6 +383,28 @@ export function zodTable<TableName extends string, Shape extends Record<string,
233
383
  }
234
384
  >
235
385
  >
386
+ schema: {
387
+ doc: z.ZodObject<
388
+ Shape & {
389
+ _id: ReturnType<typeof zid<TableName>>
390
+ _creationTime: z.ZodNumber
391
+ }
392
+ >
393
+ docArray: z.ZodArray<
394
+ z.ZodObject<
395
+ Shape & {
396
+ _id: ReturnType<typeof zid<TableName>>
397
+ _creationTime: z.ZodNumber
398
+ }
399
+ >
400
+ >
401
+ /** The base schema - user fields without system fields */
402
+ base: z.ZodObject<Shape>
403
+ /** Alias for base - user fields for insert operations */
404
+ insert: z.ZodObject<Shape>
405
+ /** Update schema - _id required, _creationTime optional, user fields partial */
406
+ update: z.ZodObject<UpdateShape<TableName, Shape>>
407
+ }
236
408
  }
237
409
 
238
410
  // Overload 2: Union/schema types
@@ -243,7 +415,16 @@ export function zodTable<TableName extends string, Schema extends z.ZodTypeAny>(
243
415
  table: ReturnType<typeof defineTable>
244
416
  tableName: TableName
245
417
  validator: ReturnType<typeof zodToConvex<Schema>>
246
- schema: Schema
418
+ schema: {
419
+ doc: AddSystemFieldsResult<TableName, Schema>
420
+ docArray: z.ZodArray<AddSystemFieldsResult<TableName, Schema>>
421
+ /** The base schema - user fields without system fields */
422
+ base: Schema
423
+ /** Alias for base - user fields for insert operations */
424
+ insert: Schema
425
+ /** Update schema - _id required, _creationTime optional, user fields partial */
426
+ update: UpdateSchemaType<TableName, Schema>
427
+ }
247
428
  docArray: z.ZodArray<AddSystemFieldsResult<TableName, Schema>>
248
429
  withSystemFields: () => AddSystemFieldsResult<TableName, Schema>
249
430
  }
@@ -272,12 +453,63 @@ export function zodTable<
272
453
  // Create docArray helper for return types
273
454
  const docArray = z.array(zDoc)
274
455
 
456
+ // Create base schema (user fields only, no system fields)
457
+ const baseSchema = z.object(shape)
458
+
459
+ // Create partial shape for user fields
460
+ const partialShape: Record<string, z.ZodTypeAny> = {}
461
+ for (const [key, value] of Object.entries(shape)) {
462
+ partialShape[key] = value.optional()
463
+ }
464
+
465
+ // Create update schema: _id required, _creationTime optional, user fields partial
466
+ const updateSchema = z.object({
467
+ _id: zid(name),
468
+ _creationTime: z.number().optional(),
469
+ ...partialShape
470
+ })
471
+
472
+ // Create schema namespace
473
+ const schema = {
474
+ doc: zDoc,
475
+ docArray,
476
+ base: baseSchema,
477
+ insert: baseSchema, // alias for base
478
+ update: updateSchema
479
+ }
480
+
481
+ // Track if we've warned about deprecated properties
482
+ const warned = { zDoc: false, docArray: false }
483
+
275
484
  // Attach everything for comprehensive usage
276
- return Object.assign(table, {
485
+ const result = Object.assign(table, {
277
486
  shape,
278
- zDoc,
279
- docArray
487
+ schema
488
+ })
489
+
490
+ Object.defineProperty(result, 'zDoc', {
491
+ get() {
492
+ if (!warned.zDoc) {
493
+ console.warn('zodvex: `zDoc` is deprecated, use `schema.doc` instead')
494
+ warned.zDoc = true
495
+ }
496
+ return schema.doc
497
+ },
498
+ enumerable: true
280
499
  })
500
+
501
+ Object.defineProperty(result, 'docArray', {
502
+ get() {
503
+ if (!warned.docArray) {
504
+ console.warn('zodvex: `docArray` is deprecated, use `schema.docArray` instead')
505
+ warned.docArray = true
506
+ }
507
+ return schema.docArray
508
+ },
509
+ enumerable: true
510
+ })
511
+
512
+ return result
281
513
  } else {
282
514
  // Union or other schema type logic
283
515
  const schema = schemaOrShape as z.ZodTypeAny
@@ -286,14 +518,60 @@ export function zodTable<
286
518
  const convexValidator = zodToConvex(schema)
287
519
 
288
520
  // For unions, use defineTable directly (not Table helper which expects object fields)
289
- // Note: TypeScript types don't reflect it, but Convex supports union validators in tables
290
- const table = defineTable(convexValidator as any)
521
+ // Convex supports union validators in tables, but TypeScript can't verify the types
522
+ const table = defineTable(asTableValidator(convexValidator))
291
523
 
292
524
  // Create document schema with system fields
293
- const withFields = addSystemFields(name, schema)
525
+ const docSchema = addSystemFields(name, schema)
294
526
 
295
527
  // Create docArray helper
296
- const docArray = z.array(withFields)
528
+ const docArray = z.array(docSchema)
529
+
530
+ // Create update schema: _id required, _creationTime optional, user fields partial
531
+ let updateSchema: z.ZodTypeAny
532
+ if (isZodUnion(schema)) {
533
+ const originalOptions = getUnionOptions(schema)
534
+ const updateOptions = originalOptions.map((variant: z.ZodTypeAny) => {
535
+ if (variant instanceof z.ZodObject) {
536
+ // Create partial shape for user fields
537
+ const partialShape: Record<string, z.ZodTypeAny> = {}
538
+ for (const [key, value] of Object.entries(variant.shape)) {
539
+ partialShape[key] = (value as z.ZodTypeAny).optional()
540
+ }
541
+ // Add system fields: _id required, _creationTime optional
542
+ return z.object({
543
+ _id: zid(name),
544
+ _creationTime: z.number().optional(),
545
+ ...partialShape
546
+ })
547
+ }
548
+ return variant
549
+ })
550
+ updateSchema = createUnionFromOptions(updateOptions)
551
+ } else if (schema instanceof z.ZodObject) {
552
+ // Create partial shape for user fields
553
+ const partialShape: Record<string, z.ZodTypeAny> = {}
554
+ for (const [key, value] of Object.entries(schema.shape)) {
555
+ partialShape[key] = (value as z.ZodTypeAny).optional()
556
+ }
557
+ // Add system fields: _id required, _creationTime optional
558
+ updateSchema = z.object({
559
+ _id: zid(name),
560
+ _creationTime: z.number().optional(),
561
+ ...partialShape
562
+ })
563
+ } else {
564
+ updateSchema = schema
565
+ }
566
+
567
+ // Create schema namespace
568
+ const schemaNamespace = {
569
+ doc: docSchema,
570
+ docArray,
571
+ base: schema,
572
+ insert: schema, // alias for base
573
+ update: updateSchema
574
+ }
297
575
 
298
576
  // Attach helpers for union tables
299
577
  // Return structure similar to Table() but without fields-based helpers
@@ -301,8 +579,8 @@ export function zodTable<
301
579
  table,
302
580
  tableName: name,
303
581
  validator: convexValidator,
304
- schema,
305
- docArray,
582
+ schema: schemaNamespace,
583
+ docArray, // deprecated
306
584
  withSystemFields: () => addSystemFields(name, schema)
307
585
  }
308
586
  }
@@ -0,0 +1,38 @@
1
+ /**
2
+ * Transform layer - General-purpose schema traversal and value transformation utilities.
3
+ *
4
+ * This module provides primitives for:
5
+ * - Walking Zod schemas (walkSchema, findFieldsWithMeta)
6
+ * - Extracting metadata from schemas (getMetadata, hasMetadata)
7
+ * - Recursively transforming values based on schema structure (transformBySchema, transformBySchemaAsync)
8
+ *
9
+ * @example
10
+ * ```ts
11
+ * import { findFieldsWithMeta, transformBySchema } from 'zodvex/transform'
12
+ *
13
+ * // Find all fields with custom metadata
14
+ * const sensitiveFields = findFieldsWithMeta(schema, meta => meta?.sensitive === true)
15
+ *
16
+ * // Transform values based on metadata
17
+ * const masked = transformBySchema(value, schema, ctx, (val, info) => {
18
+ * if (info.meta?.pii) return '[REDACTED]'
19
+ * return val
20
+ * })
21
+ * ```
22
+ */
23
+
24
+ // Transformation
25
+ export { transformBySchema, transformBySchemaAsync } from './transform'
26
+
27
+ // Traversal
28
+ export { findFieldsWithMeta, getMetadata, hasMetadata, walkSchema } from './traverse'
29
+ // Types
30
+ export type {
31
+ AsyncTransformFn,
32
+ FieldInfo,
33
+ SchemaVisitor,
34
+ TransformContext,
35
+ TransformFn,
36
+ TransformOptions,
37
+ WalkSchemaOptions
38
+ } from './types'