zodvex 0.7.3 → 0.7.4-beta.0

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "zodvex",
3
- "version": "0.7.3",
3
+ "version": "0.7.4-beta.0",
4
4
  "description": "Codec-first Zod v4 integration for Convex -- type-safe validation, encoding, DB wrapping, and codegen.",
5
5
  "keywords": ["zod", "convex", "validators", "codec", "mapping", "schema", "validation"],
6
6
  "homepage": "https://github.com/panzacoder/zodvex#readme",
@@ -73,14 +73,32 @@ type InitServerBuilders = {
73
73
 
74
74
  type MaybePromise<T> = T | Promise<T>
75
75
 
76
+ /**
77
+ * The runtime type of a customization's declared args (`z.output`), with one
78
+ * correction for the **empty** case: `z.output<$ZodObject<{}>>` widens to
79
+ * `{ [x: string]: unknown }`, which is wrong (an args-less customization receives
80
+ * no args) and breaks standalone customizations whose `input` params are
81
+ * hand-annotated narrower than that wide index signature (the #72-fallout
82
+ * regression). `{} extends ZArgs` catches both `{}` and the `Record<string, never>`
83
+ * default while staying false for real declared args.
84
+ */
85
+ type ResolvedCustomArgs<ZArgs extends ZodValidator> =
86
+ // biome-ignore lint/complexity/noBannedTypes: {} is the empty-object probe, intentional
87
+ {} extends ZArgs ? Record<string, never> : z.output<$ZodObject<ZArgs>>
88
+
76
89
  /**
77
90
  * A `.withContext()` customization for zodvex builders. Unlike convex-helpers'
78
91
  * `Customization` (which types `args` as Convex `PropertyValidators`), the
79
92
  * declared `args` are **zod** — they run through the same zod→Convex + codec
80
93
  * pipeline as consumer args, so `input` receives the **decoded runtime** values
81
- * (`z.output`), and the resulting function registers the wire validator. See #72.
94
+ * and the resulting function registers the wire validator. See #72.
95
+ *
96
+ * For a reusable, standalone customization, author it with {@link defineContext}
97
+ * (full inference, zero annotations) or annotate it with {@link ZodvexCustomization}
98
+ * — a bare object literal has no contextual type, so its `input` params would
99
+ * otherwise need hand-annotations that drift from this type.
82
100
  */
83
- type ZodWithContextCustomization<
101
+ export type ZodWithContextCustomization<
84
102
  InputCtx,
85
103
  ZArgs extends ZodValidator,
86
104
  CustomCtx extends Record<string, any>,
@@ -90,7 +108,7 @@ type ZodWithContextCustomization<
90
108
  args?: ZArgs
91
109
  input?: (
92
110
  ctx: InputCtx,
93
- args: z.output<$ZodObject<ZArgs>>,
111
+ args: ResolvedCustomArgs<ZArgs>,
94
112
  extra?: ExtraArgs
95
113
  ) => MaybePromise<{
96
114
  ctx: CustomCtx
@@ -135,7 +153,7 @@ export type ZodvexBuilder<
135
153
  >
136
154
  ) => CustomBuilder<
137
155
  FuncType,
138
- z.output<$ZodObject<ZArgs>>,
156
+ ResolvedCustomArgs<ZArgs>,
139
157
  Overwrite<CodecCtx, CustomCtx>,
140
158
  CustomMadeArgs,
141
159
  InputCtx,
@@ -144,6 +162,86 @@ export type ZodvexBuilder<
144
162
  >
145
163
  }
146
164
 
