fluent-convex 0.12.2 → 0.13.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/README.md CHANGED
@@ -2,19 +2,19 @@
2
2
 
3
3
  # Fluent Convex
4
4
 
5
- A fluent API builder for Convex functions with middleware support, inspired by [oRPC](https://orpc.unnoq.com/).
5
+ A fluent builder for Convex functions with composable middleware, reusable chains, and plugin support. Inspired by [oRPC](https://orpc.unnoq.com/).
6
6
 
7
- **[Live Docs & Interactive Showcase](https://friendly-zebra-716.convex.site)** -- see every feature in action with live demos and real source code.
7
+ **[Live Docs & Interactive Showcase](https://friendly-zebra-716.convex.site)** every feature with live demos and real source code.
8
+
9
+ [![Is this the best way to write Convex? Does anyone care?](https://thumbs.video-to-markdown.com/e943cc42.jpg)](https://youtu.be/oFqWtLBSgk8)
8
10
 
9
11
  ## Features
10
12
 
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
13
+ - **Composable middleware** authentication, logging, error handling with onion-style composition ([docs](https://friendly-zebra-716.convex.site/middleware))
14
+ - **Reusable chains** define logic once, call it directly from other handlers (no extra function invocation), register it multiple ways ([docs](https://friendly-zebra-716.convex.site/reusable-chains))
15
+ - **Fluent API** clean chainable syntax for queries, mutations, and actions ([docs](https://friendly-zebra-716.convex.site/basics))
16
+ - **Full type inference** middleware context flows through the entire chain
17
+ - **Plugin system** extend with plugins like `fluent-convex/zod` for Zod schema validation ([docs](https://friendly-zebra-716.convex.site/zod-plugin))
18
18
 
19
19
  ## Installation
20
20
 
@@ -26,8 +26,6 @@ npm install fluent-convex
26
26
 
27
27
  > For a complete walkthrough with live demos, see the **[Getting Started guide](https://friendly-zebra-716.convex.site/)**.
28
28
 
29
- **Important:** All functions must end with `.public()` or `.internal()` to be registered with Convex.
30
-
31
29
  ```ts
32
30
  import { createBuilder } from "fluent-convex";
33
31
  import { v } from "convex/values";
@@ -35,85 +33,83 @@ import type { DataModel } from "./_generated/dataModel";
35
33
 
36
34
  const convex = createBuilder<DataModel>();
37
35
 
38
- // Simple query
39
36
  export const listNumbers = convex
40
37
  .query()
41
38
  .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) };
39
+ .handler(async (ctx, args) => {
40
+ const rows = await ctx.db.query("numbers").order("desc").take(args.count);
41
+ return rows.map((r) => r.value);
49
42
  })
50
- .public(); // Must end with .public() or .internal()
43
+ .public();
44
+ ```
51
45
 
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
- }
46
+ Everything starts with `createBuilder<DataModel>()`. From there, chain `.query()` / `.mutation()` / `.action()`, add `.input()` and `.handler()`, and finalize with `.public()` or `.internal()` to register the function with Convex.
47
+
48
+ ## Middleware
49
+
50
+ > See the **[Middleware docs](https://friendly-zebra-716.convex.site/middleware)** for detailed examples.
58
51
 
52
+ Middleware wraps your handlers with reusable logic. It uses an onion model: `next()` executes the rest of the chain including the handler, so middleware can run code before and after.
53
+
54
+ ```ts
55
+ const authMiddleware = convex.query().createMiddleware(async (ctx, next) => {
56
+ const identity = await ctx.auth.getUserIdentity();
57
+ if (!identity) throw new Error("Unauthorized");
59
58
  return next({
60
- ...context,
61
- user: {
62
- id: identity.subject,
63
- name: identity.name ?? "Unknown",
64
- },
59
+ ...ctx,
60
+ user: { id: identity.subject, name: identity.name ?? "Unknown" },
65
61
  });
66
62
  });
67
63
 
68
- export const listNumbersAuth = convex
64
+ export const listNumbersProtected = convex
69
65
  .query()
70
66
  .use(authMiddleware)
71
67
  .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
- };
68
+ .handler(async (ctx, args) => {
69
+ const rows = await ctx.db.query("numbers").order("desc").take(args.count);
70
+ return { viewer: ctx.user.name, numbers: rows.map((r) => r.value) };
82
71
  })
83
72
  .public();
84
73
  ```
85
74
 
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.
75
+ ### Cross-function-type middleware
89
76
 
90
- fluent-convex supports three flavors of input validation through the same `.input()` API:
77
+ Because `authMiddleware` above was created with `.query().createMiddleware()`, its input context is `QueryCtx` so it can't be used on mutations or actions (`ActionCtx` lacks `db`, causing a type error). For middleware that only needs properties shared across all function types (like `auth`), use `$context` to declare exactly what the middleware requires:
91
78
 
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.
79
+ ```ts
80
+ import type { Auth } from "convex/server";
81
+
82
+ // Works with queries, mutations, AND actions
83
+ const authMiddleware = convex
84
+ .$context<{ auth: Auth }>()
85
+ .createMiddleware(async (ctx, next) => {
86
+ const identity = await ctx.auth.getUserIdentity();
87
+ if (!identity) throw new Error("Unauthorized");
88
+ return next({
89
+ ...ctx,
90
+ user: { id: identity.subject, name: identity.name ?? "Unknown" },
91
+ });
92
+ });
99
93
 
100
- There are two main middleware patterns:
94
+ const authQuery = convex.query().use(authMiddleware);
95
+ const authMutation = convex.mutation().use(authMiddleware);
96
+ const authAction = convex.action().use(authMiddleware);
97
+ ```
101
98
 
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)
99
+ | Pattern | Input context | Use when |
100
+ | -------------------------------------------------------- | ------------------------ | ------------------------------------------------ |
101
+ | `convex.query().createMiddleware(fn)` | `QueryCtx` (has `db`) | Middleware that reads the database |
102
+ | `convex.createMiddleware(fn)` | `EmptyObject` | Middleware that needs no context at all |
103
+ | `convex.$context<{ auth: Auth }>().createMiddleware(fn)` | Exactly `{ auth: Auth }` | Middleware that needs specific shared properties |
104
104
 
105
105
  ## Reusable Chains & Callables
106
106
 
107
107
  > See the **[Reusable Chains docs](https://friendly-zebra-716.convex.site/reusable-chains)** for full examples with live demos.
108
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
109
+ Because the builder is immutable, you can stop the chain at any point and reuse it. A builder with a `.handler()` but no `.public()` / `.internal()` is a **callable** a fully-typed function you can invoke directly from other handlers without an extra Convex function call:
114
110
 
115
111
  ```ts
116
- // 1. Define a callable - NOT yet registered with Convex
112
+ // Define a callable not yet registered with Convex
117
113
  const getNumbers = convex
118
114
  .query()
119
115
  .input({ count: v.number() })
@@ -122,29 +118,35 @@ const getNumbers = convex
122
118
  return rows.map((r) => r.value);
123
119
  });
124
120
 
125
- // 2. Register it as a public query
121
+ // Register it as a public query
126
122
  export const listNumbers = getNumbers.public();
127
123
 
128
- // 3. Call it directly from inside another handler - no additional function invocation!
124
+ // Call it directly from another handler no extra function invocation
129
125
  export const getNumbersWithTimestamp = convex
130
126
  .query()
131
127
  .input({ count: v.number() })
132
128
  .handler(async (ctx, args) => {
133
- const numbers = await getNumbers(ctx, args); // <-- direct call
129
+ const numbers = await getNumbers(ctx, args);
134
130
  return { numbers, fetchedAt: Date.now() };
135
131
  })
136
132
  .public();
137
133
 
138
- // 4. Register the same callable with different middleware
134
+ // Register the same callable with different middleware
139
135
  export const listNumbersProtected = getNumbers.use(authMiddleware).public();
140
136
  export const listNumbersLogged = getNumbers.use(withLogging("logged")).public();
141
137
  ```
142
138
 
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.
139
+ ## Validation
140
+
141
+ > See the **[Validation docs](https://friendly-zebra-716.convex.site/validation)** for a side-by-side comparison with live demos.
144
142
 
145
- ## Plugins
143
+ `.input()` supports three flavors of validation:
146
144
 
147
- ### Zod Plugin (`fluent-convex/zod`)
145
+ 1. **Property validators** — `{ count: v.number() }` (simplest)
146
+ 2. **Object validators** — `v.object({ count: v.number() })` (with `.returns()` support)
147
+ 3. **Zod schemas** — `z.object({ count: z.number().min(1) })` (via the Zod plugin)
148
+
149
+ ## Zod Plugin (`fluent-convex/zod`)
148
150
 
149
151
  > See the **[Zod Plugin docs](https://friendly-zebra-716.convex.site/zod-plugin)** for live demos including refinement validation.
150
152
 
@@ -154,76 +156,46 @@ The Zod plugin adds Zod schema support for `.input()` and `.returns()`, with **f
154
156
  npm install zod convex-helpers
155
157
  ```
156
158
 
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:
159
+ > `zod` and `convex-helpers` are optional peer dependencies only needed if you use this plugin.
160
160
 
161
161
  ```ts
162
- import { createBuilder } from "fluent-convex";
163
162
  import { WithZod } from "fluent-convex/zod";
164
163
  import { z } from "zod";
165
- import type { DataModel } from "./_generated/dataModel";
166
-
167
- const convex = createBuilder<DataModel>();
168
164
 
169
165
  export const listNumbers = convex
170
166
  .query()
171
- .extend(WithZod) // Enable Zod support
167
+ .extend(WithZod)
172
168
  .input(
173
169
  z.object({
174
- count: z.number().int().min(1).max(100), // Refinements enforced at runtime!
175
- })
170
+ count: z.number().int().min(1).max(100),
171
+ }),
176
172
  )
177
173
  .returns(z.object({ numbers: z.array(z.number()) }))
178
- .handler(async (context, input) => {
179
- const numbers = await context.db.query("numbers").take(input.count);
174
+ .handler(async (ctx, args) => {
175
+ const numbers = await ctx.db.query("numbers").take(args.count);
180
176
  return { numbers: numbers.map((n) => n.value) };
181
177
  })
182
178
  .public();
183
179
  ```
184
180
 
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.
181
+ - **Full runtime validation** — Zod refinements are enforced server-side, before and after the handler
182
+ - **Structural conversion** Zod schemas are automatically converted to Convex validators
183
+ - **Composable** `.extend(WithZod)` preserves through `.use()`, `.input()`, and `.returns()` chains
184
+ - **Plain validators still work** mix Zod and Convex validators in the same builder chain
190
185
 
191
- ## Extensibility
186
+ ## Custom Plugins
192
187
 
193
188
  > See the **[Custom Plugins docs](https://friendly-zebra-716.convex.site/custom-plugins)** for a complete worked example with live demo.
194
189
 
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.
190
+ Extend the builder by subclassing `ConvexBuilderWithFunctionKind` and overriding `_clone()`. This preserves your plugin's type through the entire builder chain:
200
191
 
201
192
  ```ts
202
193
  import {
203
194
  ConvexBuilderWithFunctionKind,
204
- type GenericDataModel,
205
- type FunctionType,
206
- type Context,
207
- type EmptyObject,
208
- type ConvexArgsValidator,
209
- type ConvexReturnsValidator,
210
195
  type ConvexBuilderDef,
211
196
  } from "fluent-convex";
212
197
 
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())
198
+ class MyPlugin extends ConvexBuilderWithFunctionKind {
227
199
  constructor(builderOrDef: any) {
228
200
  const def =
229
201
  builderOrDef instanceof ConvexBuilderWithFunctionKind
@@ -232,12 +204,10 @@ class MyPlugin<
232
204
  super(def);
233
205
  }
234
206
 
235
- // Override _clone() to preserve MyPlugin through the chain
236
207
  protected _clone(def: ConvexBuilderDef<any, any, any>): any {
237
208
  return new MyPlugin(def);
238
209
  }
239
210
 
240
- // Add custom methods
241
211
  myCustomMethod(param: string) {
242
212
  console.log("Custom method called with:", param);
243
213
  return this;
@@ -245,175 +215,66 @@ class MyPlugin<
245
215
  }
246
216
  ```
247
217
 
248
- Usage:
218
+ Usage — plugins compose with `.extend()`:
249
219
 
250
220
  ```ts
251
221
  export const myQuery = convex
252
222
  .query()
253
223
  .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
224
+ .extend(WithZod)
270
225
  .myCustomMethod("hello")
271
226
  .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 };
227
+ .handler(async (ctx, args) => {
228
+ /* ... */
315
229
  })
316
230
  .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
231
  ```
326
232
 
327
- ### Method Ordering Rules
233
+ ## API Reference
328
234
 
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
235
+ | Method | Description |
236
+ | ---------------------------------------- | ---------------------------------------------------------- |
237
+ | `.query()` / `.mutation()` / `.action()` | Set the function type |
238
+ | `.input(validator)` | Set input validation |
239
+ | `.returns(validator)` | Set return type validation must come before `.handler()` |
240
+ | `.use(middleware)` | Apply middleware — can come before or after `.handler()` |
241
+ | `.handler(fn)` | Define the function handler |
242
+ | `.extend(plugin)` | Extend with a plugin class |
243
+ | `.createMiddleware(fn)` | Create a middleware function |
244
+ | `.public()` / `.internal()` | Register with Convex — must come after `.handler()` |
334
245
 
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
246
+ Before registration (`.public()` / `.internal()`), the builder is **callable**. After registration, it is not.
350
247
 
351
248
  ## Caveats
352
249
 
353
250
  ### Circular types when calling `api.*` in the same file
354
251
 
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
252
+ When a function calls other functions via `api.*` in the same file without explicit `.returns()` validators, TypeScript may report circular initializer errors (TS7022). This is a standard Convex/TypeScript limitation. Workarounds:
253
+
254
+ 1. Add `.returns()` to the called functions to give them explicit return types
357
255
  2. Move the calling function to a separate file
358
256
  3. Use `internal.*` from a different module
359
257
 
360
- ## Documentation
361
-
362
- The **[live docs site](https://friendly-zebra-716.convex.site)** is an interactive showcase that demonstrates every feature with working live demos. The code snippets shown on the docs site are the actual source code powering the app -- imported via Vite `?raw` imports, so what you see is what runs.
363
-
364
- Sections:
365
- - [Getting Started](https://friendly-zebra-716.convex.site/) -- builder setup and overview
366
- - [Basics](https://friendly-zebra-716.convex.site/basics) -- queries, mutations, and the fluent chain
367
- - [Validation](https://friendly-zebra-716.convex.site/validation) -- property validators, object validators, and Zod schemas
368
- - [Middleware](https://friendly-zebra-716.convex.site/middleware) -- context-enrichment and onion middleware
369
- - [Reusable Chains](https://friendly-zebra-716.convex.site/reusable-chains) -- callable syntax and composability
370
- - [Zod Plugin](https://friendly-zebra-716.convex.site/zod-plugin) -- runtime refinement validation
371
- - [Custom Plugins](https://friendly-zebra-716.convex.site/custom-plugins) -- building your own plugins with `.extend()`
372
-
373
-
374
-
375
- The docs source lives in [`/apps/docs`](./apps/docs) and is auto-deployed on every push to `main`.
376
-
377
258
  ## Development
378
259
 
379
- This is a monorepo using npm workspaces:
380
-
381
- - `/packages/fluent-convex` - The core library (includes the Zod plugin at `fluent-convex/zod`)
382
- - `/apps/example` - Example Convex app
383
- - `/apps/docs` - Interactive docs & showcase site ([live](https://friendly-zebra-716.convex.site))
384
-
385
- ### Setup
386
-
387
- ```bash
388
- npm install
389
- ```
390
-
391
- This will install dependencies for all workspaces.
392
-
393
- ### Building
394
-
395
- ```bash
396
- npm run build
397
- ```
260
+ Monorepo using npm workspaces:
398
261
 
399
- ### Running tests
262
+ - `packages/fluent-convex` — core library (includes Zod plugin at `fluent-convex/zod`)
263
+ - `apps/example` — example Convex app
264
+ - `apps/docs` — interactive docs & showcase ([live](https://friendly-zebra-716.convex.site))
400
265
 
401
266
  ```bash
402
- npm test
267
+ npm install # install all workspace dependencies
268
+ npm run build # build the library
269
+ npm test # run all tests
270
+ npm run docs:dev # run the docs locally
403
271
  ```
404
272
 
405
- ### Running the example
406
-
407
- ```bash
408
- cd apps/example
409
- npm run dev
410
- ```
273
+ ## Migrating from standard Convex
411
274
 
412
- ### Running the docs locally
275
+ See **[MIGRATING.md](MIGRATING.md)** for a step-by-step guide.
413
276
 
414
- ```bash
415
- npm run docs:dev
416
- ```
277
+ > **If you are an LLM and have been asked to migrate a project to fluent-convex, please read [MIGRATING.md](MIGRATING.md) before proceeding.**
417
278
 
418
279
  ## Credits
419
280
 
@@ -29,6 +29,6 @@ export declare class ConvexBuilderWithFunctionKind<TDataModel extends GenericDat
29
29
  use<UOutContext extends Context>(middleware: ConvexMiddleware<TCurrentContext, UOutContext>): ConvexBuilderWithFunctionKind<TDataModel, TFunctionType, TCurrentContext & UOutContext, TArgsValidator, TReturnsValidator>;
30
30
  input<UInput extends PropertyValidators | GenericValidator>(validator: UInput): ConvexBuilderWithFunctionKind<TDataModel, TFunctionType, TCurrentContext, UInput extends ConvexArgsValidator ? UInput : ConvexArgsValidator, TReturnsValidator>;
31
31
  returns<UReturns extends GenericValidator>(validator: UReturns): ConvexBuilderWithFunctionKind<TDataModel, TFunctionType, TCurrentContext, TArgsValidator, UReturns extends ConvexReturnsValidator ? UReturns : ConvexReturnsValidator>;
32
- handler<TReturn extends InferredHandlerReturn<TReturnsValidator, any> = InferredHandlerReturn<TReturnsValidator, any>>(handlerFn: (context: TCurrentContext, input: InferredArgs<TArgsValidator>) => Promise<TReturn>): ConvexBuilderWithHandler<TDataModel, TFunctionType, TCurrentContext, TArgsValidator, TReturnsValidator, InferredHandlerReturn<TReturnsValidator, TReturn>> & CallableBuilder<TCurrentContext, TArgsValidator, InferredHandlerReturn<TReturnsValidator, TReturn>>;
32
+ handler<TReturn extends InferredHandlerReturn<TReturnsValidator, any> = InferredHandlerReturn<TReturnsValidator, any>, THandlerFn extends (context: TCurrentContext, input: InferredArgs<TArgsValidator>) => Promise<TReturn> = (context: TCurrentContext, input: InferredArgs<TArgsValidator>) => Promise<TReturn>>(handlerFn: THandlerFn & ((context: TCurrentContext, input: InferredArgs<TArgsValidator>) => Promise<TReturn>)): ConvexBuilderWithHandler<TDataModel, TFunctionType, TCurrentContext, TArgsValidator, TReturnsValidator, InferredHandlerReturn<TReturnsValidator, TReturn>> & THandlerFn & CallableBuilder<TCurrentContext, TArgsValidator, InferredHandlerReturn<TReturnsValidator, TReturn>>;
33
33
  }
34
34
  //# sourceMappingURL=ConvexBuilderWithFunctionKind.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"ConvexBuilderWithFunctionKind.d.ts","sourceRoot":"","sources":["../src/ConvexBuilderWithFunctionKind.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"ConvexBuilderWithFunctionKind.d.ts","sourceRoot":"","sources":["../src/ConvexBuilderWithFunctionKind.ts"],"names":[],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"ConvexBuilderWithFunctionKind.js","sourceRoot":"","sources":["../src/ConvexBuilderWithFunctionKind.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,wBAAwB,EAAE,MAAM,4BAA4B,CAAC;AAYtE,OAAO,EAAE,MAAM,IAAI,aAAa,EAAE,MAAM,UAAU,CAAC;AAEnD,MAAM,OAAO,6BAA6B;IAO9B,GAAG,CAIX;IAEF,YACE,GAAuE;QAEvE,IAAI,CAAC,GAAG,GAAG,GAAG,CAAC;IACjB,CAAC;IAED;;;;OAIG;IACO,MAAM,CAAC,GAAoC;QACnD,OAAO,IAAI,6BAA6B,CAAC,GAAG,CAAC,CAAC;IAChD,CAAC;IAID,MAAM,CACJ,OAAwE;QAExE,OAAO,aAAa,CAAC,IAAI,EAAE,OAAO,CAAC,CAAC;IACtC,CAAC;IAED,QAAQ;QAKN,OAAO;YACL,gBAAgB,CACd,UAA4C;gBAE5C,OAAO,UAAU,CAAC;YACpB,CAAC;SACF,CAAC;IACJ,CAAC;IAYD,gBAAgB,CACd,UAAqD;QAErD,OAAO,UAAU,CAAC;IACpB,CAAC;IAED,GAAG,CACD,UAA0D;QAQ1D,OAAO,IAAI,CAAC,MAAM,CAAC;YACjB,GAAG,IAAI,CAAC,GAAG;YACX,WAAW,EAAE,CAAC,GAAG,IAAI,CAAC,GAAG,CAAC,WAAW,EAAE,UAAiC,CAAC;SAC1E,CAAC,CAAC;IACL,CAAC;IAED,KAAK,CACH,SAAiB;QAQjB,OAAO,IAAI,CAAC,MAAM,CAAC;YACjB,GAAG,IAAI,CAAC,GAAG;YACX,aAAa,EAAE,SAAS;SACzB,CAAC,CAAC;IACL,CAAC;IAED,OAAO,CACL,SAAmB;QAQnB,OAAO,IAAI,CAAC,MAAM,CAAC;YACjB,GAAG,IAAI,CAAC,GAAG;YACX,gBAAgB,EAAE,SAAS;SAC5B,CAAC,CAAC;IACL,CAAC;IAED,OAAO,CAML,SAGqB;QAcrB,IAAI,IAAI,CAAC,GAAG,CAAC,OAAO,EAAE,CAAC;YACrB,MAAM,IAAI,KAAK,CACb,0EAA0E,CAC3E,CAAC;QACJ,CAAC;QAED,4EAA4E;QAC5E,mFAAmF;QACnF,oFAAoF;QACpF,MAAM,UAAU,GAAG,KAAK,EACtB,cAAuB,EACvB,QAAsC,EACtC,EAAE;YACF,OAAO,SAAS,CAAC,cAAiC,EAAE,QAAQ,CAAC,CAAC;QAChE,CAAC,CAAC;QAIF,OAAO,IAAI,wBAAwB,CAOjC;YACA,GAAG,IAAI,CAAC,GAAG;YACX,OAAO,EAAE,UAAiB;SAC3B,CAQiE,CAAC;IACrE,CAAC;CACF"}
1
+ {"version":3,"file":"ConvexBuilderWithFunctionKind.js","sourceRoot":"","sources":["../src/ConvexBuilderWithFunctionKind.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,wBAAwB,EAAE,MAAM,4BAA4B,CAAC;AAYtE,OAAO,EAAE,MAAM,IAAI,aAAa,EAAE,MAAM,UAAU,CAAC;AAEnD,MAAM,OAAO,6BAA6B;IAO9B,GAAG,CAIX;IAEF,YACE,GAAuE;QAEvE,IAAI,CAAC,GAAG,GAAG,GAAG,CAAC;IACjB,CAAC;IAED;;;;OAIG;IACO,MAAM,CAAC,GAAoC;QACnD,OAAO,IAAI,6BAA6B,CAAC,GAAG,CAAC,CAAC;IAChD,CAAC;IAID,MAAM,CACJ,OAAwE;QAExE,OAAO,aAAa,CAAC,IAAI,EAAE,OAAO,CAAC,CAAC;IACtC,CAAC;IAED,QAAQ;QAKN,OAAO;YACL,gBAAgB,CACd,UAA4C;gBAE5C,OAAO,UAAU,CAAC;YACpB,CAAC;SACF,CAAC;IACJ,CAAC;IAYD,gBAAgB,CACd,UAAqD;QAErD,OAAO,UAAU,CAAC;IACpB,CAAC;IAED,GAAG,CACD,UAA0D;QAQ1D,OAAO,IAAI,CAAC,MAAM,CAAC;YACjB,GAAG,IAAI,CAAC,GAAG;YACX,WAAW,EAAE,CAAC,GAAG,IAAI,CAAC,GAAG,CAAC,WAAW,EAAE,UAAiC,CAAC;SAC1E,CAAC,CAAC;IACL,CAAC;IAED,KAAK,CACH,SAAiB;QAQjB,OAAO,IAAI,CAAC,MAAM,CAAC;YACjB,GAAG,IAAI,CAAC,GAAG;YACX,aAAa,EAAE,SAAS;SACzB,CAAC,CAAC;IACL,CAAC;IAED,OAAO,CACL,SAAmB;QAQnB,OAAO,IAAI,CAAC,MAAM,CAAC;YACjB,GAAG,IAAI,CAAC,GAAG;YACX,gBAAgB,EAAE,SAAS;SAC5B,CAAC,CAAC;IACL,CAAC;IAED,OAAO,CAaL,SAIwB;QAexB,IAAI,IAAI,CAAC,GAAG,CAAC,OAAO,EAAE,CAAC;YACrB,MAAM,IAAI,KAAK,CACb,0EAA0E,CAC3E,CAAC;QACJ,CAAC;QAED,4EAA4E;QAC5E,mFAAmF;QACnF,oFAAoF;QACpF,MAAM,UAAU,GAAG,KAAK,EACtB,cAAuB,EACvB,QAAsC,EACtC,EAAE;YACF,OAAO,SAAS,CAAC,cAAiC,EAAE,QAAQ,CAAC,CAAC;QAChE,CAAC,CAAC;QAIF,OAAO,IAAI,wBAAwB,CAOjC;YACA,GAAG,IAAI,CAAC,GAAG;YACX,OAAO,EAAE,UAAiB;SAC3B,CASiE,CAAC;IACrE,CAAC;CACF"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "fluent-convex",
3
- "version": "0.12.2",
3
+ "version": "0.13.0",
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",
@@ -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"
@@ -130,11 +130,19 @@ export class ConvexBuilderWithFunctionKind<
130
130
  TReturnsValidator,
131
131
  any
132
132
  > = InferredHandlerReturn<TReturnsValidator, any>,
133
- >(
134
- handlerFn: (
133
+ THandlerFn extends (
134
+ context: TCurrentContext,
135
+ input: InferredArgs<TArgsValidator>
136
+ ) => Promise<TReturn> = (
135
137
  context: TCurrentContext,
136
138
  input: InferredArgs<TArgsValidator>
137
- ) => Promise<TReturn>
139
+ ) => Promise<TReturn>,
140
+ >(
141
+ handlerFn: THandlerFn &
142
+ ((
143
+ context: TCurrentContext,
144
+ input: InferredArgs<TArgsValidator>
145
+ ) => Promise<TReturn>)
138
146
  ): ConvexBuilderWithHandler<
139
147
  TDataModel,
140
148
  TFunctionType,
@@ -143,6 +151,7 @@ export class ConvexBuilderWithFunctionKind<
143
151
  TReturnsValidator,
144
152
  InferredHandlerReturn<TReturnsValidator, TReturn>
145
153
  > &
154
+ THandlerFn &
146
155
  CallableBuilder<
147
156
  TCurrentContext,
148
157
  TArgsValidator,
@@ -184,6 +193,7 @@ export class ConvexBuilderWithFunctionKind<
184
193
  TReturnsValidator,
185
194
  InferredReturn
186
195
  > &
196
+ THandlerFn &
187
197
  CallableBuilder<TCurrentContext, TArgsValidator, InferredReturn>;
188
198
  }
189
199
  }
@@ -443,6 +443,23 @@ describe("Builder Core", () => {
443
443
  >(callableQuery);
444
444
  });
445
445
 
446
+ it("should infer one-arg handlers with empty input objects", () => {
447
+ const callableQuery = convex
448
+ .query()
449
+ .input({})
450
+ .handler(async (context) => {
451
+ assertType(context.db);
452
+ return { success: Boolean(context.db) };
453
+ });
454
+
455
+ assertType<
456
+ (
457
+ context: any,
458
+ args: Record<never, never>
459
+ ) => Promise<{ success: boolean }>
460
+ >(callableQuery);
461
+ });
462
+
446
463
  it("should lose callability after .public()", () => {
447
464
  const callableQuery = convex
448
465
  .query()