zodvex 0.2.4 → 0.3.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/src/tables.ts CHANGED
@@ -1,8 +1,50 @@
1
+ import { defineTable } from 'convex/server'
1
2
  import type { GenericId } from 'convex/values'
2
3
  import { Table } from 'convex-helpers/server'
3
4
  import { z } from 'zod'
4
5
  import { zid } from './ids'
5
- import { type ConvexValidatorFromZodFieldsAuto, zodToConvexFields } from './mapping'
6
+ import { type ConvexValidatorFromZodFieldsAuto, zodToConvex, zodToConvexFields } from './mapping'
7
+
8
+ /**
9
+ * Adds Convex system fields (_id, _creationTime) to a Zod schema.
10
+ *
11
+ * For object schemas: extends with system fields
12
+ * For union schemas: adds system fields to each variant
13
+ *
14
+ * @param tableName - The Convex table name
15
+ * @param schema - The Zod schema (object or union)
16
+ * @returns Schema with system fields added
17
+ */
18
+ export function addSystemFields<T extends string, S extends z.ZodTypeAny>(
19
+ tableName: T,
20
+ schema: S
21
+ ): z.ZodTypeAny {
22
+ // Handle union schemas - add system fields to each variant
23
+ if (schema instanceof z.ZodUnion || schema instanceof z.ZodDiscriminatedUnion) {
24
+ const options = (schema as z.ZodUnion<any>).options.map((variant: z.ZodTypeAny) => {
25
+ if (variant instanceof z.ZodObject) {
26
+ return variant.extend({
27
+ _id: zid(tableName),
28
+ _creationTime: z.number()
29
+ })
30
+ }
31
+ // Non-object variants are returned as-is (shouldn't happen in practice)
32
+ return variant
33
+ })
34
+ return z.union(options as any)
35
+ }
36
+
37
+ // Handle object schemas
38
+ if (schema instanceof z.ZodObject) {
39
+ return schema.extend({
40
+ _id: zid(tableName),
41
+ _creationTime: z.number()
42
+ })
43
+ }
44
+
45
+ // Fallback: return schema as-is
46
+ return schema
47
+ }
6
48
 
7
49
  // Helper to create a Zod schema for a Convex document
8
50
  export function zodDoc<
@@ -35,21 +77,43 @@ export function zodDocOrNull<
35
77
  }
36
78
 
37
79
  /**
38
- * Defines a Convex table using a raw Zod shape (an object mapping field names to Zod types).
80
+ * Helper to detect if input is an object shape (plain object with Zod validators)
81
+ */
82
+ function isObjectShape(input: any): input is Record<string, z.ZodTypeAny> {
83
+ // Check if it's a plain object (not a Zod instance)
84
+ if (!input || typeof input !== 'object') return false
85
+
86
+ // If it's a Zod instance, it's not an object shape
87
+ if (input instanceof z.ZodType) return false
88
+
89
+ // Check if all values are Zod types
90
+ for (const key in input) {
91
+ if (!(input[key] instanceof z.ZodType)) {
92
+ return false
93
+ }
94
+ }
95
+
96
+ return true
97
+ }
98
+
99
+ /**
100
+ * Defines a Convex table using either:
101
+ * - A raw Zod shape (an object mapping field names to Zod types)
102
+ * - A Zod union schema (for polymorphic tables)
39
103
  *
40
- * This function intentionally accepts a raw shape instead of a ZodObject instance.
104
+ * For object shapes, this function intentionally accepts a raw shape instead of a ZodObject instance.
41
105
  * Accepting raw shapes allows TypeScript to infer field types more accurately and efficiently,
42
106
  * leading to better type inference and performance throughout the codebase.
43
- * This architectural decision is important for projects that rely heavily on type safety and
44
- * developer experience, as it avoids the type erasure that can occur when using ZodObject directly.
107
+ *
108
+ * For union schemas, this enables polymorphic tables with discriminated unions.
45
109
  *
46
110
  * Returns the Table definition along with Zod schemas for documents and arrays.
47
111
  *
48
112
  * @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
113
+ * @param schemaOrShape - Either a raw object shape or a Zod union schema
114
+ * @returns A Table with attached helpers (shape, schema, zDoc, docArray, withSystemFields)
51
115
  *
52
- * @example
116
+ * @example Object shape
53
117
  * ```ts
54
118
  * const Users = zodTable('users', {
55
119
  * name: z.string(),
@@ -66,36 +130,115 @@ export function zodDocOrNull<
66
130
  * { returns: Users.docArray }
67
131
  * )
68
132
  * ```
133
+ *
134
+ * @example Union schema (polymorphic table)
135
+ * ```ts
136
+ * const shapeSchema = z.union([
137
+ * z.object({ kind: z.literal('circle'), r: z.number() }),
138
+ * z.object({ kind: z.literal('rectangle'), width: z.number() })
139
+ * ])
140
+ *
141
+ * const Shapes = zodTable('shapes', shapeSchema)
142
+ *
143
+ * // Use in schema
144
+ * export default defineSchema({ shapes: Shapes.table })
145
+ *
146
+ * // Use for return types with system fields
147
+ * export const getShapes = zQuery(query, {},
148
+ * async (ctx) => ctx.db.query('shapes').collect(),
149
+ * { returns: Shapes.docArray }
150
+ * )
151
+ * ```
69
152
  */