165
+ type AnyZodvexBuilder = ZodvexBuilder<any, any, any, any>
166
+
167
+ /**
168
+ * The input ctx a builder's `.withContext()` expects (the codec-wrapped ctx).
169
+ * Same-kind builders share it — `zm`/`zim`, `za`/`zia`, `zq`/`ziq` differ only in
170
+ * visibility — so a customization typed against it is reusable across both.
171
+ */
172
+ type InputCtxOf<B extends AnyZodvexBuilder> =
173
+ B extends ZodvexBuilder<any, infer CodecCtx, infer InputCtx, any>
174
+ ? Overwrite<InputCtx, CodecCtx>
175
+ : never
176
+
177
+ /**
178
+ * The shape of a `.withContext()` customization for a builder, as a **type** to
179
+ * annotate with: `const c: ZodvexCustomization<typeof zm> = { … }`.
180
+ *
181
+ * It pins the builder's input ctx so `input`'s `ctx`/`args` are contextually typed
182
+ * (no hand-annotation). A type annotation can't *infer* the output generics
183
+ * (`CustomCtx` etc.) from the value, so they default permissively here — meaning a
184
+ * customization that adds ctx fields the handler later reads should use
185
+ * {@link defineContext} (which infers them) rather than this alias. Reach for this
186
+ * when the customization adds no ctx, or re-types what it adds explicitly.
187
+ */
188
+ export type ZodvexCustomization<
189
+ B extends AnyZodvexBuilder,
190
+ ZArgs extends ZodValidator = Record<string, never>,
191
+ CustomCtx extends Record<string, any> = Record<string, any>,
192
+ CustomMadeArgs extends Record<string, any> = Record<string, never>,
193
+ ExtraArgs extends Record<string, any> = Record<string, any>
194
+ > = ZodWithContextCustomization<InputCtxOf<B>, ZArgs, CustomCtx, CustomMadeArgs, ExtraArgs>
195
+
196
+ /**
197
+ * Author a reusable `.withContext()` customization with full type inference.
198
+ *
199
+ * A standalone customization object has no contextual type, so its `input` params
200
+ * trip `noImplicitAny` and must be hand-annotated — and that hand annotation drifts
201
+ * from zodvex's internal type (the #72-fallout break). `defineContext` is an
202
+ * **identity at runtime** (`(_builder, c) => c`); its sole purpose is to be an
203
+ * inference site:
204
+ *
205
+ * - the `builder` argument pins the input ctx, so `input`'s `ctx` and `args` are
206
+ * inferred (zero annotations);
207
+ * - the output generics (`CustomCtx` / `CustomMadeArgs` / `ExtraArgs`) are inferred
208
+ * from your `input`'s return, so the handler downstream still sees the precise
209
+ * merged ctx (which a {@link ZodvexCustomization} type annotation cannot do).
210
+ *
211
+ * The result carries no visibility, so it feeds **both** same-kind builders — pass
212
+ * either (`zm`/`zim`, `za`/`zia`, `zq`/`ziq` share the input ctx):
213
+ *
214
+ * ```ts
215
+ * const authed = defineContext(zm, {
216
+ * args: {},
217
+ * input: async (ctx, _args, extra?: { required?: Entitlement[] }) => ({
218
+ * ctx: { ...ctx, identity: await resolveIdentity(ctx) },
219
+ * args: {},
220
+ * }),
221
+ * })
222
+ * export const appMutation = zm.withContext(authed)
223
+ * export const appInternalMutation = zim.withContext(authed)
224
+ * ```
225
+ */
226
+ export function defineContext<
227
+ B extends AnyZodvexBuilder,
228
+ ZArgs extends ZodValidator = Record<string, never>,
229
+ CustomCtx extends Record<string, any> = Record<string, never>,
230
+ CustomMadeArgs extends Record<string, any> = Record<string, never>,
231
+ ExtraArgs extends Record<string, any> = Record<string, any>
232
+ >(
233
+ _builder: B,
234
+ customization: ZodWithContextCustomization<
235
+ InputCtxOf<B>,
236
+ ZArgs,
237
+ CustomCtx,
238
+ CustomMadeArgs,
239
+ ExtraArgs
240
+ >
241
+ ): ZodWithContextCustomization<InputCtxOf<B>, ZArgs, CustomCtx, CustomMadeArgs, ExtraArgs> {
242
+ return customization
243
+ }
244
+
147
245
  // Overload 1: wrapDb: false — no codec DB wrapping
148
246
  export function initZodvex<DM extends GenericDataModel>(
149
247
  schema: { __zodTableMap: ZodTableMap },
@@ -49,11 +49,14 @@ export {
49
49
  } from '../../internal/db'
50
50
  // One-time setup + types
51
51
  export {
52
+ defineContext,
52
53
  initZodvex,
53
54
  type ZodvexActionCtx,
54
55
  type ZodvexBuilder,
56
+ type ZodvexCustomization,
55
57
  type ZodvexMutationCtx,
56
- type ZodvexQueryCtx
58
+ type ZodvexQueryCtx,
59
+ type ZodWithContextCustomization
57
60
  } from '../../internal/init'
58
61
  // Rule and audit types for .withRules() and .audit()
59
62
  //