zodvex 0.2.5 → 0.3.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 CHANGED
@@ -18,6 +18,13 @@ Type-safe Convex functions with Zod schemas. Preserve Convex's optional/nullable
18
18
  - [Form Validation](#form-validation)
19
19
  - [API Reference](#api-reference)
20
20
  - [Advanced Usage](#advanced-usage)
21
+ - [Custom Context Builders](#custom-context-builders)
22
+ - [Date Handling](#date-handling)
23
+ - [Return Type Helpers](#return-type-helpers)
24
+ - [Working with Large Schemas](#working-with-large-schemas)
25
+ - [Polymorphic Tables with Unions](#polymorphic-tables-with-unions)
26
+ - [AI SDK Compatibility](#ai-sdk-compatibility)
27
+ - [Migration Guide](./MIGRATION.md)
21
28
 
22
29
  ## Installation
23
30
 
@@ -441,27 +448,94 @@ export const updateProfile = authMutation({
441
448
 
442
449
  ### Date Handling
443
450
 
444
- Dates are automatically converted to timestamps:
451
+ zodvex automatically converts dates between JavaScript `Date` objects and Convex timestamps when using `z.date()` in your schemas.
452
+
453
+ #### Automatic Conversion (Recommended)
454
+
455
+ Use `z.date()` for automatic handling - no manual conversion needed:
445
456
 
446
457
  ```ts
447
458
  const eventShape = {
448
459
  title: z.string(),
449
460
  startDate: z.date(),
450
- endDate: z.date().nullable()
461
+ endDate: z.date().nullable(),
462
+ createdAt: z.date().optional()
451
463
  }
452
464
 
453
465
  export const Events = zodTable('events', eventShape)
454
466
 
467
+ // In your function - dates work seamlessly
455
468
  export const createEvent = zm({
456
469
  args: eventShape,
457
- handler: async (ctx, event) => {
458
- // event.startDate is a Date object
459
- // Automatically converted to timestamp for storage
460
- return await ctx.db.insert('events', event)
470
+ handler: async (ctx, { startDate, endDate, title }) => {
471
+ // startDate and endDate are already Date objects!
472
+ console.log(startDate.toISOString()) // Works
473
+
474
+ // Automatically converted to timestamps for storage
475
+ return await ctx.db.insert('events', { title, startDate, endDate })
461
476
  }
462
477
  })
478
+
479
+ // On the frontend - just pass Date objects
480
+ const createEvent = useMutation(api.events.createEvent)
481
+ await createEvent({
482
+ title: 'My Event',
483
+ startDate: new Date('2024-01-01'),
484
+ endDate: new Date('2024-01-02')
485
+ })
486
+ // ✅ No manual conversion needed!
463
487
  ```
464
488
 
489
+ **How it works:**
490
+ - **Args**: Timestamps from client → automatically decoded to `Date` objects
491
+ - **Returns**: `Date` objects from handler → automatically encoded to timestamps
492
+ - **Storage**: Dates are stored as `v.float64()` (Convex doesn't have a native Date type)
493
+
494
+ #### Manual String Dates (Alternative)
495
+
496
+ If you prefer working with ISO strings instead of Date objects, use `z.string()`:
497
+
498
+ ```ts
499
+ // ❌ Using z.string() means NO automatic conversion
500
+ const schema = {
501
+ birthday: z.string() // Stored as ISO string
502
+ }
503
+
504
+ export const updateUser = zm({
505
+ args: schema,
506
+ handler: async (ctx, { birthday }) => {
507
+ // birthday is a string, you must manually parse
508
+ const date = new Date(birthday) // Manual conversion
509
+ await ctx.db.insert('users', { birthday })
510
+ }
511
+ })
512
+
513
+ // On frontend - manual conversion
514
+ await updateUser({
515
+ birthday: new Date().toISOString() // Must manually convert
516
+ })
517
+ ```
518
+
519
+ **When to use which:**
520
+ - ✅ **`z.date()`** - When you want automatic conversion and type-safe Date objects (recommended)
521
+ - ⚠️ **`z.string()`** - When you need ISO strings for display/formatting (requires manual parsing)
522
+
523
+ #### Edge Case: Date/Number Unions
524
+
525
+ If you need a field that accepts both dates and numbers (rare), use explicit transforms:
526
+
527
+ ```ts
528
+ // Edge case: field that can be a date OR a timestamp
529
+ const flexibleDate = z.union([
530
+ z.date(),
531
+ z.number().transform(ts => new Date(ts))
532
+ ])
533
+
534
+ // Most apps don't need this - fields are either dates OR numbers, not both
535
+ ```
536
+
537
+ **Note:** In real-world schemas, mixing dates and numbers in unions is uncommon. Design your data model so fields have a single type.
538
+
465
539
  ### Return Type Helpers
466
540
 
467
541
  ```ts
@@ -501,6 +575,243 @@ const UserUpdate = safePick(User, {
501
575
  })
502
576
  ```
503
577
 
578
+ ### Polymorphic Tables with Unions
579
+
580
+ zodvex fully supports polymorphic tables using discriminated unions! Use `zodTable()` directly with union schemas:
581
+
582
+ ```ts
583
+ import { zodTable, zid } from 'zodvex'
584
+ import { z } from 'zod'
585
+
586
+ // Define your discriminated union schema
587
+ const shapeSchema = z.union([
588
+ z.object({
589
+ kind: z.literal('circle'),
590
+ cx: z.number(),
591
+ cy: z.number(),
592
+ r: z.number()
593
+ }),
594
+ z.object({
595
+ kind: z.literal('rectangle'),
596
+ x: z.number(),
597
+ y: z.number(),
598
+ width: z.number(),
599
+ height: z.number()
600
+ }),
601
+ z.object({
602
+ kind: z.literal('path'),
603
+ path: z.string()
604
+ })
605
+ ])
606
+
607
+ // Define the table - zodTable() accepts unions!
608
+ export const Shapes = zodTable('shapes', shapeSchema)
609
+
610
+ // Use in schema
611
+ export default defineSchema({
612
+ shapes: Shapes.table
613
+ })
614
+
615
+ // docArray automatically includes system fields for each variant
616
+ export const getShapes = zq({
617
+ args: {},
618
+ returns: Shapes.docArray, // ✅ Each variant has _id and _creationTime
619
+ handler: async (ctx) => ctx.db.query('shapes').collect()
620
+ })
621
+
622
+ // Create with type-safe discriminated unions
623
+ export const createShape = zm({
624
+ args: shapeSchema,
625
+ handler: async (ctx, shape) => {
626
+ // shape is discriminated - TypeScript knows the fields based on `kind`
627
+ return await ctx.db.insert('shapes', shape)
628
+ }
629
+ })
630
+ ```
631
+
632
+ #### Union Table Helpers
633
+
634
+ Union tables provide different helpers than object tables:
635
+
636
+ ```ts
637
+ const Shapes = zodTable('shapes', shapeSchema)
638
+
639
+ // Available properties:
640
+ Shapes.table // TableDefinition for schema
641
+ Shapes.tableName // 'shapes'
642
+ Shapes.schema // Original union schema
643
+ Shapes.validator // Convex validator (union)
644
+ Shapes.docArray // Array schema with system fields added to each variant
645
+ Shapes.withSystemFields() // Returns union with _id and _creationTime on each variant
646
+
647
+ // NOT available (specific to object tables):
648
+ // Shapes.shape - unions don't have a fixed shape
649
+ // Shapes.zDoc - use withSystemFields() instead
650
+ ```
651
+
652
+ #### Advanced Union Patterns
653
+
654
+ **Shared base schema:**
655
+
656
+ ```ts
657
+ const baseShape = z.object({
658
+ color: z.string(),
659
+ strokeWidth: z.number()
660
+ })
661
+
662
+ const shapeSchema = z.union([
663
+ baseShape.extend({
664
+ kind: z.literal('circle'),
665
+ radius: z.number()
666
+ }),
667
+ baseShape.extend({
668
+ kind: z.literal('rectangle'),
669
+ width: z.number(),
670
+ height: z.number()
671
+ })
672
+ ])
673
+
674
+ export const Shapes = zodTable('shapes', shapeSchema)
675
+ ```
676
+
677
+ **Using z.discriminatedUnion:**
678
+
679
+ ```ts
680
+ const shapeSchema = z.discriminatedUnion('kind', [
681
+ z.object({ kind: z.literal('circle'), r: z.number() }),
682
+ z.object({ kind: z.literal('rectangle'), w: z.number(), h: z.number() })
683
+ ])
684
+
685
+ export const Shapes = zodTable('shapes', shapeSchema)
686
+ ```
687
+
688
+ **When to use discriminated unions:**
689
+ - Polymorphic data (e.g., different shape types, notification variants)
690
+ - Tables with multiple distinct subtypes sharing common fields
691
+ - Event sourcing patterns with different event types
692
+ - When you need type-safe variant handling in TypeScript
693
+
694
+ **Learn more:**
695
+ - [Convex Polymorphic Data](https://docs.convex.dev/database/types#polymorphic-types)
696
+ - [Zod Discriminated Unions](https://zod.dev/discriminated-unions)
697
+
698
+ ### AI SDK Compatibility
699
+
700
+ zodvex schemas are fully compatible with [Vercel's AI SDK](https://sdk.vercel.ai/docs), including `zid` for Convex IDs.
701
+
702
+ #### Using with AI SDK
703
+
704
+ ```ts
705
+ import { generateObject } from 'ai'
706
+ import { openai } from '@ai-sdk/openai'
707
+ import { z } from 'zod'
708
+ import { zid } from 'zodvex'
709
+
710
+ const userSchema = z.object({
711
+ id: zid('users'), // ✅ Works with AI SDK
712
+ name: z.string(),
713
+ email: z.string().email(),
714
+ age: z.number().int(),
715
+ teamId: zid('teams').optional()
716
+ })
717
+
718
+ const result = await generateObject({
719
+ model: openai('gpt-4'),
720
+ schema: userSchema,
721
+ prompt: 'Generate a sample user profile'
722
+ })
723
+
724
+ // result.object is fully typed and validated
725
+ console.log(result.object.id) // Type: GenericId<'users'>
726
+ ```
727
+
728
+ #### Why It Works
729
+
730
+ AI SDK requires schemas to be serializable (no `.transform()` or `.brand()`). zodvex's `zid` uses type-level branding instead of runtime transforms, making it compatible:
731
+
732
+ ```ts
733
+ // zodvex approach (compatible)
734
+ zid('users') // String validator with type assertion → GenericId<'users'>
735
+
736
+ // Not compatible (using transforms)
737
+ z.string().transform(s => s as GenericId<'users'>) // ❌ AI SDK rejects
738
+ ```
739
+
740
+ #### Schemas with Custom Transforms
741
+
742
+ If you have schemas with custom `.transform()` or `.pipe()`, AI SDK may reject them. For complex transformations, consider:
743
+
744
+ **Option 1: Separate schemas**
745
+ ```ts
746
+ // For AI SDK (no transforms)
747
+ const aiUserSchema = z.object({
748
+ createdAt: z.string() // ISO string
749
+ })
750
+
751
+ // For internal use (with transforms)
752
+ const internalUserSchema = z.object({
753
+ createdAt: z.string().transform(s => new Date(s))
754
+ })
755
+ ```
756
+
757
+ **Option 2: Use zodvex's JSON Schema helper**
758
+ ```ts
759
+ import { toJSONSchema } from 'zodvex'
760
+
761
+ const schema = z.object({
762
+ userId: zid('users'),
763
+ createdAt: z.date(),
764
+ name: z.string()
765
+ })
766
+
767
+ // Automatically handles zid and z.date() for JSON Schema generation
768
+ const jsonSchema = toJSONSchema(schema)
769
+ // Use with AI SDK or other JSON Schema consumers
770
+ ```
771
+
772
+ The `toJSONSchema` helper automatically handles zodvex-managed types:
773
+ - `zid('tableName')` → `{ type: "string", format: "convex-id:tableName" }`
774
+ - `z.date()` → `{ type: "string", format: "date-time" }`
775
+
776
+ For custom overrides, use `zodvexJSONSchemaOverride` directly:
777
+ ```ts
778
+ import { zodvexJSONSchemaOverride, composeOverrides } from 'zodvex'
779
+
780
+ const jsonSchema = z.toJSONSchema(schema, {
781
+ unrepresentable: 'any',
782
+ override: composeOverrides(myCustomOverride, zodvexJSONSchemaOverride)
783
+ })
784
+ ```
785
+
786
+ ## zodvex vs convex-helpers/zod4
787
+
788
+ Convex officially supports Zod 4 via `convex-helpers/server/zod4`. zodvex builds on those primitives to provide a batteries-included, opinionated solution.
789
+
790
+ **Use convex-helpers if you want:**
791
+ - Low-level control over encoding/decoding
792
+ - Explicit Zod codecs for all conversions
793
+ - Minimal abstractions
794
+ - Both Zod 3 and 4 support in one package
795
+
796
+ **Use zodvex if you want:**
797
+ - Automatic date handling (no manual codecs needed)
798
+ - Table helpers with `.table`, `.zDoc`, `.docArray`, `.shape`
799
+ - Builder pattern API for consistent function definitions
800
+ - Codec abstraction with `.pick()` for subsets
801
+ - Turnkey developer experience
802
+
803
+ **Key differences:**
804
+
805
+ | Feature | zodvex | convex-helpers/zod4 |
806
+ |---------|--------|---------------------|
807
+ | Date conversion | Automatic with `z.date()` | Manual `z.codec()` required |
808
+ | Table helpers | `zodTable()` with helpers | Not provided |
809
+ | Builder pattern | `zQueryBuilder()`, etc. | Not provided |
810
+ | Codec abstraction | `convexCodec()` with `.pick()` | Not provided |
811
+ | Philosophy | Batteries-included | Minimal primitives |
812
+
813
+ Both are valid choices - zodvex trades some explicitness for significantly better ergonomics.
814
+
504
815
  ## Why zodvex?
505
816
 
506
817
  - **Correct optional/nullable semantics** - Preserves Convex's distinction
@@ -508,7 +819,8 @@ const UserUpdate = safePick(User, {
508
819
  - `.nullable()` → `v.union(T, v.null())` (required but can be null)
509
820
  - Both → `v.optional(v.union(T, v.null()))`
510
821
  - **Superior type safety** - Builders provide better type inference than wrapper functions
511
- - **Date handling** - Automatic `Date` ↔ timestamp conversion
822
+ - **Automatic date handling** - `Date` ↔ timestamp conversion happens transparently
823
+ - **Table helpers** - `zodTable()` provides `.zDoc`, `.docArray`, and `.shape` for DRY schemas
512
824
  - **End-to-end validation** - Same schema from database to frontend forms
513
825
 
514
826
  ## Compatibility
package/dist/index.d.ts CHANGED
@@ -1,11 +1,11 @@
1
1
  import { Customization } from 'convex-helpers/server/customFunctions';
2
2
  export { customCtx } from 'convex-helpers/server/customFunctions';
3
3
  import * as convex_server from 'convex/server';
4
- import { RegisteredQuery, RegisteredMutation, RegisteredAction, DefaultFunctionArgs, FunctionVisibility, ArgsArrayToObject, GenericDataModel, QueryBuilder, GenericQueryCtx } from 'convex/server';
4
+ import { RegisteredQuery, RegisteredMutation, RegisteredAction, DefaultFunctionArgs, FunctionVisibility, ArgsArrayToObject, GenericDataModel, QueryBuilder, GenericQueryCtx, defineTable } from 'convex/server';
5
5
  import * as convex_values from 'convex/values';
6
6
  import { VAny, VOptional, VUnion, VId, GenericId, VString, VFloat64, VInt64, VBoolean, VNull, VArray, VObject, VLiteral, VRecord, PropertyValidators } from 'convex/values';
7
7
  import { z } from 'zod';
8
- import * as convex_helpers from 'convex-helpers';
8
+ import { Table } from 'convex-helpers/server';
9
9
 
10
10
  type IsZid<T> = T extends {
11
11
  _tableName: infer _TableName extends string;
@@ -61,8 +61,11 @@ declare const registryHelpers: {
61
61
  /**
62
62
  * Create a Zod validator for a Convex Id
63
63
  *
64
- * Uses the string transform brand pattern for proper type narrowing with ctx.db.get()
65
- * This aligns with Zod v4 best practices and matches convex-helpers implementation
64
+ * Compatible with AI SDK and other tools that don't support transforms.
65
+ * Uses type-level branding instead of runtime transforms for GenericId<T> compatibility.
66
+ *
67
+ * @param tableName - The Convex table name for this ID
68
+ * @returns A Zod string validator typed as GenericId<TableName>
66
69
  */
67
70
  declare function zid<TableName extends string>(tableName: TableName): z.ZodType<GenericId<TableName>> & {
68
71
  _tableName: TableName;
@@ -75,8 +78,8 @@ declare function getObjectShape(obj: any): Record<string, any>;
75
78
  type InferArgs<A> = A extends z.ZodObject<infer S> ? z.infer<z.ZodObject<S>> : A extends Record<string, z.ZodTypeAny> ? {
76
79
  [K in keyof A]: z.infer<A[K]>;
77
80
  } : A extends z.ZodTypeAny ? z.infer<A> : Record<string, never>;
78
- type InferReturns<R> = R extends z.ZodUnion<any> ? any : R extends z.ZodCustom<any> ? any : R extends z.ZodType<any, any, any> ? z.output<R> : R extends undefined ? any : R;
79
- type InferHandlerReturns<R> = R extends z.ZodUnion<any> ? any : R extends z.ZodCustom<any> ? any : R extends z.ZodType<any, any, any> ? z.input<R> : any;
81
+ type InferReturns<R> = R extends z.ZodType<any, any, any> ? z.output<R> : R extends undefined ? any : R;
82
+ type InferHandlerReturns<R> = R extends z.ZodType<any, any, any> ? z.input<R> : any;
80
83
  /**
81
84
  * Extract the visibility type from a Convex builder function
82
85
  */
@@ -343,7 +346,133 @@ type BaseCodec = {
343
346
  declare function registerBaseCodec(codec: BaseCodec): void;
344
347
  declare function findBaseCodec(schema: any): BaseCodec | undefined;
345
348
  declare function isDateSchema(schema: any): boolean;
349
+ /**
350
+ * Checks if a schema is a zid (Convex ID) schema by looking at its description.
351
+ * zid schemas are marked with "convexId:{tableName}" in their description.
352
+ */
353
+ declare function isZidSchema(schema: z.ZodTypeAny): boolean;
354
+ /**
355
+ * Extracts the table name from a zid schema's description.
356
+ * Returns undefined if not a zid schema.
357
+ */
358
+ declare function getZidTableName(schema: z.ZodTypeAny): string | undefined;
359
+ /**
360
+ * Context object passed to the JSON Schema override function.
361
+ * Uses 'any' types for compatibility with Zod's internal types.
362
+ */
363
+ interface JSONSchemaOverrideContext {
364
+ zodSchema: any;
365
+ jsonSchema: any;
366
+ }
367
+ /**
368
+ * Override function for z.toJSONSchema that handles zodvex-managed types.
369
+ *
370
+ * Handles:
371
+ * - zid schemas: Converts to { type: "string" } with convexId format
372
+ * - z.date(): Converts to { type: "string", format: "date-time" }
373
+ *
374
+ * @example
375
+ * ```ts
376
+ * import { z } from 'zod'
377
+ * import { zid, zodvexJSONSchemaOverride } from 'zodvex'
378
+ *
379
+ * const schema = z.object({
380
+ * userId: zid('users'),
381
+ * name: z.string()
382
+ * })
383
+ *
384
+ * const jsonSchema = z.toJSONSchema(schema, {
385
+ * unrepresentable: 'any',
386
+ * override: zodvexJSONSchemaOverride
387
+ * })
388
+ * // => { type: "object", properties: { userId: { type: "string" }, name: { type: "string" } } }
389
+ * ```
390
+ */
391
+ declare function zodvexJSONSchemaOverride(ctx: JSONSchemaOverrideContext): void;
392
+ /**
393
+ * Composes multiple JSON Schema override functions into one.
394
+ * Overrides run in order - first override runs first.
395
+ *
396
+ * @example
397
+ * ```ts
398
+ * import { composeOverrides, zodvexJSONSchemaOverride } from 'zodvex'
399
+ *
400
+ * const myOverride = (ctx) => {
401
+ * if (ctx.zodSchema.description?.startsWith('myType:')) {
402
+ * ctx.jsonSchema.type = 'string'
403
+ * ctx.jsonSchema.format = 'my-format'
404
+ * }
405
+ * }
406
+ *
407
+ * // User's override runs first, then zodvex's
408
+ * z.toJSONSchema(schema, {
409
+ * unrepresentable: 'any',
410
+ * override: composeOverrides(myOverride, zodvexJSONSchemaOverride)
411
+ * })
412
+ * ```
413
+ */
414
+ declare function composeOverrides(...overrides: Array<((ctx: JSONSchemaOverrideContext) => void) | undefined>): (ctx: JSONSchemaOverrideContext) => void;
415
+ /**
416
+ * Options for toJSONSchema, matching Zod's interface.
417
+ */
418
+ interface ToJSONSchemaOptions {
419
+ target?: 'draft-4' | 'draft-7' | 'draft-2020-12' | 'openapi-3.0';
420
+ unrepresentable?: 'throw' | 'any';
421
+ cycles?: 'ref' | 'throw';
422
+ reused?: 'ref' | 'inline';
423
+ io?: 'input' | 'output';
424
+ override?: (ctx: JSONSchemaOverrideContext) => void;
425
+ }
426
+ /**
427
+ * Converts a Zod schema to JSON Schema with zodvex-aware overrides.
428
+ *
429
+ * This is a convenience wrapper around z.toJSONSchema that automatically
430
+ * handles zodvex-managed types like zid and dates.
431
+ *
432
+ * @example
433
+ * ```ts
434
+ * import { zid, toJSONSchema } from 'zodvex'
435
+ *
436
+ * const schema = z.object({
437
+ * userId: zid('users'),
438
+ * createdAt: z.date(),
439
+ * name: z.string()
440
+ * })
441
+ *
442
+ * const jsonSchema = toJSONSchema(schema)
443
+ * // Works with AI SDK's generateObject, etc.
444
+ * ```
445
+ */
446
+ declare function toJSONSchema<T extends z.ZodTypeAny>(schema: T, options?: ToJSONSchemaOptions): Record<string, any>;
346
447
 
448
+ /**
449
+ * Helper type for Convex system fields added to documents
450
+ */
451
+ type SystemFields<TableName extends string> = {
452
+ _id: ReturnType<typeof zid<TableName>>;
453
+ _creationTime: z.ZodNumber;
454
+ };
455
+ /**
456
+ * Maps over union options, extending each ZodObject variant with system fields.
457
+ * Non-object variants are preserved as-is.
458
+ */
459
+ type MapSystemFields<TableName extends string, Options extends readonly z.ZodTypeAny[]> = {
460
+ [K in keyof Options]: Options[K] extends z.ZodObject<infer Shape extends z.ZodRawShape> ? z.ZodObject<Shape & SystemFields<TableName>> : Options[K];
461
+ };
462
+ /**
463
+ * Adds Convex system fields (_id, _creationTime) to a Zod schema.
464
+ *
465
+ * For object schemas: extends with system fields
466
+ * For union schemas: adds system fields to each variant
467
+ *
468
+ * @param tableName - The Convex table name
469
+ * @param schema - The Zod schema (object or union)
470
+ * @returns Schema with system fields added
471
+ */
472
+ declare function addSystemFields<TableName extends string, Shape extends z.ZodRawShape>(tableName: TableName, schema: z.ZodObject<Shape>): z.ZodObject<Shape & SystemFields<TableName>>;
473
+ declare function addSystemFields<TableName extends string, Options extends readonly z.ZodTypeAny[]>(tableName: TableName, schema: z.ZodUnion<Options>): z.ZodUnion<MapSystemFields<TableName, Options>>;
474
+ declare function addSystemFields<TableName extends string, Options extends readonly z.ZodObject<z.ZodRawShape>[], Discriminator extends string>(tableName: TableName, schema: z.ZodDiscriminatedUnion<Options, Discriminator>): z.ZodDiscriminatedUnion<MapSystemFields<TableName, Options>, Discriminator>;
475
+ declare function addSystemFields<TableName extends string, S extends z.ZodTypeAny>(tableName: TableName, schema: S): S;
347
476
  declare function zodDoc<TableName extends string, Shape extends z.ZodRawShape, Schema extends z.ZodObject<Shape>>(tableName: TableName, schema: Schema): z.ZodObject<Shape & {
348
477
  _id: ReturnType<typeof zid<TableName>>;
349
478
  _creationTime: z.ZodNumber;
@@ -357,21 +486,23 @@ declare function zodDocOrNull<TableName extends string, Shape extends z.ZodRawSh
357
486
  _creationTime: z.ZodNumber;
358
487
  }, z.core.$strip>, z.ZodNull]>;
359
488
  /**
360
- * Defines a Convex table using a raw Zod shape (an object mapping field names to Zod types).
489
+ * Defines a Convex table using either:
490
+ * - A raw Zod shape (an object mapping field names to Zod types)
491
+ * - A Zod union schema (for polymorphic tables)
361
492
  *
362
- * This function intentionally accepts a raw shape instead of a ZodObject instance.
493
+ * For object shapes, this function intentionally accepts a raw shape instead of a ZodObject instance.
363
494
  * Accepting raw shapes allows TypeScript to infer field types more accurately and efficiently,
364
495
  * leading to better type inference and performance throughout the codebase.
365
- * This architectural decision is important for projects that rely heavily on type safety and
366
- * developer experience, as it avoids the type erasure that can occur when using ZodObject directly.
496
+ *
497
+ * For union schemas, this enables polymorphic tables with discriminated unions.
367
498
  *
368
499
  * Returns the Table definition along with Zod schemas for documents and arrays.
369
500
  *
370
501
  * @param name - The table name
371
- * @param shape - A raw object mapping field names to Zod validators
372
- * @returns A Table with attached shape, zDoc schema, and docArray helper
502
+ * @param schemaOrShape - Either a raw object shape or a Zod union schema
503
+ * @returns A Table with attached helpers (shape, schema, zDoc, docArray, withSystemFields)
373
504
  *
374
- * @example
505
+ * @example Object shape
375
506
  * ```ts
376
507
  * const Users = zodTable('users', {
377
508
  * name: z.string(),
@@ -388,65 +519,45 @@ declare function zodDocOrNull<TableName extends string, Shape extends z.ZodRawSh
388
519
  * { returns: Users.docArray }
389
520
  * )
390
521
  * ```
522
+ *
523
+ * @example Union schema (polymorphic table)
524
+ * ```ts
525
+ * const shapeSchema = z.union([
526
+ * z.object({ kind: z.literal('circle'), r: z.number() }),
527
+ * z.object({ kind: z.literal('rectangle'), width: z.number() })
528
+ * ])
529
+ *
530
+ * const Shapes = zodTable('shapes', shapeSchema)
531
+ *
532
+ * // Use in schema
533
+ * export default defineSchema({ shapes: Shapes.table })
534
+ *
535
+ * // Use for return types with system fields
536
+ * export const getShapes = zQuery(query, {},
537
+ * async (ctx) => ctx.db.query('shapes').collect(),
538
+ * { returns: Shapes.docArray }
539
+ * )
540
+ * ```
391
541
  */
392
- declare function zodTable<TableName extends string, Shape extends Record<string, z.ZodTypeAny>>(name: TableName, shape: Shape): {
393
- name: TableName;
394
- table: convex_server.TableDefinition<convex_values.VObject<convex_server.Expand<{ [Property_1 in { [Property in keyof ConvexValidatorFromZodFieldsAuto<Shape>]: ConvexValidatorFromZodFieldsAuto<Shape>[Property]["isOptional"] extends "optional" ? Property : never; }[keyof Shape]]?: Exclude<convex_values.Infer<ConvexValidatorFromZodFieldsAuto<Shape>[Property_1]>, undefined> | undefined; } & { [Property_2_1 in Exclude<keyof Shape, { [Property_2 in keyof ConvexValidatorFromZodFieldsAuto<Shape>]: ConvexValidatorFromZodFieldsAuto<Shape>[Property_2]["isOptional"] extends "optional" ? Property_2 : never; }[keyof Shape]>]: convex_values.Infer<ConvexValidatorFromZodFieldsAuto<Shape>[Property_2_1]>; }>, ConvexValidatorFromZodFieldsAuto<Shape>, "required", { [Property_3 in keyof ConvexValidatorFromZodFieldsAuto<Shape>]: Property_3 | `${Property_3 & string}.${ConvexValidatorFromZodFieldsAuto<Shape>[Property_3]["fieldPaths"]}`; }[keyof Shape] & string>, {}, {}, {}>;
395
- doc: convex_values.VObject<convex_server.Expand<{ [Property_5 in (convex_helpers.Expand<ConvexValidatorFromZodFieldsAuto<Shape> & {
396
- _id: convex_values.VId<GenericId<TableName>, "required">;
397
- _creationTime: convex_values.VFloat64<number, "required">;
398
- }> extends infer T_1 extends Record<string, convex_values.GenericValidator> ? { [Property_4 in keyof T_1]: T_1[Property_4]["isOptional"] extends "optional" ? Property_4 : never; } : never)[keyof convex_helpers.Expand<ConvexValidatorFromZodFieldsAuto<Shape> & {
399
- _id: convex_values.VId<GenericId<TableName>, "required">;
400
- _creationTime: convex_values.VFloat64<number, "required">;
401
- }>]]?: Exclude<convex_values.Infer<convex_helpers.Expand<ConvexValidatorFromZodFieldsAuto<Shape> & {
402
- _id: convex_values.VId<GenericId<TableName>, "required">;
403
- _creationTime: convex_values.VFloat64<number, "required">;
404
- }>[Property_5]>, undefined> | undefined; } & { [Property_1_1 in Exclude<keyof convex_helpers.Expand<ConvexValidatorFromZodFieldsAuto<Shape> & {
405
- _id: convex_values.VId<GenericId<TableName>, "required">;
406
- _creationTime: convex_values.VFloat64<number, "required">;
407
- }>, (convex_helpers.Expand<ConvexValidatorFromZodFieldsAuto<Shape> & {
408
- _id: convex_values.VId<GenericId<TableName>, "required">;
409
- _creationTime: convex_values.VFloat64<number, "required">;
410
- }> extends infer T_2 extends Record<string, convex_values.GenericValidator> ? { [Property_4_1 in keyof T_2]: T_2[Property_4_1]["isOptional"] extends "optional" ? Property_4_1 : never; } : never)[keyof convex_helpers.Expand<ConvexValidatorFromZodFieldsAuto<Shape> & {
411
- _id: convex_values.VId<GenericId<TableName>, "required">;
412
- _creationTime: convex_values.VFloat64<number, "required">;
413
- }>]>]: convex_values.Infer<convex_helpers.Expand<ConvexValidatorFromZodFieldsAuto<Shape> & {
414
- _id: convex_values.VId<GenericId<TableName>, "required">;
415
- _creationTime: convex_values.VFloat64<number, "required">;
416
- }>[Property_1_1]>; }>, convex_helpers.Expand<ConvexValidatorFromZodFieldsAuto<Shape> & {
417
- _id: convex_values.VId<GenericId<TableName>, "required">;
418
- _creationTime: convex_values.VFloat64<number, "required">;
419
- }>, "required", (convex_helpers.Expand<ConvexValidatorFromZodFieldsAuto<Shape> & {
420
- _id: convex_values.VId<GenericId<TableName>, "required">;
421
- _creationTime: convex_values.VFloat64<number, "required">;
422
- }> extends infer T_3 extends convex_values.PropertyValidators ? { [Property_2_1_1 in keyof T_3]: Property_2_1_1 | `${Property_2_1_1 & string}.${T_3[Property_2_1_1]["fieldPaths"]}`; } : never)[keyof convex_helpers.Expand<ConvexValidatorFromZodFieldsAuto<Shape> & {
423
- _id: convex_values.VId<GenericId<TableName>, "required">;
424
- _creationTime: convex_values.VFloat64<number, "required">;
425
- }>]>;
426
- withoutSystemFields: ConvexValidatorFromZodFieldsAuto<Shape>;
427
- withSystemFields: convex_helpers.Expand<ConvexValidatorFromZodFieldsAuto<Shape> & {
428
- _id: convex_values.VId<GenericId<TableName>, "required">;
429
- _creationTime: convex_values.VFloat64<number, "required">;
430
- }>;
431
- systemFields: {
432
- _id: convex_values.VId<GenericId<TableName>, "required">;
433
- _creationTime: convex_values.VFloat64<number, "required">;
434
- };
435
- _id: convex_values.VId<GenericId<TableName>, "required">;
436
- } & {
542
+ type AddSystemFieldsResult<TableName extends string, Schema extends z.ZodTypeAny> = Schema extends z.ZodObject<infer Shape extends z.ZodRawShape> ? z.ZodObject<Shape & SystemFields<TableName>> : Schema extends z.ZodUnion<infer Options extends readonly z.ZodTypeAny[]> ? z.ZodUnion<MapSystemFields<TableName, Options>> : Schema extends z.ZodDiscriminatedUnion<infer Options extends readonly z.ZodObject<z.ZodRawShape>[], infer Disc extends string> ? z.ZodDiscriminatedUnion<MapSystemFields<TableName, Options>, Disc> : Schema;
543
+ declare function zodTable<TableName extends string, Shape extends Record<string, z.ZodTypeAny>>(name: TableName, shape: Shape): ReturnType<typeof Table<ConvexValidatorFromZodFieldsAuto<Shape>, TableName>> & {
437
544
  shape: Shape;
438
545
  zDoc: z.ZodObject<Shape & {
439
546
  _id: ReturnType<typeof zid<TableName>>;
440
547
  _creationTime: z.ZodNumber;
441
548
  }>;
442
- docArray: z.ZodArray<z.ZodObject<Readonly<{
443
- [k: string]: z.core.$ZodType<unknown, unknown, z.core.$ZodTypeInternals<unknown, unknown>>;
444
- }> & {
445
- _id: z.ZodType<GenericId<TableName>, unknown, z.core.$ZodTypeInternals<GenericId<TableName>, unknown>> & {
446
- _tableName: TableName;
447
- };
549
+ docArray: z.ZodArray<z.ZodObject<Shape & {
550
+ _id: ReturnType<typeof zid<TableName>>;
448
551
  _creationTime: z.ZodNumber;
449
- }, z.core.$strip>>;
552
+ }>>;
553
+ };
554
+ declare function zodTable<TableName extends string, Schema extends z.ZodTypeAny>(name: TableName, schema: Schema): {
555
+ table: ReturnType<typeof defineTable>;
556
+ tableName: TableName;
557
+ validator: ReturnType<typeof zodToConvex<Schema>>;
558
+ schema: Schema;
559
+ docArray: z.ZodArray<AddSystemFieldsResult<TableName, Schema>>;
560
+ withSystemFields: () => AddSystemFieldsResult<TableName, Schema>;
450
561
  };
451
562
 
452
563
  declare function pick<T extends Record<string, any>, K extends keyof T>(obj: T, keys: K[]): Pick<T, K>;
@@ -486,4 +597,4 @@ declare function safePick(schema: z.ZodObject<any>, mask: Mask): z.ZodObject<any
486
597
  */
487
598
  declare function safeOmit(schema: z.ZodObject<any>, mask: Mask): z.ZodObject<any>;
488
599
 
489
- export { type ConvexCodec, type ConvexValidatorFromZod, type ConvexValidatorFromZodFieldsAuto, type CustomBuilder, type ExtractCtx, type ExtractVisibility, type InferArgs, type InferHandlerReturns, type InferReturns, type PreserveReturnType, type Zid, type ZodToConvexArgs, type ZodValidator, convexCodec, customFnBuilder, findBaseCodec, formatZodIssues, fromConvexJS, getObjectShape, handleZodValidationError, isDateSchema, makeUnion, mapDateFieldToNumber, pick, pickShape, registerBaseCodec, registryHelpers, returnsAs, safeOmit, safePick, toConvexJS, zActionBuilder, zCustomAction, zCustomActionBuilder, zCustomMutation, zCustomMutationBuilder, zCustomQuery, zCustomQueryBuilder, zMutationBuilder, zPaginated, zQueryBuilder, zid, zodDoc, zodDocOrNull, zodTable, zodToConvex, zodToConvexFields };
600
+ export { type ConvexCodec, type ConvexValidatorFromZod, type ConvexValidatorFromZodFieldsAuto, type CustomBuilder, type ExtractCtx, type ExtractVisibility, type InferArgs, type InferHandlerReturns, type InferReturns, type JSONSchemaOverrideContext, type PreserveReturnType, type ToJSONSchemaOptions, type Zid, type ZodToConvexArgs, type ZodValidator, addSystemFields, composeOverrides, convexCodec, customFnBuilder, findBaseCodec, formatZodIssues, fromConvexJS, getObjectShape, getZidTableName, handleZodValidationError, isDateSchema, isZidSchema, makeUnion, mapDateFieldToNumber, pick, pickShape, registerBaseCodec, registryHelpers, returnsAs, safeOmit, safePick, toConvexJS, toJSONSchema, zActionBuilder, zCustomAction, zCustomActionBuilder, zCustomMutation, zCustomMutationBuilder, zCustomQuery, zCustomQueryBuilder, zMutationBuilder, zPaginated, zQueryBuilder, zid, zodDoc, zodDocOrNull, zodTable, zodToConvex, zodToConvexFields, zodvexJSONSchemaOverride };