fluent-convex 0.11.1 → 0.12.3
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 +403 -0
- package/dist/ConvexBuilderWithHandler.d.ts.map +1 -1
- package/dist/ConvexBuilderWithHandler.js +4 -6
- package/dist/ConvexBuilderWithHandler.js.map +1 -1
- package/dist/types.d.ts +1 -1
- package/dist/types.d.ts.map +1 -1
- package/package.json +3 -2
- package/src/ConvexBuilderWithHandler.ts +15 -13
- package/src/builder-core.test-d.ts +25 -21
- package/src/context-helper.test.ts +1 -1
- package/src/error-paths.test.ts +7 -7
- package/src/extensibility.test.ts +3 -3
- package/src/middleware-onion.test.ts +4 -4
- package/src/types.ts +3 -2
- package/src/zod/withZod.test-d.ts +3 -2
- package/src/zod/withZod.test.ts +8 -8
package/README.md
ADDED
|
@@ -0,0 +1,403 @@
|
|
|
1
|
+

|
|
2
|
+
|
|
3
|
+
# Fluent Convex
|
|
4
|
+
|
|
5
|
+
A fluent API builder for Convex functions with middleware support, inspired by [oRPC](https://orpc.unnoq.com/).
|
|
6
|
+
|
|
7
|
+
**[Live Docs & Interactive Showcase](https://friendly-zebra-716.convex.site)** -- see every feature in action with live demos and real source code.
|
|
8
|
+
|
|
9
|
+
## Features
|
|
10
|
+
|
|
11
|
+
- **Middleware support** - Compose reusable middleware for authentication, logging, and more ([docs](https://friendly-zebra-716.convex.site/middleware))
|
|
12
|
+
- **Callable builders** - Define logic once, call it directly from other handlers, and register it multiple ways ([docs](https://friendly-zebra-716.convex.site/reusable-chains))
|
|
13
|
+
- **Type-safe** - Full TypeScript support with type inference
|
|
14
|
+
- **Fluent API** - Chain methods for a clean, readable syntax ([docs](https://friendly-zebra-716.convex.site/basics))
|
|
15
|
+
- **Plugin system** - Extend with plugins like `fluent-convex/zod` for Zod schema support ([docs](https://friendly-zebra-716.convex.site/zod-plugin))
|
|
16
|
+
- **Extensible** - Build your own plugins with the `_clone()` factory pattern ([docs](https://friendly-zebra-716.convex.site/custom-plugins))
|
|
17
|
+
- **Works with Convex** - Built on top of Convex's function system
|
|
18
|
+
|
|
19
|
+
## Installation
|
|
20
|
+
|
|
21
|
+
```bash
|
|
22
|
+
npm install fluent-convex
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
## Quick Start
|
|
26
|
+
|
|
27
|
+
> For a complete walkthrough with live demos, see the **[Getting Started guide](https://friendly-zebra-716.convex.site/)**.
|
|
28
|
+
|
|
29
|
+
**Important:** All functions must end with `.public()` or `.internal()` to be registered with Convex.
|
|
30
|
+
|
|
31
|
+
```ts
|
|
32
|
+
import { createBuilder } from "fluent-convex";
|
|
33
|
+
import { v } from "convex/values";
|
|
34
|
+
import type { DataModel } from "./_generated/dataModel";
|
|
35
|
+
|
|
36
|
+
const convex = createBuilder<DataModel>();
|
|
37
|
+
|
|
38
|
+
// Simple query
|
|
39
|
+
export const listNumbers = convex
|
|
40
|
+
.query()
|
|
41
|
+
.input({ count: v.number() })
|
|
42
|
+
.handler(async (context, input) => {
|
|
43
|
+
const numbers = await context.db
|
|
44
|
+
.query("numbers")
|
|
45
|
+
.order("desc")
|
|
46
|
+
.take(input.count);
|
|
47
|
+
|
|
48
|
+
return { numbers: numbers.map((n) => n.value) };
|
|
49
|
+
})
|
|
50
|
+
.public(); // Must end with .public() or .internal()
|
|
51
|
+
|
|
52
|
+
// With middleware
|
|
53
|
+
const authMiddleware = convex.query().createMiddleware(async (context, next) => {
|
|
54
|
+
const identity = await context.auth.getUserIdentity();
|
|
55
|
+
if (!identity) {
|
|
56
|
+
throw new Error("Unauthorized");
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
return next({
|
|
60
|
+
...context,
|
|
61
|
+
user: {
|
|
62
|
+
id: identity.subject,
|
|
63
|
+
name: identity.name ?? "Unknown",
|
|
64
|
+
},
|
|
65
|
+
});
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
export const listNumbersAuth = convex
|
|
69
|
+
.query()
|
|
70
|
+
.use(authMiddleware)
|
|
71
|
+
.input({ count: v.number() })
|
|
72
|
+
.handler(async (context, input) => {
|
|
73
|
+
const numbers = await context.db
|
|
74
|
+
.query("numbers")
|
|
75
|
+
.order("desc")
|
|
76
|
+
.take(input.count);
|
|
77
|
+
|
|
78
|
+
return {
|
|
79
|
+
viewer: context.user.name, // user is available from middleware!
|
|
80
|
+
numbers: numbers.map((n) => n.value),
|
|
81
|
+
};
|
|
82
|
+
})
|
|
83
|
+
.public();
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
## Validation
|
|
87
|
+
|
|
88
|
+
> See the **[Validation docs](https://friendly-zebra-716.convex.site/validation)** for a side-by-side comparison of all three approaches with live demos.
|
|
89
|
+
|
|
90
|
+
fluent-convex supports three flavors of input validation through the same `.input()` API:
|
|
91
|
+
|
|
92
|
+
1. **Property validators** -- `{ count: v.number() }` (simplest)
|
|
93
|
+
2. **Object validators** -- `v.object({ count: v.number() })` (with `.returns()` support)
|
|
94
|
+
3. **Zod schemas** -- `z.object({ count: z.number().min(1) })` (via the Zod plugin)
|
|
95
|
+
|
|
96
|
+
## Middleware
|
|
97
|
+
|
|
98
|
+
> See the **[Middleware docs](https://friendly-zebra-716.convex.site/middleware)** for detailed examples of both patterns.
|
|
99
|
+
|
|
100
|
+
There are two main middleware patterns:
|
|
101
|
+
|
|
102
|
+
- **Context-enrichment** -- adds new properties to the context (e.g. `ctx.user`)
|
|
103
|
+
- **Onion (wrap)** -- runs code before *and* after the handler (e.g. timing, error handling)
|
|
104
|
+
|
|
105
|
+
## Reusable Chains & Callables
|
|
106
|
+
|
|
107
|
+
> See the **[Reusable Chains docs](https://friendly-zebra-716.convex.site/reusable-chains)** for full examples with live demos.
|
|
108
|
+
|
|
109
|
+
Because the builder is immutable, you can stop the chain at any point and reuse that partial builder later. A builder with a `.handler()` but no `.public()` / `.internal()` is called a **callable** -- a fully-typed function you can:
|
|
110
|
+
|
|
111
|
+
1. **Call directly** from inside other handlers (no additional Convex function invocation)
|
|
112
|
+
2. **Register** as a standalone Convex endpoint
|
|
113
|
+
3. **Extend** with more middleware and register multiple ways
|
|
114
|
+
|
|
115
|
+
```ts
|
|
116
|
+
// 1. Define a callable - NOT yet registered with Convex
|
|
117
|
+
const getNumbers = convex
|
|
118
|
+
.query()
|
|
119
|
+
.input({ count: v.number() })
|
|
120
|
+
.handler(async (ctx, args) => {
|
|
121
|
+
const rows = await ctx.db.query("numbers").order("desc").take(args.count);
|
|
122
|
+
return rows.map((r) => r.value);
|
|
123
|
+
});
|
|
124
|
+
|
|
125
|
+
// 2. Register it as a public query
|
|
126
|
+
export const listNumbers = getNumbers.public();
|
|
127
|
+
|
|
128
|
+
// 3. Call it directly from inside another handler - no additional function invocation!
|
|
129
|
+
export const getNumbersWithTimestamp = convex
|
|
130
|
+
.query()
|
|
131
|
+
.input({ count: v.number() })
|
|
132
|
+
.handler(async (ctx, args) => {
|
|
133
|
+
const numbers = await getNumbers(ctx, args); // <-- direct call
|
|
134
|
+
return { numbers, fetchedAt: Date.now() };
|
|
135
|
+
})
|
|
136
|
+
.public();
|
|
137
|
+
|
|
138
|
+
// 4. Register the same callable with different middleware
|
|
139
|
+
export const listNumbersProtected = getNumbers.use(authMiddleware).public();
|
|
140
|
+
export const listNumbersLogged = getNumbers.use(withLogging("logged")).public();
|
|
141
|
+
```
|
|
142
|
+
|
|
143
|
+
The callable syntax is `callable(ctx, args)` -- the first argument passes the context (so the middleware chain runs with the right ctx), the second passes the validated arguments.
|
|
144
|
+
|
|
145
|
+
## Plugins
|
|
146
|
+
|
|
147
|
+
### Zod Plugin (`fluent-convex/zod`)
|
|
148
|
+
|
|
149
|
+
> See the **[Zod Plugin docs](https://friendly-zebra-716.convex.site/zod-plugin)** for live demos including refinement validation.
|
|
150
|
+
|
|
151
|
+
The Zod plugin adds Zod schema support for `.input()` and `.returns()`, with **full runtime validation** including refinements (`.min()`, `.max()`, `.email()`, etc.).
|
|
152
|
+
|
|
153
|
+
```bash
|
|
154
|
+
npm install zod convex-helpers
|
|
155
|
+
```
|
|
156
|
+
|
|
157
|
+
> **Note:** `zod` and `convex-helpers` are optional peer dependencies of `fluent-convex`. They're only needed if you use the Zod plugin.
|
|
158
|
+
|
|
159
|
+
Usage:
|
|
160
|
+
|
|
161
|
+
```ts
|
|
162
|
+
import { createBuilder } from "fluent-convex";
|
|
163
|
+
import { WithZod } from "fluent-convex/zod";
|
|
164
|
+
import { z } from "zod";
|
|
165
|
+
import type { DataModel } from "./_generated/dataModel";
|
|
166
|
+
|
|
167
|
+
const convex = createBuilder<DataModel>();
|
|
168
|
+
|
|
169
|
+
export const listNumbers = convex
|
|
170
|
+
.query()
|
|
171
|
+
.extend(WithZod) // Enable Zod support
|
|
172
|
+
.input(
|
|
173
|
+
z.object({
|
|
174
|
+
count: z.number().int().min(1).max(100), // Refinements enforced at runtime!
|
|
175
|
+
})
|
|
176
|
+
)
|
|
177
|
+
.returns(z.object({ numbers: z.array(z.number()) }))
|
|
178
|
+
.handler(async (context, input) => {
|
|
179
|
+
const numbers = await context.db.query("numbers").take(input.count);
|
|
180
|
+
return { numbers: numbers.map((n) => n.value) };
|
|
181
|
+
})
|
|
182
|
+
.public();
|
|
183
|
+
```
|
|
184
|
+
|
|
185
|
+
Key features:
|
|
186
|
+
- **Full runtime validation** - Zod refinements (`.min()`, `.max()`, `.email()`, `.regex()`, etc.) are enforced server-side. Args are validated before the handler runs; return values after.
|
|
187
|
+
- **Structural conversion** - Zod schemas are automatically converted to Convex validators for Convex's built-in validation.
|
|
188
|
+
- **Composable** - `.extend(WithZod)` preserves the `WithZod` type through `.use()`, `.input()`, and `.returns()` chains.
|
|
189
|
+
- **Plain validators still work** - You can mix Zod and Convex validators in the same builder chain.
|
|
190
|
+
|
|
191
|
+
## Extensibility
|
|
192
|
+
|
|
193
|
+
> See the **[Custom Plugins docs](https://friendly-zebra-716.convex.site/custom-plugins)** for a complete worked example with live demo.
|
|
194
|
+
|
|
195
|
+
You can extend the builder with your own plugins by subclassing `ConvexBuilderWithFunctionKind` and overriding the `_clone()` factory method.
|
|
196
|
+
|
|
197
|
+
### Writing a Plugin
|
|
198
|
+
|
|
199
|
+
The `_clone()` method is called internally by `.use()`, `.input()`, and `.returns()` to create new builder instances. By overriding it, your plugin's type is preserved through the entire builder chain.
|
|
200
|
+
|
|
201
|
+
```ts
|
|
202
|
+
import {
|
|
203
|
+
ConvexBuilderWithFunctionKind,
|
|
204
|
+
type GenericDataModel,
|
|
205
|
+
type FunctionType,
|
|
206
|
+
type Context,
|
|
207
|
+
type EmptyObject,
|
|
208
|
+
type ConvexArgsValidator,
|
|
209
|
+
type ConvexReturnsValidator,
|
|
210
|
+
type ConvexBuilderDef,
|
|
211
|
+
} from "fluent-convex";
|
|
212
|
+
|
|
213
|
+
class MyPlugin<
|
|
214
|
+
TDataModel extends GenericDataModel = GenericDataModel,
|
|
215
|
+
TFunctionType extends FunctionType = FunctionType,
|
|
216
|
+
TCurrentContext extends Context = EmptyObject,
|
|
217
|
+
TArgsValidator extends ConvexArgsValidator | undefined = undefined,
|
|
218
|
+
TReturnsValidator extends ConvexReturnsValidator | undefined = undefined,
|
|
219
|
+
> extends ConvexBuilderWithFunctionKind<
|
|
220
|
+
TDataModel,
|
|
221
|
+
TFunctionType,
|
|
222
|
+
TCurrentContext,
|
|
223
|
+
TArgsValidator,
|
|
224
|
+
TReturnsValidator
|
|
225
|
+
> {
|
|
226
|
+
// Accept both builder instances (from .extend()) and raw defs (from _clone())
|
|
227
|
+
constructor(builderOrDef: any) {
|
|
228
|
+
const def =
|
|
229
|
+
builderOrDef instanceof ConvexBuilderWithFunctionKind
|
|
230
|
+
? (builderOrDef as any).def
|
|
231
|
+
: builderOrDef;
|
|
232
|
+
super(def);
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
// Override _clone() to preserve MyPlugin through the chain
|
|
236
|
+
protected _clone(def: ConvexBuilderDef<any, any, any>): any {
|
|
237
|
+
return new MyPlugin(def);
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
// Add custom methods
|
|
241
|
+
myCustomMethod(param: string) {
|
|
242
|
+
console.log("Custom method called with:", param);
|
|
243
|
+
return this;
|
|
244
|
+
}
|
|
245
|
+
}
|
|
246
|
+
```
|
|
247
|
+
|
|
248
|
+
Usage:
|
|
249
|
+
|
|
250
|
+
```ts
|
|
251
|
+
export const myQuery = convex
|
|
252
|
+
.query()
|
|
253
|
+
.extend(MyPlugin)
|
|
254
|
+
.myCustomMethod("hello") // Custom method from plugin
|
|
255
|
+
.use(authMiddleware) // .use() preserves MyPlugin type
|
|
256
|
+
.input({ count: v.number() })
|
|
257
|
+
.handler(async (ctx, input) => { ... })
|
|
258
|
+
.public();
|
|
259
|
+
```
|
|
260
|
+
|
|
261
|
+
### Composing Multiple Plugins
|
|
262
|
+
|
|
263
|
+
Plugins can be composed with `.extend()`:
|
|
264
|
+
|
|
265
|
+
```ts
|
|
266
|
+
export const myQuery = convex
|
|
267
|
+
.query()
|
|
268
|
+
.extend(MyPlugin)
|
|
269
|
+
.extend(WithZod) // WithZod overrides .input()/.returns() from MyPlugin
|
|
270
|
+
.myCustomMethod("hello")
|
|
271
|
+
.input(z.object({ count: z.number() }))
|
|
272
|
+
.handler(async (ctx, input) => { ... })
|
|
273
|
+
.public();
|
|
274
|
+
```
|
|
275
|
+
|
|
276
|
+
## Flexible Method Ordering
|
|
277
|
+
|
|
278
|
+
The builder API is flexible about method ordering, allowing you to structure your code in the way that makes the most sense for your use case.
|
|
279
|
+
|
|
280
|
+
### Middleware After Handler
|
|
281
|
+
|
|
282
|
+
You can add middleware **after** defining the handler, which is useful when you want to wrap existing handlers with additional functionality:
|
|
283
|
+
|
|
284
|
+
```ts
|
|
285
|
+
export const getNumbers = convex
|
|
286
|
+
.query()
|
|
287
|
+
.input({ count: v.number() })
|
|
288
|
+
.handler(async (context, input) => {
|
|
289
|
+
return await context.db.query("numbers").take(input.count);
|
|
290
|
+
})
|
|
291
|
+
.use(authMiddleware) // Middleware added after handler
|
|
292
|
+
.public();
|
|
293
|
+
```
|
|
294
|
+
|
|
295
|
+
### Callable Builders
|
|
296
|
+
|
|
297
|
+
Before registering a function with `.public()` or `.internal()`, the builder is **callable** -- you can invoke it directly from other handlers (see [Reusable Chains](#reusable-chains--callables) above) or use it in tests:
|
|
298
|
+
|
|
299
|
+
```ts
|
|
300
|
+
// A callable (not yet registered)
|
|
301
|
+
const getDouble = convex
|
|
302
|
+
.query()
|
|
303
|
+
.input({ count: v.number() })
|
|
304
|
+
.handler(async (context, input) => {
|
|
305
|
+
return { doubled: input.count * 2 };
|
|
306
|
+
});
|
|
307
|
+
|
|
308
|
+
// Call it from another handler
|
|
309
|
+
export const tripled = convex
|
|
310
|
+
.query()
|
|
311
|
+
.input({ count: v.number() })
|
|
312
|
+
.handler(async (ctx, input) => {
|
|
313
|
+
const { doubled } = await getDouble(ctx, input);
|
|
314
|
+
return { tripled: doubled + input.count };
|
|
315
|
+
})
|
|
316
|
+
.public();
|
|
317
|
+
|
|
318
|
+
// Or call it directly in tests
|
|
319
|
+
const mockContext = {} as any;
|
|
320
|
+
const result = await getDouble(mockContext, { count: 5 });
|
|
321
|
+
console.log(result); // { doubled: 10 }
|
|
322
|
+
|
|
323
|
+
// Register it when you also need it as a standalone endpoint
|
|
324
|
+
export const doubleNumber = getDouble.public();
|
|
325
|
+
```
|
|
326
|
+
|
|
327
|
+
### Method Ordering Rules
|
|
328
|
+
|
|
329
|
+
- **`.returns()`** must be called **before** `.handler()`
|
|
330
|
+
- **`.use()`** can be called **before or after** `.handler()`
|
|
331
|
+
- **`.public()`** or **`.internal()`** must be called **after** `.handler()` and is **required** to register the function
|
|
332
|
+
- Functions are **callable** before registration, **non-callable** after registration
|
|
333
|
+
- **All exported functions must end with `.public()` or `.internal()`** - functions without registration will not be available in your Convex API
|
|
334
|
+
|
|
335
|
+
## API
|
|
336
|
+
|
|
337
|
+
### Methods
|
|
338
|
+
|
|
339
|
+
- `.query()` - Define a Convex query
|
|
340
|
+
- `.mutation()` - Define a Convex mutation
|
|
341
|
+
- `.action()` - Define a Convex action
|
|
342
|
+
- `.public()` - Register the function as public (required to register)
|
|
343
|
+
- `.internal()` - Register the function as internal/private (required to register)
|
|
344
|
+
- `.input(validator)` - Set input validation (Convex validators)
|
|
345
|
+
- `.returns(validator)` - Set return validation (Convex validators)
|
|
346
|
+
- `.use(middleware)` - Apply middleware
|
|
347
|
+
- `.createMiddleware(fn)` - Create a middleware function
|
|
348
|
+
- `.handler(fn)` - Define the function handler
|
|
349
|
+
- `.extend(plugin)` - Extend the builder with a plugin class
|
|
350
|
+
|
|
351
|
+
## Caveats
|
|
352
|
+
|
|
353
|
+
### Circular types when calling `api.*` in the same file
|
|
354
|
+
|
|
355
|
+
When a function calls other functions via `api.*` in the same file, and those functions don't have explicit `.returns()` validators, TypeScript may report circular initializer errors (TS7022). This is a standard Convex/TypeScript limitation, not specific to fluent-convex. Workarounds:
|
|
356
|
+
1. Add `.returns()` to the **called** functions -- this gives them explicit return types, breaking the cycle
|
|
357
|
+
2. Move the calling function to a separate file
|
|
358
|
+
3. Use `internal.*` from a different module
|
|
359
|
+
|
|
360
|
+
## Development
|
|
361
|
+
|
|
362
|
+
This is a monorepo using npm workspaces:
|
|
363
|
+
|
|
364
|
+
- `/packages/fluent-convex` - The core library (includes the Zod plugin at `fluent-convex/zod`)
|
|
365
|
+
- `/apps/example` - Example Convex app
|
|
366
|
+
- `/apps/docs` - Interactive docs & showcase site ([live](https://friendly-zebra-716.convex.site))
|
|
367
|
+
|
|
368
|
+
### Setup
|
|
369
|
+
|
|
370
|
+
```bash
|
|
371
|
+
npm install
|
|
372
|
+
```
|
|
373
|
+
|
|
374
|
+
This will install dependencies for all workspaces.
|
|
375
|
+
|
|
376
|
+
### Building
|
|
377
|
+
|
|
378
|
+
```bash
|
|
379
|
+
npm run build
|
|
380
|
+
```
|
|
381
|
+
|
|
382
|
+
### Running tests
|
|
383
|
+
|
|
384
|
+
```bash
|
|
385
|
+
npm test
|
|
386
|
+
```
|
|
387
|
+
|
|
388
|
+
### Running the example
|
|
389
|
+
|
|
390
|
+
```bash
|
|
391
|
+
cd apps/example
|
|
392
|
+
npm run dev
|
|
393
|
+
```
|
|
394
|
+
|
|
395
|
+
### Running the docs locally
|
|
396
|
+
|
|
397
|
+
```bash
|
|
398
|
+
npm run docs:dev
|
|
399
|
+
```
|
|
400
|
+
|
|
401
|
+
## Credits
|
|
402
|
+
|
|
403
|
+
Borrowed heavily from [oRPC](https://orpc.unnoq.com/learn-and-contribute/overview) and helped out by AI.
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ConvexBuilderWithHandler.d.ts","sourceRoot":"","sources":["../src/ConvexBuilderWithHandler.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,KAAK,gBAAgB,EACrB,KAAK,eAAe,EACpB,KAAK,kBAAkB,EACvB,KAAK,gBAAgB,EAOtB,MAAM,eAAe,CAAC;AACvB,OAAO,KAAK,EAAE,gBAAgB,EAAuB,MAAM,cAAc,CAAC;AAE1E,OAAO,KAAK,EACV,YAAY,EACZ,OAAO,EACP,WAAW,EACX,mBAAmB,EACnB,sBAAsB,EAEtB,gBAAgB,EAChB,YAAY,EACZ,eAAe,EAIf,kBAAkB,EACnB,MAAM,SAAS,CAAC;AAEjB;;;;;;;;;GASG;AACH,KAAK,oBAAoB,CACvB,iBAAiB,SAAS,sBAAsB,GAAG,SAAS,EAC5D,cAAc,IACZ,CAAC,iBAAiB,CAAC,SAAS,CAAC,sBAAsB,CAAC,GACpD,OAAO,CAAC,kBAAkB,CAAC,iBAAiB,CAAC,CAAC,GAC9C,OAAO,CAAC,cAAc,CAAC,CAAC;AAE5B,qBAAa,wBAAwB,CACnC,UAAU,SAAS,gBAAgB,GAAG,gBAAgB,EACtD,aAAa,SAAS,YAAY,GAAG,YAAY,EACjD,eAAe,SAAS,OAAO,GAAG,WAAW,EAC7C,cAAc,SAAS,mBAAmB,GAAG,SAAS,GAAG,SAAS,EAClE,iBAAiB,SAAS,sBAAsB,GAAG,SAAS,GAAG,SAAS,EACxE,cAAc,GAAG,GAAG;IAEpB,SAAS,CAAC,GAAG,EAAE,gBAAgB,CAC7B,aAAa,EACb,cAAc,EACd,iBAAiB,CAClB,CAAC;gBAGA,GAAG,EAAE,gBAAgB,CAAC,aAAa,EAAE,cAAc,EAAE,iBAAiB,CAAC;
|
|
1
|
+
{"version":3,"file":"ConvexBuilderWithHandler.d.ts","sourceRoot":"","sources":["../src/ConvexBuilderWithHandler.ts"],"names":[],"mappings":"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"}
|
|
@@ -5,8 +5,8 @@ export class ConvexBuilderWithHandler {
|
|
|
5
5
|
constructor(def) {
|
|
6
6
|
this.def = def;
|
|
7
7
|
// Create a callable function that delegates to _call
|
|
8
|
-
const callable = ((context) => {
|
|
9
|
-
return this._call(context);
|
|
8
|
+
const callable = ((context, args) => {
|
|
9
|
+
return this._call(context, args);
|
|
10
10
|
});
|
|
11
11
|
// Copy properties from prototype to the callable function
|
|
12
12
|
// This is a bit of a hack to make the function behave like an instance of the class
|
|
@@ -28,14 +28,12 @@ export class ConvexBuilderWithHandler {
|
|
|
28
28
|
return extendBuilder(this, fnOrCls);
|
|
29
29
|
}
|
|
30
30
|
// Internal method to handle the call
|
|
31
|
-
_call(context) {
|
|
31
|
+
async _call(context, args) {
|
|
32
32
|
const { handler, middlewares } = this.def;
|
|
33
33
|
if (!handler) {
|
|
34
34
|
throw new Error("Handler not set.");
|
|
35
35
|
}
|
|
36
|
-
return
|
|
37
|
-
return this._executeWithMiddleware(middlewares, context, handler, args);
|
|
38
|
-
};
|
|
36
|
+
return this._executeWithMiddleware(middlewares, context, handler, args);
|
|
39
37
|
}
|
|
40
38
|
/**
|
|
41
39
|
* Execute middleware as an onion: each middleware's `next()` runs the rest
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ConvexBuilderWithHandler.js","sourceRoot":"","sources":["../src/ConvexBuilderWithHandler.ts"],"names":[],"mappings":"AAAA,OAAO,EAKL,YAAY,EACZ,oBAAoB,EACpB,eAAe,EACf,uBAAuB,EACvB,aAAa,EACb,qBAAqB,GACtB,MAAM,eAAe,CAAC;AAEvB,OAAO,EAAE,MAAM,IAAI,aAAa,EAAE,MAAM,UAAU,CAAC;AAkCnD,MAAM,OAAO,wBAAwB;IAQzB,GAAG,CAIX;IAEF,YACE,GAAuE;QAEvE,IAAI,CAAC,GAAG,GAAG,GAAG,CAAC;QAEf,qDAAqD;QACrD,MAAM,QAAQ,GAAG,CAAC,
|
|
1
|
+
{"version":3,"file":"ConvexBuilderWithHandler.js","sourceRoot":"","sources":["../src/ConvexBuilderWithHandler.ts"],"names":[],"mappings":"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"}
|
package/dist/types.d.ts
CHANGED
|
@@ -28,7 +28,7 @@ export type ActionCtx<DataModel extends GenericDataModel = GenericDataModel> = G
|
|
|
28
28
|
export type FunctionType = "query" | "mutation" | "action";
|
|
29
29
|
export type Visibility = "public" | "internal";
|
|
30
30
|
export type CallableBuilder<TCurrentContext extends Context, TArgsValidator extends ConvexArgsValidator | undefined, THandlerReturn> = {
|
|
31
|
-
(context: TCurrentContext
|
|
31
|
+
(context: TCurrentContext, args: InferredArgs<TArgsValidator>): Promise<THandlerReturn>;
|
|
32
32
|
};
|
|
33
33
|
export interface ConvexBuilderDef<TFunctionType extends FunctionType | undefined = undefined, TArgsValidator extends ConvexArgsValidator | undefined = undefined, TReturnsValidator extends ConvexReturnsValidator | undefined = undefined> {
|
|
34
34
|
functionType?: TFunctionType;
|
package/dist/types.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,kBAAkB,EAAE,gBAAgB,EAAE,MAAM,eAAe,CAAC;AAC1E,OAAO,KAAK,EACV,gBAAgB,EAChB,kBAAkB,EAClB,eAAe,EACf,gBAAgB,EACjB,MAAM,eAAe,CAAC;AACvB,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,cAAc,CAAC;AAExD,MAAM,MAAM,OAAO,GAAG,MAAM,CAAC;AAE7B,MAAM,MAAM,mBAAmB,GAAG,kBAAkB,GAAG,gBAAgB,CAAC;AACxE,MAAM,MAAM,sBAAsB,GAAG,gBAAgB,CAAC;AAEtD,KAAK,aAAa,CAAC,CAAC,IAAI,CAAC,SAAS,gBAAgB,GAAG,CAAC,CAAC,MAAM,CAAC,GAAG,KAAK,CAAC;AAEvE,MAAM,MAAM,WAAW,GAAG,MAAM,CAAC,KAAK,EAAE,KAAK,CAAC,CAAC;AAE/C,MAAM,MAAM,mBAAmB,CAC7B,SAAS,SAAS,OAAO,EACzB,SAAS,SAAS,OAAO,IACvB,SAAS,SAAS,WAAW,GAAG,SAAS,GAAG,SAAS,CAAC;AAE1D,KAAK,YAAY,CAAC,CAAC,SAAS,MAAM,CAAC,WAAW,EAAE,GAAG,CAAC,IAAI;KACrD,CAAC,IAAI,MAAM,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,SAAS,gBAAgB,GACzC,CAAC,CAAC,CAAC,CAAC,CAAC,YAAY,CAAC,SAAS,UAAU,GACnC,CAAC,GACD,KAAK,GACP,KAAK;CACV,CAAC,MAAM,CAAC,CAAC,CAAC;AAEX,KAAK,YAAY,CAAC,CAAC,SAAS,MAAM,CAAC,WAAW,EAAE,GAAG,CAAC,IAAI;KACrD,CAAC,IAAI,MAAM,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,SAAS,gBAAgB,GACzC,CAAC,CAAC,CAAC,CAAC,CAAC,YAAY,CAAC,SAAS,UAAU,GACnC,KAAK,GACL,CAAC,GACH,KAAK;CACV,CAAC,MAAM,CAAC,CAAC,CAAC;AAEX,KAAK,YAAY,CAAC,CAAC,SAAS,MAAM,CAAC,WAAW,EAAE,GAAG,CAAC,IAAI;KACrD,CAAC,IAAI,YAAY,CAAC,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC,SAAS,gBAAgB,GAClD,aAAa,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,GAAG,SAAS,GAC/B,KAAK;CACV,CAAC;AAEF,KAAK,YAAY,CAAC,CAAC,SAAS,MAAM,CAAC,WAAW,EAAE,GAAG,CAAC,IAAI;KACrD,CAAC,IAAI,YAAY,CAAC,CAAC,CAAC,GAAG,aAAa,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;CAC5C,CAAC;AAEF,MAAM,MAAM,SAAS,CAAC,CAAC,SAAS,mBAAmB,IACjD,CAAC,SAAS,gBAAgB,GAAG,CAAC,CAAC,MAAM,CAAC,GAAG,YAAY,CAAC,CAAC,CAAC,GAAG,YAAY,CAAC,CAAC,CAAC,CAAC;AAE7E,MAAM,MAAM,YAAY,CAAC,CAAC,SAAS,sBAAsB,IAAI,aAAa,CAAC,CAAC,CAAC,CAAC;AAE9E,MAAM,MAAM,UAAU,CAAC,CAAC,IAAI,CAAC,GAAG,WAAW,CAAC,CAAC,CAAC,CAAC;AAE/C,MAAM,MAAM,QAAQ,CAAC,SAAS,SAAS,gBAAgB,GAAG,gBAAgB,IACxE,eAAe,CAAC,SAAS,CAAC,CAAC;AAC7B,MAAM,MAAM,WAAW,CAAC,SAAS,SAAS,gBAAgB,GAAG,gBAAgB,IAC3E,kBAAkB,CAAC,SAAS,CAAC,CAAC;AAChC,MAAM,MAAM,SAAS,CAAC,SAAS,SAAS,gBAAgB,GAAG,gBAAgB,IACzE,gBAAgB,CAAC,SAAS,CAAC,CAAC;AAE9B,MAAM,MAAM,YAAY,GAAG,OAAO,GAAG,UAAU,GAAG,QAAQ,CAAC;AAC3D,MAAM,MAAM,UAAU,GAAG,QAAQ,GAAG,UAAU,CAAC;AAE/C,MAAM,MAAM,eAAe,CACzB,eAAe,SAAS,OAAO,EAC/B,cAAc,SAAS,mBAAmB,GAAG,SAAS,EACtD,cAAc,IACZ;IACF,CACE,OAAO,EAAE,eAAe,
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "fluent-convex",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.12.3",
|
|
4
4
|
"description": "A fluent API builder for Convex functions with middleware support, inspired by oRPC",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/index.js",
|
|
@@ -28,7 +28,7 @@
|
|
|
28
28
|
"typecheck": "tsc --noEmit",
|
|
29
29
|
"test": "vitest run --typecheck",
|
|
30
30
|
"dev:test": "vitest --typecheck",
|
|
31
|
-
"prepublishOnly": "npm run build"
|
|
31
|
+
"prepublishOnly": "node -e \"require('fs').copyFileSync('../../README.md','README.md')\" && npm run build"
|
|
32
32
|
},
|
|
33
33
|
"keywords": [
|
|
34
34
|
"convex",
|
|
@@ -44,6 +44,7 @@
|
|
|
44
44
|
"publishConfig": {
|
|
45
45
|
"access": "public"
|
|
46
46
|
},
|
|
47
|
+
"homepage": "https://friendly-zebra-716.convex.site/",
|
|
47
48
|
"repository": {
|
|
48
49
|
"type": "git",
|
|
49
50
|
"url": "git+https://github.com/mikecann/fluent-convex.git"
|
|
@@ -65,8 +65,11 @@ export class ConvexBuilderWithHandler<
|
|
|
65
65
|
this.def = def;
|
|
66
66
|
|
|
67
67
|
// Create a callable function that delegates to _call
|
|
68
|
-
const callable = ((
|
|
69
|
-
|
|
68
|
+
const callable = ((
|
|
69
|
+
context: TCurrentContext,
|
|
70
|
+
args: InferredArgs<TArgsValidator>
|
|
71
|
+
) => {
|
|
72
|
+
return this._call(context, args);
|
|
70
73
|
}) as any;
|
|
71
74
|
|
|
72
75
|
// Copy properties from prototype to the callable function
|
|
@@ -98,23 +101,22 @@ export class ConvexBuilderWithHandler<
|
|
|
98
101
|
}
|
|
99
102
|
|
|
100
103
|
// Internal method to handle the call
|
|
101
|
-
private _call(
|
|
102
|
-
context: TCurrentContext
|
|
103
|
-
|
|
104
|
+
private async _call(
|
|
105
|
+
context: TCurrentContext,
|
|
106
|
+
args: InferredArgs<TArgsValidator>
|
|
107
|
+
): Promise<THandlerReturn> {
|
|
104
108
|
const { handler, middlewares } = this.def;
|
|
105
109
|
|
|
106
110
|
if (!handler) {
|
|
107
111
|
throw new Error("Handler not set.");
|
|
108
112
|
}
|
|
109
113
|
|
|
110
|
-
return
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
);
|
|
117
|
-
};
|
|
114
|
+
return this._executeWithMiddleware(
|
|
115
|
+
middlewares,
|
|
116
|
+
context as Context,
|
|
117
|
+
handler,
|
|
118
|
+
args
|
|
119
|
+
);
|
|
118
120
|
}
|
|
119
121
|
|
|
120
122
|
/**
|
|
@@ -222,7 +222,7 @@ describe("Builder Core", () => {
|
|
|
222
222
|
|
|
223
223
|
// Should be callable
|
|
224
224
|
assertType<
|
|
225
|
-
(context: any
|
|
225
|
+
(context: any, args: { count: number }) => Promise<string>
|
|
226
226
|
>(nonRegisteredQuery);
|
|
227
227
|
});
|
|
228
228
|
|
|
@@ -308,10 +308,9 @@ describe("Builder Core", () => {
|
|
|
308
308
|
// Should still be callable after middleware
|
|
309
309
|
assertType<
|
|
310
310
|
(
|
|
311
|
-
context: any
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
}) => Promise<{ count: number; userId: string }>
|
|
311
|
+
context: any,
|
|
312
|
+
args: { count: number }
|
|
313
|
+
) => Promise<{ count: number; userId: string }>
|
|
315
314
|
>(callableQuery);
|
|
316
315
|
});
|
|
317
316
|
|
|
@@ -348,8 +347,9 @@ describe("Builder Core", () => {
|
|
|
348
347
|
// Should still be callable after multiple middleware
|
|
349
348
|
assertType<
|
|
350
349
|
(
|
|
351
|
-
context: any
|
|
352
|
-
|
|
350
|
+
context: any,
|
|
351
|
+
args: { count: number }
|
|
352
|
+
) => Promise<{ count: number }>
|
|
353
353
|
>(callableQuery);
|
|
354
354
|
});
|
|
355
355
|
|
|
@@ -362,7 +362,7 @@ describe("Builder Core", () => {
|
|
|
362
362
|
});
|
|
363
363
|
|
|
364
364
|
// Should be callable
|
|
365
|
-
assertType<(context: any
|
|
365
|
+
assertType<(context: any, args: { value: number }) => Promise<any>>(
|
|
366
366
|
callableMutation
|
|
367
367
|
);
|
|
368
368
|
});
|
|
@@ -377,7 +377,7 @@ describe("Builder Core", () => {
|
|
|
377
377
|
|
|
378
378
|
// Should be callable
|
|
379
379
|
assertType<
|
|
380
|
-
(context: any
|
|
380
|
+
(context: any, args: { url: string }) => Promise<{ url: string }>
|
|
381
381
|
>(callableAction);
|
|
382
382
|
});
|
|
383
383
|
|
|
@@ -398,11 +398,12 @@ describe("Builder Core", () => {
|
|
|
398
398
|
// Should be callable
|
|
399
399
|
assertType<
|
|
400
400
|
(
|
|
401
|
-
context: any
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
401
|
+
context: any,
|
|
402
|
+
args: {
|
|
403
|
+
name?: string;
|
|
404
|
+
count?: number;
|
|
405
|
+
}
|
|
406
|
+
) => Promise<{ name?: string; count?: number }>
|
|
406
407
|
>(callableQuery);
|
|
407
408
|
});
|
|
408
409
|
|
|
@@ -422,8 +423,9 @@ describe("Builder Core", () => {
|
|
|
422
423
|
// Should be callable
|
|
423
424
|
assertType<
|
|
424
425
|
(
|
|
425
|
-
context: any
|
|
426
|
-
|
|
426
|
+
context: any,
|
|
427
|
+
args: { count: number }
|
|
428
|
+
) => Promise<{ numbers: number[] }>
|
|
427
429
|
>(callableQuery);
|
|
428
430
|
});
|
|
429
431
|
|
|
@@ -435,8 +437,9 @@ describe("Builder Core", () => {
|
|
|
435
437
|
// Should be callable
|
|
436
438
|
assertType<
|
|
437
439
|
(
|
|
438
|
-
context: any
|
|
439
|
-
|
|
440
|
+
context: any,
|
|
441
|
+
args: Record<never, never>
|
|
442
|
+
) => Promise<{ success: boolean }>
|
|
440
443
|
>(callableQuery);
|
|
441
444
|
});
|
|
442
445
|
|
|
@@ -451,8 +454,9 @@ describe("Builder Core", () => {
|
|
|
451
454
|
// Before .public(), should be callable
|
|
452
455
|
assertType<
|
|
453
456
|
(
|
|
454
|
-
context: any
|
|
455
|
-
|
|
457
|
+
context: any,
|
|
458
|
+
args: { count: number }
|
|
459
|
+
) => Promise<{ count: number }>
|
|
456
460
|
>(callableQuery);
|
|
457
461
|
|
|
458
462
|
const registeredQuery = callableQuery.public();
|
|
@@ -471,7 +475,7 @@ describe("Builder Core", () => {
|
|
|
471
475
|
});
|
|
472
476
|
|
|
473
477
|
// Before .internal(), should be callable
|
|
474
|
-
assertType<(context: any
|
|
478
|
+
assertType<(context: any, args: { value: number }) => Promise<any>>(
|
|
475
479
|
callableMutation
|
|
476
480
|
);
|
|
477
481
|
|
package/src/error-paths.test.ts
CHANGED
|
@@ -107,7 +107,7 @@ describe("middleware error propagation", () => {
|
|
|
107
107
|
.use(failing)
|
|
108
108
|
.handler(async () => "should not reach");
|
|
109
109
|
|
|
110
|
-
await expect(fn({} as any
|
|
110
|
+
await expect(fn({} as any, {})).rejects.toThrow(
|
|
111
111
|
"middleware failed before next"
|
|
112
112
|
);
|
|
113
113
|
});
|
|
@@ -123,7 +123,7 @@ describe("middleware error propagation", () => {
|
|
|
123
123
|
.use(failsAfter)
|
|
124
124
|
.handler(async () => "handler ran");
|
|
125
125
|
|
|
126
|
-
await expect(fn({} as any
|
|
126
|
+
await expect(fn({} as any, {})).rejects.toThrow(
|
|
127
127
|
"middleware failed after next"
|
|
128
128
|
);
|
|
129
129
|
});
|
|
@@ -159,7 +159,7 @@ describe("middleware error propagation", () => {
|
|
|
159
159
|
throw new Error("boom");
|
|
160
160
|
});
|
|
161
161
|
|
|
162
|
-
await expect(fn({} as any
|
|
162
|
+
await expect(fn({} as any, {})).rejects.toThrow("boom");
|
|
163
163
|
|
|
164
164
|
// Both middleware should have caught the error in reverse order
|
|
165
165
|
expect(order).toEqual([
|
|
@@ -188,7 +188,7 @@ describe("middleware skipping next()", () => {
|
|
|
188
188
|
return "handler result";
|
|
189
189
|
});
|
|
190
190
|
|
|
191
|
-
await fn({} as any
|
|
191
|
+
await fn({} as any, {});
|
|
192
192
|
|
|
193
193
|
// Handler should NOT have executed
|
|
194
194
|
expect(handlerRan.value).toBe(false);
|
|
@@ -204,7 +204,7 @@ describe("handler called with correct context and args", () => {
|
|
|
204
204
|
return { name: args.name, count: args.count };
|
|
205
205
|
});
|
|
206
206
|
|
|
207
|
-
const result = await fn({} as any
|
|
207
|
+
const result = await fn({} as any, { name: "test", count: 42 });
|
|
208
208
|
expect(result).toEqual({ name: "test", count: 42 });
|
|
209
209
|
});
|
|
210
210
|
|
|
@@ -224,7 +224,7 @@ describe("handler called with correct context and args", () => {
|
|
|
224
224
|
return { a: ctx.a, b: ctx.b };
|
|
225
225
|
});
|
|
226
226
|
|
|
227
|
-
const result = await fn({} as any
|
|
227
|
+
const result = await fn({} as any, {});
|
|
228
228
|
expect(result).toEqual({ a: 1, b: 2 });
|
|
229
229
|
});
|
|
230
230
|
});
|
|
@@ -235,7 +235,7 @@ describe("empty middleware chain", () => {
|
|
|
235
235
|
.query()
|
|
236
236
|
.handler(async () => "no middleware");
|
|
237
237
|
|
|
238
|
-
const result = await fn({} as any
|
|
238
|
+
const result = await fn({} as any, {});
|
|
239
239
|
expect(result).toBe("no middleware");
|
|
240
240
|
});
|
|
241
241
|
});
|
|
@@ -173,7 +173,7 @@ describe("Extensibility", () => {
|
|
|
173
173
|
console.log("Keys:", Object.keys(handlerBuilder));
|
|
174
174
|
|
|
175
175
|
// The result should be callable
|
|
176
|
-
const result = await handlerBuilder({} as any
|
|
176
|
+
const result = await handlerBuilder({} as any, { message: "hello" });
|
|
177
177
|
expect(result).toBe("LOG: hello");
|
|
178
178
|
});
|
|
179
179
|
|
|
@@ -187,7 +187,7 @@ describe("Extensibility", () => {
|
|
|
187
187
|
return args.defaultField;
|
|
188
188
|
});
|
|
189
189
|
|
|
190
|
-
const result = await chained({} as any
|
|
190
|
+
const result = await chained({} as any, { defaultField: "test" });
|
|
191
191
|
expect(result).toBe("test");
|
|
192
192
|
});
|
|
193
193
|
|
|
@@ -209,7 +209,7 @@ describe("Extensibility", () => {
|
|
|
209
209
|
return `${args.defaultField}-${ctx.extra}`;
|
|
210
210
|
});
|
|
211
211
|
|
|
212
|
-
const result = await chained({} as any
|
|
212
|
+
const result = await chained({} as any, { defaultField: "value" });
|
|
213
213
|
expect(result).toBe("value-data");
|
|
214
214
|
});
|
|
215
215
|
|
|
@@ -40,7 +40,7 @@ describe("Onion middleware composition", () => {
|
|
|
40
40
|
return { done: true };
|
|
41
41
|
});
|
|
42
42
|
|
|
43
|
-
await fn({} as any
|
|
43
|
+
await fn({} as any, {});
|
|
44
44
|
|
|
45
45
|
// Onion order: outer wraps inner wraps handler
|
|
46
46
|
expect(order).toEqual([
|
|
@@ -71,7 +71,7 @@ describe("Onion middleware composition", () => {
|
|
|
71
71
|
throw new Error("handler exploded");
|
|
72
72
|
});
|
|
73
73
|
|
|
74
|
-
await expect(fn({} as any
|
|
74
|
+
await expect(fn({} as any, {})).rejects.toThrow("handler exploded");
|
|
75
75
|
|
|
76
76
|
// The middleware should have intercepted the error
|
|
77
77
|
expect(caughtError).toBe("handler exploded");
|
|
@@ -95,7 +95,7 @@ describe("Onion middleware composition", () => {
|
|
|
95
95
|
return { done: true };
|
|
96
96
|
});
|
|
97
97
|
|
|
98
|
-
await fn({} as any
|
|
98
|
+
await fn({} as any, {});
|
|
99
99
|
|
|
100
100
|
// next() should block until the handler completes,
|
|
101
101
|
// so measured duration should include the 50ms sleep
|
|
@@ -122,7 +122,7 @@ describe("Onion middleware composition", () => {
|
|
|
122
122
|
};
|
|
123
123
|
});
|
|
124
124
|
|
|
125
|
-
const result = await fn({} as any
|
|
125
|
+
const result = await fn({} as any, {});
|
|
126
126
|
expect(result).toEqual({ user: "alice", timestamp: 12345 });
|
|
127
127
|
});
|
|
128
128
|
});
|
package/src/types.ts
CHANGED
|
@@ -70,8 +70,9 @@ export type CallableBuilder<
|
|
|
70
70
|
THandlerReturn,
|
|
71
71
|
> = {
|
|
72
72
|
(
|
|
73
|
-
context: TCurrentContext
|
|
74
|
-
|
|
73
|
+
context: TCurrentContext,
|
|
74
|
+
args: InferredArgs<TArgsValidator>
|
|
75
|
+
): Promise<THandlerReturn>;
|
|
75
76
|
};
|
|
76
77
|
export interface ConvexBuilderDef<
|
|
77
78
|
TFunctionType extends FunctionType | undefined = undefined,
|
|
@@ -201,8 +201,9 @@ describe("WithZod Callable Builder", () => {
|
|
|
201
201
|
// Should be callable
|
|
202
202
|
assertType<
|
|
203
203
|
(
|
|
204
|
-
context: any
|
|
205
|
-
|
|
204
|
+
context: any,
|
|
205
|
+
args: { count: number }
|
|
206
|
+
) => Promise<{ count: number }>
|
|
206
207
|
>(callableQuery);
|
|
207
208
|
});
|
|
208
209
|
});
|
package/src/zod/withZod.test.ts
CHANGED
|
@@ -83,7 +83,7 @@ describe("WithZod plugin", () => {
|
|
|
83
83
|
return { result: input.count * 2 };
|
|
84
84
|
});
|
|
85
85
|
|
|
86
|
-
const result = await fn({} as any
|
|
86
|
+
const result = await fn({} as any, { count: 5 });
|
|
87
87
|
expect(result).toEqual({ result: 10 });
|
|
88
88
|
});
|
|
89
89
|
|
|
@@ -96,7 +96,7 @@ describe("WithZod plugin", () => {
|
|
|
96
96
|
return { numbers: [1, 2, 3] };
|
|
97
97
|
});
|
|
98
98
|
|
|
99
|
-
const result = await fn({} as any
|
|
99
|
+
const result = await fn({} as any, {});
|
|
100
100
|
expect(result).toEqual({ numbers: [1, 2, 3] });
|
|
101
101
|
});
|
|
102
102
|
|
|
@@ -110,11 +110,11 @@ describe("WithZod plugin", () => {
|
|
|
110
110
|
});
|
|
111
111
|
|
|
112
112
|
// Valid input should pass
|
|
113
|
-
const result = await fn({} as any
|
|
113
|
+
const result = await fn({} as any, { count: 5 });
|
|
114
114
|
expect(result).toBe(5);
|
|
115
115
|
|
|
116
116
|
// Invalid input should throw
|
|
117
|
-
await expect(fn({} as any
|
|
117
|
+
await expect(fn({} as any, { count: -1 })).rejects.toThrow();
|
|
118
118
|
});
|
|
119
119
|
|
|
120
120
|
it("should validate Zod returns refinements at runtime", async () => {
|
|
@@ -126,7 +126,7 @@ describe("WithZod plugin", () => {
|
|
|
126
126
|
return -1; // This should fail validation
|
|
127
127
|
});
|
|
128
128
|
|
|
129
|
-
await expect(fn({} as any
|
|
129
|
+
await expect(fn({} as any, {})).rejects.toThrow();
|
|
130
130
|
});
|
|
131
131
|
|
|
132
132
|
it("should work with plain Convex validators when using WithZod", async () => {
|
|
@@ -138,7 +138,7 @@ describe("WithZod plugin", () => {
|
|
|
138
138
|
return input.count;
|
|
139
139
|
});
|
|
140
140
|
|
|
141
|
-
const result = await fn({} as any
|
|
141
|
+
const result = await fn({} as any, { count: 42 });
|
|
142
142
|
expect(result).toBe(42);
|
|
143
143
|
});
|
|
144
144
|
|
|
@@ -157,7 +157,7 @@ describe("WithZod plugin", () => {
|
|
|
157
157
|
return `${input.name}-${ctx.extra}`;
|
|
158
158
|
});
|
|
159
159
|
|
|
160
|
-
const result = await fn({} as any
|
|
160
|
+
const result = await fn({} as any, { name: "test" });
|
|
161
161
|
expect(result).toBe("test-data");
|
|
162
162
|
});
|
|
163
163
|
|
|
@@ -175,6 +175,6 @@ describe("WithZod plugin", () => {
|
|
|
175
175
|
return input.name;
|
|
176
176
|
});
|
|
177
177
|
|
|
178
|
-
await expect(fn({} as any
|
|
178
|
+
await expect(fn({} as any, { name: "" })).rejects.toThrow();
|
|
179
179
|
});
|
|
180
180
|
});
|