70
153
  export function zodTable<TableName extends string, Shape extends Record<string, z.ZodTypeAny>>(
71
154
  name: TableName,
72
155
  shape: Shape
73
- ) {
74
- // Convert fields with proper types
75
- const convexFields = zodToConvexFields(shape) as ConvexValidatorFromZodFieldsAuto<Shape>
76
-
77
- // Create the Table from convex-helpers with explicit type
78
- const table = Table<ConvexValidatorFromZodFieldsAuto<Shape>, TableName>(name, convexFields)
79
-
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
87
- return Object.assign(table, {
88
- shape,
89
- zDoc,
90
- docArray
91
- }) as typeof table & {
92
- shape: Shape
93
- zDoc: z.ZodObject<
156
+ ): ReturnType<typeof Table<any, TableName>> & {
157
+ shape: Shape
158
+ zDoc: z.ZodObject<
159
+ Shape & {
160
+ _id: ReturnType<typeof zid<TableName>>
161
+ _creationTime: z.ZodNumber
162
+ }
163
+ >
164
+ docArray: z.ZodArray<
165
+ z.ZodObject<
94
166
  Shape & {
95
167
  _id: ReturnType<typeof zid<TableName>>
96
168
  _creationTime: z.ZodNumber
97
169
  }
98
170
  >
99
- docArray: z.ZodArray<typeof zDoc>
171
+ >
172
+ }
173
+
174
+ export function zodTable<TableName extends string, Schema extends z.ZodTypeAny>(
175
+ name: TableName,
176
+ schema: Schema
177
+ ): {
178
+ table: ReturnType<typeof defineTable>
179
+ tableName: TableName
180
+ validator: ReturnType<typeof zodToConvex<Schema>>
181
+ schema: Schema
182
+ docArray: z.ZodArray<ReturnType<typeof addSystemFields<TableName, Schema>>>
183
+ withSystemFields: () => ReturnType<typeof addSystemFields<TableName, Schema>>
184
+ }
185
+
186
+ export function zodTable<
187
+ TableName extends string,
188
+ SchemaOrShape extends z.ZodTypeAny | Record<string, z.ZodTypeAny>
189
+ >(name: TableName, schemaOrShape: SchemaOrShape): any {
190
+ // Detect if it's an object shape or a schema
191
+ if (isObjectShape(schemaOrShape)) {
192
+ // Original object shape logic
193
+ const shape = schemaOrShape as Record<string, z.ZodTypeAny>
194
+
195
+ // Convert fields with proper types
196
+ const convexFields = zodToConvexFields(shape) as ConvexValidatorFromZodFieldsAuto<typeof shape>
197
+
198
+ // Create the Table from convex-helpers with explicit type
199
+ const table = Table<ConvexValidatorFromZodFieldsAuto<typeof shape>, TableName>(
200
+ name,
201
+ convexFields
202
+ )
203
+
204
+ // Create zDoc schema with system fields
205
+ const zDoc = zodDoc(name, z.object(shape))
206
+
207
+ // Create docArray helper for return types
208
+ const docArray = z.array(zDoc)
209
+
210
+ // Attach everything for comprehensive usage
211
+ return Object.assign(table, {
212
+ shape,
213
+ zDoc,
214
+ docArray
215
+ })
216
+ } else {
217
+ // Union or other schema type logic
218
+ const schema = schemaOrShape as z.ZodTypeAny
219
+
220
+ // Convert schema to Convex validator
221
+ const convexValidator = zodToConvex(schema)
222
+
223
+ // For unions, use defineTable directly (not Table helper which expects object fields)
224
+ // Note: TypeScript types don't reflect it, but Convex supports union validators in tables
225
+ const table = defineTable(convexValidator as any)
226
+
227
+ // Create document schema with system fields
228
+ const withFields = addSystemFields(name, schema)
229
+
230
+ // Create docArray helper
231
+ const docArray = z.array(withFields)
232
+
233
+ // Attach helpers for union tables
234
+ // Return structure similar to Table() but without fields-based helpers
235
+ return {
236
+ table,
237
+ tableName: name,
238
+ validator: convexValidator,
239
+ schema,
240
+ docArray,
241
+ withSystemFields: () => addSystemFields(name, schema)
242
+ }
100
243
  }
101
244
  }
package/src/types.ts CHANGED
@@ -15,31 +15,19 @@ export type InferArgs<A> = A extends z.ZodObject<infer S>
15
15
  ? z.infer<A>
16
16
  : Record<string, never>
17
17
 
18
- // Return type inference with immediate bailout for unions/custom to avoid depth
19
- export type InferReturns<R> = R extends z.ZodUnion<any>
20
- ? any
21
- : // Bail immediately for unions
22
- R extends z.ZodCustom<any>
18
+ // Return type inference - uses z.output for Zod schemas
19
+ // Previously had bailouts for unions/custom to avoid TypeScript depth errors,
20
+ // but research (Issue #20) showed convex-helpers handles these without issues.
21
+ // Removing bailouts fixes Issue #19 (Promise<any> return types).
22
+ export type InferReturns<R> = R extends z.ZodType<any, any, any>
23
+ ? z.output<R>
24
+ : R extends undefined
23
25
  ? any
24
- : // Bail immediately for custom
25
- R extends z.ZodType<any, any, any>
26
- ? z.output<R>
27
- : // Use z.output for other schemas
28
- R extends undefined
29
- ? any
30
- : R
26
+ : R
31
27
 
32
28
  // For handler authoring: what the handler returns before wrapper validation/encoding
33
- export type InferHandlerReturns<R> = R extends z.ZodUnion<any>
34
- ? any
35
- : // Bail immediately for unions
36
- R extends z.ZodCustom<any>
37
- ? any
38
- : // Bail immediately for custom
39
- R extends z.ZodType<any, any, any>
40
- ? z.input<R>
41
- : // Use z.input for other schemas
42
- any
29
+ // Uses z.input since this is what the handler produces before encoding
30
+ export type InferHandlerReturns<R> = R extends z.ZodType<any, any, any> ? z.input<R> : any
43
31
 
44
32
  /**
45
33
  * Extract the visibility type from a Convex builder function
package/src/wrappers.ts CHANGED
@@ -39,8 +39,8 @@ function containsCustom(schema: z.ZodTypeAny, maxDepth = 50, currentDepth = 0):
39
39
 
40
40
  let result = false
41
41
 
42
- // Use _def.typeName instead of instanceof since ZodCustom is not exported in Zod v4
43
- if ((schema as any)._def?.typeName === 'ZodCustom') {
42
+ // Zod v4 exports ZodCustom and instances expose `schema.type === "custom"`.
43
+ if (schema instanceof z.ZodCustom) {
44
44
  result = true
45
45
  } else if (schema instanceof z.ZodUnion) {
46
46
  result = (schema.options as z.ZodTypeAny[]).some(opt =>