@bb-labs/convex-cache 0.0.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.
Files changed (86) hide show
  1. package/README.md +342 -0
  2. package/dist/cli/fns/dev.d.ts +11 -0
  3. package/dist/cli/fns/dev.js +59 -0
  4. package/dist/cli/fns/fns/convex-runner.d.ts +26 -0
  5. package/dist/cli/fns/fns/convex-runner.js +71 -0
  6. package/dist/cli/fns/fns/generate-z-schema/fns/build-schema-map.d.ts +8 -0
  7. package/dist/cli/fns/fns/generate-z-schema/fns/build-schema-map.js +39 -0
  8. package/dist/cli/fns/fns/generate-z-schema/fns/load-convex.d.ts +7 -0
  9. package/dist/cli/fns/fns/generate-z-schema/fns/load-convex.js +49 -0
  10. package/dist/cli/fns/fns/generate-z-schema/generate.d.ts +7 -0
  11. package/dist/cli/fns/fns/generate-z-schema/generate.js +16 -0
  12. package/dist/cli/fns/fns/generate-z-schema/utils/build-schema-file.d.ts +13 -0
  13. package/dist/cli/fns/fns/generate-z-schema/utils/build-schema-file.js +42 -0
  14. package/dist/cli/fns/fns/generate-z-schema/utils/extract-schema.d.ts +3 -0
  15. package/dist/cli/fns/fns/generate-z-schema/utils/extract-schema.js +84 -0
  16. package/dist/cli/fns/fns/generate-z-schema/utils/find-fn-ref.d.ts +5 -0
  17. package/dist/cli/fns/fns/generate-z-schema/utils/find-fn-ref.js +27 -0
  18. package/dist/cli/index.d.ts +2 -0
  19. package/dist/cli/index.js +25 -0
  20. package/dist/cli/lib/convex-config.d.ts +21 -0
  21. package/dist/cli/lib/convex-config.js +37 -0
  22. package/dist/cli/lib/dir-watcher.d.ts +49 -0
  23. package/dist/cli/lib/dir-watcher.js +97 -0
  24. package/dist/cli/lib/package-cmds.d.ts +10 -0
  25. package/dist/cli/lib/package-cmds.js +17 -0
  26. package/dist/cli/lib/resolve-bun.d.ts +4 -0
  27. package/dist/cli/lib/resolve-bun.js +24 -0
  28. package/dist/cli/lib/shell-runner.d.ts +61 -0
  29. package/dist/cli/lib/shell-runner.js +133 -0
  30. package/dist/convex-cache/adapters/next/fns/build-preloader.d.ts +5 -0
  31. package/dist/convex-cache/adapters/next/fns/build-preloader.js +6 -0
  32. package/dist/convex-cache/adapters/next/fns/preload-query.d.ts +12 -0
  33. package/dist/convex-cache/adapters/next/fns/preload-query.js +27 -0
  34. package/dist/convex-cache/adapters/next/hooks/hooks.d.ts +8 -0
  35. package/dist/convex-cache/adapters/next/hooks/hooks.js +9 -0
  36. package/dist/convex-cache/adapters/next/index.d.ts +1 -0
  37. package/dist/convex-cache/adapters/next/index.js +1 -0
  38. package/dist/convex-cache/adapters/next/lib/revalidate-query-cache.d.ts +13 -0
  39. package/dist/convex-cache/adapters/next/lib/revalidate-query-cache.js +21 -0
  40. package/dist/convex-cache/adapters/next/server-fns/preload-query.d.ts +16 -0
  41. package/dist/convex-cache/adapters/next/server-fns/preload-query.js +32 -0
  42. package/dist/convex-cache/adapters/next/server-fns/revalidate-cache.d.ts +3 -0
  43. package/dist/convex-cache/adapters/next/server-fns/revalidate-cache.js +6 -0
  44. package/dist/convex-cache/adapters/next/server.d.ts +2 -0
  45. package/dist/convex-cache/adapters/next/server.js +2 -0
  46. package/dist/convex-cache/adapters/next/types/preloaded.d.ts +8 -0
  47. package/dist/convex-cache/adapters/next/types/preloaded.js +1 -0
  48. package/dist/convex-cache/adapters/next/utils/cache-profile.d.ts +1 -0
  49. package/dist/convex-cache/adapters/next/utils/cache-profile.js +1 -0
  50. package/dist/convex-cache/adapters/react/hooks/hooks.d.ts +8 -0
  51. package/dist/convex-cache/adapters/react/hooks/hooks.js +12 -0
  52. package/dist/convex-cache/adapters/react/index.d.ts +2 -0
  53. package/dist/convex-cache/adapters/react/index.js +2 -0
  54. package/dist/convex-cache/adapters/react/provider/provider.d.ts +13 -0
  55. package/dist/convex-cache/adapters/react/provider/provider.js +16 -0
  56. package/dist/convex-cache/core/client-cache/helpers/hooks/use-client-cache.d.ts +10 -0
  57. package/dist/convex-cache/core/client-cache/helpers/hooks/use-client-cache.js +16 -0
  58. package/dist/convex-cache/core/client-cache/helpers/hooks/use-query-key.d.ts +7 -0
  59. package/dist/convex-cache/core/client-cache/helpers/hooks/use-query-key.js +5 -0
  60. package/dist/convex-cache/core/client-cache/queries/paginated-query.d.ts +13 -0
  61. package/dist/convex-cache/core/client-cache/queries/paginated-query.js +37 -0
  62. package/dist/convex-cache/core/client-cache/queries/query.d.ts +10 -0
  63. package/dist/convex-cache/core/client-cache/queries/query.js +23 -0
  64. package/dist/convex-cache/core/helpers/utils/convert-paginated-schema.d.ts +13 -0
  65. package/dist/convex-cache/core/helpers/utils/convert-paginated-schema.js +45 -0
  66. package/dist/convex-cache/core/helpers/utils/fetch-schema-from-map.d.ts +21 -0
  67. package/dist/convex-cache/core/helpers/utils/fetch-schema-from-map.js +16 -0
  68. package/dist/convex-cache/core/helpers/utils/query-key.d.ts +10 -0
  69. package/dist/convex-cache/core/helpers/utils/query-key.js +16 -0
  70. package/dist/convex-cache/core/server-cache/queries/paginated-query.d.ts +15 -0
  71. package/dist/convex-cache/core/server-cache/queries/paginated-query.js +16 -0
  72. package/dist/convex-cache/core/server-cache/queries/query.d.ts +12 -0
  73. package/dist/convex-cache/core/server-cache/queries/query.js +16 -0
  74. package/dist/convex-cache/core/types/index.d.ts +2 -0
  75. package/dist/convex-cache/core/types/index.js +2 -0
  76. package/dist/convex-cache/core/types/types/paginated-query.d.ts +8 -0
  77. package/dist/convex-cache/core/types/types/paginated-query.js +2 -0
  78. package/dist/convex-cache/core/types/types/query.d.ts +5 -0
  79. package/dist/convex-cache/core/types/types/query.js +1 -0
  80. package/dist/convex-cache/index.d.ts +1 -0
  81. package/dist/convex-cache/index.js +1 -0
  82. package/dist/convex-cache/types/schema-map.d.ts +4 -0
  83. package/dist/convex-cache/types/schema-map.js +1 -0
  84. package/dist/validation/index.d.ts +10 -0
  85. package/dist/validation/index.js +83 -0
  86. package/package.json +58 -0
package/README.md ADDED
@@ -0,0 +1,342 @@
1
+ ## Introduction
2
+
3
+ A library for caching Convex queries on the client and server, providing supercharged performance for your Convex-powered applications.
4
+
5
+ ## Features
6
+
7
+ - 🚀 **Client-side caching** - Cache query results in the browser using IndexedDb
8
+ - ⚡ **Server-side caching** - Cache query results in server components
9
+ - 🔄 **Automatic revalidation** - Smart cache invalidation and revalidation strategies
10
+ - 📄 **Paginated query support** - Built-in support for paginated queries
11
+ - ✅ **Type-safe** - Full TypeScript support with Zod schema validation
12
+ - 🛠️ **CLI tool** - Development tooling for generating schemas from Convex functions
13
+ - ⚛️ **React & Next.js adapters** - Easy integration with React and Next.js applications
14
+ - 🛠️ **Composable** - Easy extension for other frameworks not currently supported using adapters
15
+
16
+ ## Installation
17
+
18
+ ```bash
19
+ npm install convex-cache
20
+ # or
21
+ bun add convex-cache
22
+ # or
23
+ yarn add convex-cache
24
+ ```
25
+
26
+ ## How it works
27
+
28
+ `convex-cache` provides two caching strategies: client-side caching for instant page loads and server-side caching for optimized server rendering. Both strategies automatically revalidate when data changes on Convex. Devs can use either one or both simultaneously depending on their project requirements.
29
+
30
+ ### Client Cache
31
+
32
+ The client cache stores query results in IndexedDb, enabling instant page loads. When a component requests data, the cache is checked first. If found, cached data is returned immediately while a background revalidation ensures freshness. On cache miss, data is fetched from Convex, validated against schemas, stored in IndexedDb, and returned to the component.
33
+
34
+ <a aria-label="Client Cache Flowchart" href="https://github.com/bigbang-sdk/convex-cache">
35
+ <picture>
36
+ <source srcset="https://raw.githubusercontent.com/bigbang-sdk/convex-cache/refs/heads/main/assets/flowchart/client-cache-dark.png" media="(prefers-color-scheme: dark)" />
37
+ <source srcset="https://raw.githubusercontent.com/bigbang-sdk/convex-cache/refs/heads/main/assets/flowchart/client-cache-light.png" media="(prefers-color-scheme: light)" />
38
+ <img src="https://raw.githubusercontent.com/bigbang-sdk/convex-cache/refs/heads/main/assets/flowchart/client-cache-light.png" alt="Client Cache Flowchart" referrerpolicy="no-referrer-when-downgrade" />
39
+ </picture>
40
+ </a>
41
+
42
+ **Flow:**
43
+
44
+ 1. User (in Singapore) requests `example.com` from Next.js
45
+ 2. Next.js serves the page (without data) from the nearest edge location (Singapore - very fast)
46
+ 3. Page requests and receives the data from local DB (ultra fast - on the same device)
47
+ 4. Page connects to Convex (USA)
48
+ 5. Convex streams changes
49
+ 6. `convex-cache` automatically updates the cache in local DB
50
+
51
+ **Note:**
52
+
53
+ 1. If a user isn’t connected to Convex when the data for a query changes, their next visit will show stale data (from their local DB). However, it is immediately revalidated once the page connects to Convex.
54
+
55
+ ### Server Cache
56
+
57
+ ### Next.js
58
+
59
+ The server cache (with Next.js) leverages Next.js's native caching system, integrating seamlessly with Cache Components and Partial Pre-Rendering (PPR). Queries are preloaded in server components and cached at the Next.js level. When data changes on Convex, the Next.js cache is automatically revalidated, ensuring pages always serve fresh data.
60
+
61
+ <a aria-label="Server Cache Flowchart" href="https://github.com/bigbang-sdk/convex-cache">
62
+ <picture>
63
+ <source srcset="https://raw.githubusercontent.com/bigbang-sdk/convex-cache/refs/heads/main/assets/flowchart/server-cache-dark.png" media="(prefers-color-scheme: dark)" />
64
+ <source srcset="https://raw.githubusercontent.com/bigbang-sdk/convex-cache/refs/heads/main/assets/flowchart/server-cache-light.png" media="(prefers-color-scheme: light)" />
65
+ <img src="https://raw.githubusercontent.com/bigbang-sdk/convex-cache/refs/heads/main/assets/flowchart/server-cache-light.png" alt="Server Cache Flowchart" referrerpolicy="no-referrer-when-downgrade" />
66
+ </picture>
67
+ </a>
68
+
69
+ **Flow:**
70
+
71
+ 1. User (in Singapore) requests `example.com` from Next.js
72
+ 2. Next.js serves the page (with cached data) from the nearest edge location (Singapore - very fast)
73
+ 3. Page connects to Convex (USA)
74
+ 4. Convex streams changes
75
+ 5. `convex-cache` automatically revalidates the cache in Next.js's native cache using an internal server action
76
+
77
+ **Note:**
78
+
79
+ 1. The Next.js cache only revalidates if at least one user is connected to Convex when a query’s data changes. If someone is connected, all later users get fresh data.
80
+ 2. If no users are connected when the data changes, the next `preloadQuery` will return stale data. However, it will be revalidated instantly once the page connects to Convex.
81
+
82
+ ### Other frameworks for server caching
83
+
84
+ `convex-cache` currently includes adapter for Next.js. However, custom adapters can be built for other frameworks / patterns by leveraging code from the `core` folder in `convex-cache` source code.
85
+
86
+ ## Setup
87
+
88
+ ### Generate Schema Map
89
+
90
+ Use `convex-cache dev` instead of `convex dev` to generate a schema map from your Convex functions:
91
+
92
+ ```bash
93
+ npx convex-cache dev
94
+ # or
95
+ bunx convex-cache dev
96
+ ```
97
+
98
+ This watches your Convex directory, runs `convex dev`, and automatically generates Zod schemas when functions change. The schema map will be available at `convex/_generated/schemaMap.ts` (or `.js` for JavaScript projects).
99
+
100
+ The schema map ensures that if you change the shape of the data returned from your queries, existing caches with previous shape do not cause errors for clients.
101
+
102
+ ## How to Use
103
+
104
+ ### Client Cache
105
+
106
+ Client-side caching stores query results in IndexedDb, providing **supercharged** performance. With client-side caching enabled, the first page load is instant. The cache is automatically revalidated when data changes on Convex.
107
+
108
+ #### Step 1: Setup React Provider
109
+
110
+ Wrap your app with `ConvexCacheProvider`:
111
+
112
+ ```typescript
113
+ import { ConvexProvider, ConvexReactClient } from "convex/react";
114
+ import { ConvexCacheProvider } from "convex-cache/react";
115
+ import { schemaMap } from "./convex/_generated/schemaMap";
116
+
117
+ const convex = new ConvexReactClient(process.env.NEXT_PUBLIC_CONVEX_URL!);
118
+
119
+ export function App({ children }) {
120
+ return (
121
+ <ConvexProvider client={convex}>
122
+ <ConvexCacheProvider schemaMap={schemaMap}>
123
+ {children}
124
+ </ConvexCacheProvider>
125
+ </ConvexProvider>
126
+ );
127
+ }
128
+ ```
129
+
130
+ #### Step 2: Use Cached Queries in React
131
+
132
+ ```typescript
133
+ import { useCachedQueryClient } from "convex-cache/react";
134
+ import { api } from "./convex/_generated/api";
135
+
136
+ function UserProfile({ userId }) {
137
+ const user = useCachedQueryClient({
138
+ query: api.queries.getUser,
139
+ args: { userId },
140
+ });
141
+
142
+ if (!user) return <div>Loading...</div>;
143
+
144
+ return <div>{user.name}</div>;
145
+ }
146
+ ```
147
+
148
+ ### Server Cache
149
+
150
+ Server-side caching allows you to preload queries in Next.js server components and cache them for improved performance. `preloadQuery` caches query results in Next.js's native cache and works seamlessly with Cache Components and Partial Pre-Rendering (PPR). The cache on Next.js servers and edge is automatically revalidated when data changes on Convex.
151
+
152
+ #### Step 1: Setup Preloader
153
+
154
+ Create a preloader function using `buildPreloader`:
155
+
156
+ ```typescript
157
+ import { buildPreloader } from "convex-cache/next/server";
158
+ import { schemaMap } from "./convex/_generated/schemaMap";
159
+
160
+ const preloadQuery = buildPreloader(schemaMap);
161
+ ```
162
+
163
+ #### Step 2: Preload Queries in Server Components
164
+
165
+ ```typescript
166
+ import { preloadQuery } from "./path/to/preloader";
167
+ import { useCachedQueryServer } from "convex-cache/next";
168
+ import { api } from "./convex/_generated/api";
169
+
170
+ export default async function UserPage({ params }) {
171
+ const preloadedData = await preloadQuery({
172
+ query: api.queries.getUser,
173
+ args: { userId: params.userId },
174
+ });
175
+
176
+ return <UserProfile preloadedData={preloadedData} />;
177
+ }
178
+
179
+ function UserProfile({ preloadedData }) {
180
+ const user = useCachedQueryServer({
181
+ query: api.queries.getUser,
182
+ args: { userId: params.userId },
183
+ preloadedData,
184
+ });
185
+
186
+ return <div>{user.name}</div>;
187
+ }
188
+ ```
189
+
190
+ ## API Reference
191
+
192
+ ### Client Cache Hooks (`convex-cache/react`)
193
+
194
+ #### `useCachedQueryClient`
195
+
196
+ Hook for using cached queries in React client components. Results are cached in IndexedDb and automatically revalidated when data changes on Convex.
197
+
198
+ ```typescript
199
+ import { useCachedQueryClient } from "convex-cache/react";
200
+ import { api } from "./convex/_generated/api";
201
+
202
+ function UserProfile({ userId }) {
203
+ const user = useCachedQueryClient({
204
+ query: api.queries.getUser,
205
+ args: { userId },
206
+ });
207
+
208
+ if (!user) return <div>Loading...</div>;
209
+
210
+ return <div>{user.name}</div>;
211
+ }
212
+ ```
213
+
214
+ **Parameters:**
215
+
216
+ - `query`: The Convex query function reference
217
+ - `args`: The query arguments object
218
+
219
+ **Returns:** The query result, or `undefined` while loading
220
+
221
+ #### `useCachedPaginatedQueryClient`
222
+
223
+ Hook for using cached paginated queries in React client components.
224
+
225
+ ```typescript
226
+ import { useCachedPaginatedQueryClient } from "convex-cache/react";
227
+ import { api } from "./convex/_generated/api";
228
+
229
+ function UserList() {
230
+ const { results, status, loadMore } = useCachedPaginatedQueryClient({
231
+ query: api.queries.listUsers,
232
+ args: { filter: "active" },
233
+ options: { initialNumItems: 10 },
234
+ });
235
+
236
+ if (status === "LoadingFirstPage") return <div>Loading...</div>;
237
+
238
+ return (
239
+ <div>
240
+ {results.map((user) => (
241
+ <div key={user.id}>{user.name}</div>
242
+ ))}
243
+ <button onClick={loadMore}>Load More</button>
244
+ </div>
245
+ );
246
+ }
247
+ ```
248
+
249
+ **Parameters:**
250
+
251
+ - `query`: The Convex paginated query function reference
252
+ - `args`: The query arguments object (excluding pagination options)
253
+ - `options`: Pagination options
254
+ - `initialNumItems`: Number of items to load initially
255
+
256
+ **Returns:** An object with:
257
+
258
+ - `results`: Array of paginated results
259
+ - `status`: Loading status (`"LoadingFirstPage"` | `"CanLoadMore"` | `"Exhausted"`)
260
+ - `loadMore`: Function to load the next page
261
+ - `isLoading`: Boolean indicating if more items are being loaded
262
+
263
+ ### Server Cache Hooks (`convex-cache/next`)
264
+
265
+ #### `useCachedQueryServer`
266
+
267
+ Hook for using cached queries in Next.js server components. Requires preloaded data from `preloadQuery` and automatically revalidates the Next.js cache when data changes.
268
+
269
+ ```typescript
270
+ import { useCachedQueryServer } from "convex-cache/next";
271
+ import { api } from "./convex/_generated/api";
272
+
273
+ function UserProfile({ preloadedData, userId }) {
274
+ const user = useCachedQueryServer({
275
+ query: api.queries.getUser,
276
+ args: { userId },
277
+ preloadedData,
278
+ });
279
+
280
+ return <div>{user.name}</div>;
281
+ }
282
+ ```
283
+
284
+ **Parameters:**
285
+
286
+ - `query`: The Convex query function reference
287
+ - `args`: The query arguments object
288
+ - `preloadedData`: Preloaded data from `preloadQuery`
289
+
290
+ **Returns:** The query result
291
+
292
+ #### `useCachedPaginatedQueryServer`
293
+
294
+ Hook for using cached paginated queries in Next.js server components. Requires preloaded data and supports pagination with automatic cache revalidation.
295
+
296
+ ```typescript
297
+ import { useCachedPaginatedQueryServer } from "convex-cache/next";
298
+ import { api } from "./convex/_generated/api";
299
+
300
+ function UserList({ preloadedData }) {
301
+ const { results, status, loadMore } = useCachedPaginatedQueryServer({
302
+ query: api.queries.listUsers,
303
+ args: { filter: "active" },
304
+ options: { initialNumItems: 10 },
305
+ preloadedData,
306
+ });
307
+
308
+ return (
309
+ <div>
310
+ {results.map((user) => (
311
+ <div key={user.id}>{user.name}</div>
312
+ ))}
313
+ {status === "CanLoadMore" && (
314
+ <button onClick={loadMore}>Load More</button>
315
+ )}
316
+ </div>
317
+ );
318
+ }
319
+ ```
320
+
321
+ **Parameters:**
322
+
323
+ - `query`: The Convex paginated query function reference
324
+ - `args`: The query arguments object (excluding pagination options)
325
+ - `options`: Pagination options
326
+ - `initialNumItems`: Number of items to load initially
327
+ - `preloadedData`: Preloaded data from `preloadQuery`
328
+
329
+ **Returns:** An object with:
330
+
331
+ - `results`: Array of paginated results
332
+ - `status`: Loading status (`"LoadingFirstPage"` | `"CanLoadMore"` | `"Exhausted"`)
333
+ - `loadMore`: Function to load the next page
334
+ - `isLoading`: Boolean indicating if more items are being loaded
335
+
336
+ ## Zod Support
337
+
338
+ If you prefer using Zod schemas instead of Convex validators, you can set up Convex with Zod. This allows you to define your query schemas using Zod's validation library and works seamlessly with `convex-cache`. See the [Zod Validation documentation](https://github.com/get-convex/convex-helpers/blob/main/packages/convex-helpers/README.md#zod-validation) for setup instructions.
339
+
340
+ ## License
341
+
342
+ MIT
@@ -0,0 +1,11 @@
1
+ interface DevOptions {
2
+ /** If true, only generate schemas and skip running convex dev. */
3
+ schemaOnly?: boolean;
4
+ }
5
+ /**
6
+ * Start a one-off Convex dev run and then watch the Convex directory
7
+ * for changes, re-running Convex + schema generation on each change.
8
+ * If schemaOnly is true, skip the Convex dev step and only generate schemas.
9
+ */
10
+ export declare const dev: ({ schemaOnly }: DevOptions) => Promise<void>;
11
+ export {};
@@ -0,0 +1,59 @@
1
+ // src/cli/fns/dev.ts
2
+ import { DirWatcher } from "../lib/dir-watcher.js";
3
+ import { ConvexRunner } from "./fns/convex-runner.js";
4
+ import { getConvexDir } from "../lib/convex-config.js";
5
+ /**
6
+ * Start a one-off Convex dev run and then watch the Convex directory
7
+ * for changes, re-running Convex + schema generation on each change.
8
+ * If schemaOnly is true, skip the Convex dev step and only generate schemas.
9
+ */
10
+ export const dev = async ({ schemaOnly = false }) => {
11
+ const convexRunner = new ConvexRunner({
12
+ skipConvex: schemaOnly,
13
+ });
14
+ // Initial run – if this fails, don't bother watching.
15
+ try {
16
+ await convexRunner.runOnce();
17
+ }
18
+ catch (err) {
19
+ console.error("⚠️ Initial run failed. Not starting watcher.", err);
20
+ process.exit(1);
21
+ }
22
+ const shouldIgnore = (rel) => rel.startsWith("_generated") || rel.startsWith(".") || rel.endsWith("~") || rel.endsWith(".swp");
23
+ // Queue rebuilds so we never have overlapping runs.
24
+ let rebuildPromise = Promise.resolve();
25
+ const watcher = new DirWatcher({
26
+ rootDir: getConvexDir(),
27
+ recursive: true,
28
+ debounceMs: 200,
29
+ shouldIgnore,
30
+ onChange: async () => {
31
+ const previous = rebuildPromise;
32
+ rebuildPromise = (async () => {
33
+ // Cancel whatever is currently running (if anything),
34
+ // then wait for the previous run to fully settle before starting a new one.
35
+ convexRunner.cancel();
36
+ await previous.catch(() => {
37
+ // ignore errors from previous run, they are logged elsewhere
38
+ });
39
+ await convexRunner.runOnce();
40
+ })();
41
+ // Make DirWatcher wait for the rebuild to finish (for nicer logs).
42
+ await rebuildPromise;
43
+ },
44
+ });
45
+ watcher.start();
46
+ const cleanup = () => {
47
+ watcher.stop();
48
+ convexRunner.cancel();
49
+ };
50
+ // Graceful shutdown handling.
51
+ process.on("SIGINT", () => {
52
+ cleanup();
53
+ process.exit(0);
54
+ });
55
+ process.on("SIGTERM", () => {
56
+ cleanup();
57
+ process.exit(0);
58
+ });
59
+ };
@@ -0,0 +1,26 @@
1
+ export interface ConvexRunnerOptions {
2
+ skipConvex?: boolean;
3
+ }
4
+ /**
5
+ * Orchestrates running the Convex CLI and regenerating the Zod schema.
6
+ * Uses ShellRunner under the hood and ensures only one run happens at a time.
7
+ */
8
+ export declare class ConvexRunner {
9
+ private readonly tasks;
10
+ private readonly skipConvex;
11
+ private isRunning;
12
+ constructor({ skipConvex }: ConvexRunnerOptions);
13
+ get running(): boolean;
14
+ /**
15
+ * Cancel any in-flight Convex task run.
16
+ * This will cause the current runOnce() to reject with a "cancelled" error.
17
+ */
18
+ cancel: () => void;
19
+ private resolveCurrentPath;
20
+ /**
21
+ * Run the Convex command once, then regenerate the Zod schema.
22
+ * If a run is already in progress, this is a no-op.
23
+ * If skipConvex is true, skips the Convex command and only generates schemas.
24
+ */
25
+ runOnce: () => Promise<void>;
26
+ }
@@ -0,0 +1,71 @@
1
+ import path from "node:path";
2
+ import { fileURLToPath } from "node:url";
3
+ import { ShellRunner, ShellRunnerError } from "../../lib/shell-runner.js";
4
+ import { runCmd, runFileCmd } from "../../lib/package-cmds.js";
5
+ /**
6
+ * Orchestrates running the Convex CLI and regenerating the Zod schema.
7
+ * Uses ShellRunner under the hood and ensures only one run happens at a time.
8
+ */
9
+ export class ConvexRunner {
10
+ constructor({ skipConvex = false }) {
11
+ this.tasks = new ShellRunner();
12
+ this.isRunning = false;
13
+ /**
14
+ * Cancel any in-flight Convex task run.
15
+ * This will cause the current runOnce() to reject with a "cancelled" error.
16
+ */
17
+ this.cancel = () => {
18
+ if (!this.isRunning)
19
+ return;
20
+ this.tasks.cancelAll();
21
+ };
22
+ this.resolveCurrentPath = () => {
23
+ const __filename = fileURLToPath(import.meta.url);
24
+ const __dirname = path.dirname(__filename);
25
+ return __dirname;
26
+ };
27
+ /**
28
+ * Run the Convex command once, then regenerate the Zod schema.
29
+ * If a run is already in progress, this is a no-op.
30
+ * If skipConvex is true, skips the Convex command and only generates schemas.
31
+ */
32
+ this.runOnce = async () => {
33
+ if (this.isRunning)
34
+ return;
35
+ this.isRunning = true;
36
+ this.tasks.resetCancelled();
37
+ try {
38
+ if (!this.skipConvex) {
39
+ const convexCmd = await runCmd("convex dev --once");
40
+ await this.tasks.run(convexCmd, {
41
+ kind: "convex",
42
+ label: "➡️ Uploading functions to Convex...",
43
+ successMessage: "✅ Convex functions uploaded",
44
+ });
45
+ }
46
+ const generateSchemaPath = path.join(this.resolveCurrentPath(), "generate-z-schema/generate.js");
47
+ const generateSchemaCmd = await runFileCmd(generateSchemaPath);
48
+ await this.tasks.run(generateSchemaCmd, {
49
+ kind: "schema",
50
+ label: "➡️ Generating Zod schemas...",
51
+ successMessage: "✅ Zod schemas generated",
52
+ });
53
+ console.log(`\n👀 Watching for any changes...`);
54
+ }
55
+ catch (err) {
56
+ // Ignore cancellations from ShellRunner, log everything else.
57
+ if (!(err instanceof ShellRunnerError && err.message === "cancelled") && (typeof err !== "object" || err === null || err.message !== "cancelled")) {
58
+ console.error("⚠️ Convex run failed:", err);
59
+ }
60
+ }
61
+ finally {
62
+ this.isRunning = false;
63
+ this.tasks.resetCancelled();
64
+ }
65
+ };
66
+ this.skipConvex = skipConvex;
67
+ }
68
+ get running() {
69
+ return this.isRunning;
70
+ }
71
+ }
@@ -0,0 +1,8 @@
1
+ import type { AnyApi } from "convex/server";
2
+ import type { SchemaEntry } from "../generate";
3
+ export declare const buildSchemaMap: ({ files, api, convexDir }: {
4
+ files: string[];
5
+ api: AnyApi;
6
+ convexDir: string;
7
+ }) => Promise<void>;
8
+ export declare const writeSchemasFile: (schemaEntries: SchemaEntry[], convexDir: string) => void;
@@ -0,0 +1,39 @@
1
+ // lib/build-schema-map.ts
2
+ import path from "node:path";
3
+ import fs from "node:fs";
4
+ import { extractSchema } from "../utils/extract-schema";
5
+ import { buildSchemaFileTs, buildSchemaFileJs, buildSchemaDtsFile } from "../utils/build-schema-file";
6
+ import { shouldUseTs } from "../../../../lib/convex-config.js";
7
+ export const buildSchemaMap = async ({ files, api, convexDir }) => {
8
+ const perFileEntries = await Promise.all(files.map((file) => extractSchema(file, api, convexDir)));
9
+ const byName = new Map();
10
+ for (const list of perFileEntries) {
11
+ for (const entry of list) {
12
+ byName.set(entry.fnName, entry);
13
+ }
14
+ }
15
+ const schemaEntries = [...byName.values()];
16
+ if (schemaEntries.length === 0) {
17
+ console.warn("⚠️ No Convex functions with `__returnsJson` found.");
18
+ return;
19
+ }
20
+ writeSchemasFile(schemaEntries, convexDir);
21
+ };
22
+ // ---------- file writer ----------
23
+ export const writeSchemasFile = (schemaEntries, convexDir) => {
24
+ const hostConvexGeneratedDir = path.join(convexDir, "_generated");
25
+ fs.mkdirSync(hostConvexGeneratedDir, { recursive: true });
26
+ let useTs = shouldUseTs();
27
+ if (useTs) {
28
+ // TS project: generate schemaMap.ts only
29
+ const tsContent = buildSchemaFileTs(schemaEntries);
30
+ fs.writeFileSync(path.join(hostConvexGeneratedDir, "schemaMap.ts"), tsContent);
31
+ }
32
+ else {
33
+ // Default: generate schemaMap.js + schemaMap.d.ts
34
+ const jsContent = buildSchemaFileJs(schemaEntries);
35
+ const dtsContent = buildSchemaDtsFile();
36
+ fs.writeFileSync(path.join(hostConvexGeneratedDir, "schemaMap.js"), jsContent);
37
+ fs.writeFileSync(path.join(hostConvexGeneratedDir, "schemaMap.d.ts"), dtsContent);
38
+ }
39
+ };
@@ -0,0 +1,7 @@
1
+ import { AnyApi } from "convex/server";
2
+ export declare const loadConvex: () => Promise<{
3
+ convexDir: string;
4
+ api: AnyApi;
5
+ files: string[];
6
+ } | undefined>;
7
+ export declare const loadConvexApi: (convexDir: string) => Promise<AnyApi | null>;
@@ -0,0 +1,49 @@
1
+ import { pathToFileURL } from "node:url";
2
+ import fg from "fast-glob";
3
+ import fs from "node:fs";
4
+ import path from "node:path";
5
+ import { getConvexDir } from "../../../../lib/convex-config.js";
6
+ export const loadConvex = async () => {
7
+ const convexDir = getConvexDir();
8
+ if (!fs.existsSync(convexDir)) {
9
+ console.warn('⚠️ No "convex" directory found in this project. Skipping schema generation.');
10
+ return;
11
+ }
12
+ const api = await loadConvexApi(convexDir);
13
+ if (!api)
14
+ return;
15
+ const files = await findConvexSourceFiles(convexDir);
16
+ return { convexDir, api, files };
17
+ };
18
+ export const loadConvexApi = async (convexDir) => {
19
+ const candidates = [
20
+ path.join(convexDir, "_generated", "api.mjs"),
21
+ path.join(convexDir, "_generated", "api.js"),
22
+ path.join(convexDir, "_generated", "api.cjs"),
23
+ path.join(convexDir, "_generated", "api.ts"),
24
+ ];
25
+ const existing = candidates.find((p) => fs.existsSync(p));
26
+ if (!existing) {
27
+ console.warn(["⚠️ No convex/_generated/api.{ts,js,mjs,cjs} found in this project.", '⚠️ Skipping schema extraction. Run "npx convex dev" or "npx convex codegen" first.'].join("\n"));
28
+ return null;
29
+ }
30
+ try {
31
+ const mod = await import(pathToFileURL(existing).href);
32
+ if (!("api" in mod)) {
33
+ console.warn(`⚠️ Found ${existing} but it does not export "api". Skipping.`);
34
+ return null;
35
+ }
36
+ return mod.api;
37
+ }
38
+ catch (err) {
39
+ console.warn(`⚠️ Failed to import ${existing}:`, err);
40
+ return null;
41
+ }
42
+ };
43
+ const findConvexSourceFiles = async (convexDir) => {
44
+ return fg("**/*.{ts,js}", {
45
+ cwd: convexDir,
46
+ ignore: ["_generated/**/*.{ts,js}"],
47
+ absolute: true,
48
+ });
49
+ };
@@ -0,0 +1,7 @@
1
+ import { ValidatorJSON } from "convex/values";
2
+ export type SchemaEntry = {
3
+ fnName: string;
4
+ schemaJson: {
5
+ returns: ValidatorJSON;
6
+ };
7
+ };
@@ -0,0 +1,16 @@
1
+ import { loadConvex } from "./fns/load-convex";
2
+ import { buildSchemaMap } from "./fns/build-schema-map";
3
+ const generateZSchema = async () => {
4
+ const loaded = await loadConvex();
5
+ if (!loaded)
6
+ return;
7
+ const { api, files, convexDir } = loaded;
8
+ await buildSchemaMap({ files, api, convexDir });
9
+ };
10
+ const main = async () => {
11
+ await generateZSchema();
12
+ };
13
+ main().catch((err) => {
14
+ console.error("⚠️ Bun generate-z-schema failed:", err);
15
+ process.exit(1);
16
+ });
@@ -0,0 +1,13 @@
1
+ import type { SchemaEntry } from "../generate";
2
+ /**
3
+ * TypeScript version: used when convex.json has `"fileType": "ts"`.
4
+ */
5
+ export declare const buildSchemaFileTs: (schemaEntries: SchemaEntry[]) => string;
6
+ /**
7
+ * JavaScript version: default when not TypeScript.
8
+ */
9
+ export declare const buildSchemaFileJs: (schemaEntries: SchemaEntry[]) => string;
10
+ /**
11
+ * Declaration file for JS projects: just types.
12
+ */
13
+ export declare const buildSchemaDtsFile: () => string;