convex-devtools 1.0.0 → 1.1.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/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Šahzudin Mahmić
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md CHANGED
@@ -6,18 +6,21 @@
6
6
  <img src="https://img.shields.io/npm/dt/convex-devtools" alt="downloads">
7
7
  </p>
8
8
 
9
- A standalone development tool for testing Convex queries, mutations, and actions with identity mocking, request saving, and auto-reloading schema discovery.
9
+ A standalone development tool for testing Convex functions and exploring data with identity mocking, request saving, schema discovery, and a schema‑driven Data Explorer.
10
10
 
11
- > ⚠️ **WARNING**: This tool is intended for **local development only**. It requires admin access to your Convex deployment.
11
+ > 💡 **Note**: This tool is designed for **development and testing**. A deploy key is required for Data Explorer queries.
12
12
 
13
13
  ## Features
14
14
 
15
15
  - 🔍 **Function Explorer** - Browse all your Convex queries, mutations, and actions in a tree view
16
16
  - 🎭 **Identity Mocking** - Test functions as different users with custom roles and claims
17
17
  - 💾 **Request Collections** - Save and organize requests like Postman
18
- - 📜 **History** - View and replay previous function calls
18
+ - 📜 **History** - View and replay previous function calls and schema queries
19
19
  - 🔄 **Auto-reload** - Schema updates automatically when your Convex files change
20
20
  - 📤 **Import/Export** - Share collections with your team
21
+ - 🧭 **Data Explorer** - Schema‑driven table view, column picker, pagination, and JSON view
22
+ - 🧱 **Query Builder** - Build filters/order via UI or switch to raw JSON
23
+ - 🗂️ **Query Tabs** - Recent tabs for functions and schema queries
21
24
 
22
25
  ## Quick Start
23
26
 
@@ -75,6 +78,19 @@ convex-devtools --no-open
75
78
 
76
79
  The tool will automatically open in your browser at `http://localhost:5173`.
77
80
 
81
+ ## Data Explorer (Schema Queries)
82
+
83
+ The Data Explorer lets you run read‑only schema queries against `ctx.db.query()` in your Convex project:
84
+
85
+ - **Builder**: pick table, order, and filters with a UI
86
+ - **JSON**: edit the query payload directly
87
+ - **Table view**: column picker, pagination, and search
88
+ - **JSON view**: raw response with search
89
+ - **Resizable panes**: adjust the Data Explorer/Response split
90
+ - **Recent tabs**: quickly jump between recent schema queries
91
+
92
+ The helper query uses Convex pagination (cursor + page size). The deploy key is required to run these queries.
93
+
78
94
  ## CLI Options
79
95
 
80
96
  | Option | Description | Default |
@@ -138,7 +154,7 @@ The export format is a simple JSON structure:
138
154
 
139
155
  ```json
140
156
  {
141
- "version": "1.0",
157
+ "version": "1.1.0",
142
158
  "exportedAt": "2025-01-29T10:00:00.000Z",
143
159
  "collections": [
144
160
  {
@@ -275,6 +291,10 @@ npm publish
275
291
  npm publish --access public
276
292
  ```
277
293
 
294
+ > Note: If your npm account requires 2FA for publishing, you can either enable it with
295
+ > `npm profile enable-2fa auth-only` (then publish with your OTP), or use a granular
296
+ > access token with “bypass 2FA for publish” enabled.
297
+
278
298
  ### Testing Before Publishing
279
299
 
280
300
  ```bash
@@ -328,9 +348,31 @@ Use the `--port` flag to specify a different port:
328
348
  convex-devtools --port 3001
329
349
  ```
330
350
 
351
+ ### "command not found: convex-devtools" after global install
352
+
353
+ If you installed globally but the command isn't found, npm's global bin directory may not be in your PATH.
354
+
355
+ 1. Find your npm global bin path:
356
+
357
+ ```bash
358
+ npm config get prefix
359
+ ```
360
+
361
+ 2. Add the bin folder to your PATH. For example, on macOS/Linux with zsh:
362
+
363
+ ```bash
364
+ echo 'export PATH="$(npm config get prefix)/bin:$PATH"' >> ~/.zshrc
365
+ source ~/.zshrc
366
+ ```
367
+
368
+ 3. Alternatively, use `npx` which doesn't require PATH setup:
369
+ ```bash
370
+ npx convex-devtools
371
+ ```
372
+
331
373
  ## Contributing
332
374
 
333
- Contributions are welcome! Please feel free to submit a Pull Request.
375
+ Contributions are welcome! Please read our [Contributing Guide](CONTRIBUTING.md) before submitting a Pull Request.
334
376
 
335
377
  1. Fork the repository
336
378
  2. Create your feature branch (`git checkout -b feature/amazing-feature`)
@@ -340,7 +382,7 @@ Contributions are welcome! Please feel free to submit a Pull Request.
340
382
 
341
383
  ## License
342
384
 
343
- MIT © [Šahzudin Mahmić]
385
+ MIT License - see the [LICENSE](LICENSE) file for details.
344
386
 
345
387
  ## Related Projects
346
388
 
@@ -12,7 +12,7 @@ var ConvexClient = class {
12
12
  const url = `${this.baseUrl}/${endpoint}`;
13
13
  const headers = {
14
14
  "Content-Type": "application/json",
15
- "Convex-Client": "convex-devtools-1.0.0"
15
+ "Convex-Client": "convex-devtools-1.0.1"
16
16
  };
17
17
  if (options?.jwtToken) {
18
18
  headers["Authorization"] = `Bearer ${options.jwtToken}`;
@@ -134,4 +134,4 @@ var ConvexClient = class {
134
134
  export {
135
135
  ConvexClient
136
136
  };
137
- //# sourceMappingURL=chunk-4PBPPYEF.js.map
137
+ //# sourceMappingURL=chunk-5B55IAKY.js.map
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/server/convex-client.ts"],"sourcesContent":["/**\n * Convex HTTP Client wrapper for DevTools\n * Supports two authentication methods:\n * 1. Deploy key - runs as admin (for admin operations)\n * 2. JWT token - runs as the authenticated user (from your auth provider like Clerk)\n */\n\nexport interface UserIdentity {\n subject: string;\n issuer?: string;\n tokenIdentifier?: string;\n name?: string;\n email?: string;\n pictureUrl?: string;\n // Custom claims\n [key: string]: unknown;\n}\n\nexport interface InvokeOptions {\n identity?: UserIdentity;\n jwtToken?: string; // Real JWT token from auth provider\n}\n\nexport class ConvexClient {\n private baseUrl: string;\n private deployKey: string;\n\n constructor(convexUrl: string, deployKey: string) {\n // Convert deployment URL to HTTP endpoint\n // e.g., https://happy-otter-123.convex.cloud -> https://happy-otter-123.convex.cloud\n this.baseUrl = convexUrl;\n this.deployKey = deployKey;\n }\n\n async invoke(\n functionPath: string,\n functionType: 'query' | 'mutation' | 'action',\n args: Record<string, unknown> = {},\n options?: InvokeOptions\n ): Promise<unknown> {\n // Normalize function path: module/submodule:functionName\n const normalizedPath = this.normalizeFunctionPath(functionPath);\n\n // Build the request\n const endpoint = this.getEndpoint(functionType);\n const url = `${this.baseUrl}/${endpoint}`;\n\n // Build headers\n const headers: Record<string, string> = {\n 'Content-Type': 'application/json',\n 'Convex-Client': 'convex-devtools-1.0.0',\n };\n\n // Authentication priority:\n // 1. JWT token (authenticates as the user who owns the token)\n // 2. Deploy key (authenticates as admin)\n // 3. No auth (unauthenticated request)\n if (options?.jwtToken) {\n // Use Bearer token auth - this is what the Convex HTTP API expects for user auth\n headers['Authorization'] = `Bearer ${options.jwtToken}`;\n console.log('[ConvexClient] Using JWT token authentication');\n } else if (this.deployKey) {\n // Deploy key gives admin access but cannot impersonate users\n headers['Authorization'] = `Convex ${this.deployKey}`;\n console.log('[ConvexClient] Using deploy key (admin) authentication');\n }\n // Without any auth, calls will be unauthenticated\n\n const body = {\n path: normalizedPath,\n args: this.encodeArgs(args),\n format: 'json',\n };\n\n const response = await fetch(url, {\n method: 'POST',\n headers,\n body: JSON.stringify(body),\n });\n\n if (!response.ok) {\n const errorText = await response.text();\n let errorData;\n try {\n errorData = JSON.parse(errorText);\n } catch {\n errorData = { message: errorText };\n }\n\n const error = new Error(errorData.message || `HTTP ${response.status}`);\n (error as any).code = errorData.code;\n (error as any).data = errorData;\n throw error;\n }\n\n const result = await response.json();\n return this.decodeResult(result);\n }\n\n private normalizeFunctionPath(path: string): string {\n // Convert various formats to Convex function path format\n // Input: \"products/products:list\" or \"products.products.list\" or \"products/products/list\"\n // Output: \"products/products:list\"\n\n // Already in correct format\n if (path.includes(':')) {\n return path;\n }\n\n // Dot notation: products.products.list -> products/products:list\n if (path.includes('.')) {\n const parts = path.split('.');\n const funcName = parts.pop()!;\n return `${parts.join('/')}:${funcName}`;\n }\n\n // Slash only: products/products/list -> products/products:list\n const parts = path.split('/');\n if (parts.length > 1) {\n const funcName = parts.pop()!;\n return `${parts.join('/')}:${funcName}`;\n }\n\n return path;\n }\n\n private getEndpoint(functionType: 'query' | 'mutation' | 'action'): string {\n switch (functionType) {\n case 'query':\n return 'api/query';\n case 'mutation':\n return 'api/mutation';\n case 'action':\n return 'api/action';\n }\n }\n\n private encodeArgs(args: Record<string, unknown>): Record<string, unknown> {\n // Convex uses a special encoding format for complex types\n // For now, we'll pass through JSON-serializable values\n // Special handling for Convex types like Id, etc.\n return this.convertToConvexJson(args) as Record<string, unknown>;\n }\n\n private convertToConvexJson(value: unknown): unknown {\n if (value === null || value === undefined) {\n return value;\n }\n\n if (Array.isArray(value)) {\n return value.map((v) => this.convertToConvexJson(v));\n }\n\n if (typeof value === 'object') {\n const obj = value as Record<string, unknown>;\n\n // Check for special $type markers (Convex encoded JSON)\n if ('$type' in obj) {\n return obj; // Already encoded\n }\n\n // Regular object\n const converted: Record<string, unknown> = {};\n for (const [k, v] of Object.entries(obj)) {\n converted[k] = this.convertToConvexJson(v);\n }\n return converted;\n }\n\n // Handle BigInt\n if (typeof value === 'bigint') {\n return { $type: 'bigint', value: value.toString() };\n }\n\n return value;\n }\n\n private decodeResult(result: unknown): unknown {\n // Decode Convex-specific types back to JavaScript\n return this.convertFromConvexJson(result);\n }\n\n private convertFromConvexJson(value: unknown): unknown {\n if (value === null || value === undefined) {\n return value;\n }\n\n if (Array.isArray(value)) {\n return value.map((v) => this.convertFromConvexJson(v));\n }\n\n if (typeof value === 'object') {\n const obj = value as Record<string, unknown>;\n\n // Check for special $type markers\n if ('$type' in obj) {\n switch (obj.$type) {\n case 'bigint':\n return BigInt(obj.value as string);\n case 'bytes':\n return new Uint8Array(obj.value as number[]);\n default:\n return obj;\n }\n }\n\n // Regular object\n const result: Record<string, unknown> = {};\n for (const [k, v] of Object.entries(obj)) {\n result[k] = this.convertFromConvexJson(v);\n }\n return result;\n }\n\n return value;\n }\n}\n"],"mappings":"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","names":["parts"]}
1
+ {"version":3,"sources":["../../src/server/convex-client.ts"],"sourcesContent":["/**\n * Convex HTTP Client wrapper for DevTools\n * Supports two authentication methods:\n * 1. Deploy key - runs as admin (for admin operations)\n * 2. JWT token - runs as the authenticated user (from your auth provider like Clerk)\n */\n\nexport interface UserIdentity {\n subject: string;\n issuer?: string;\n tokenIdentifier?: string;\n name?: string;\n email?: string;\n pictureUrl?: string;\n // Custom claims\n [key: string]: unknown;\n}\n\nexport interface InvokeOptions {\n identity?: UserIdentity;\n jwtToken?: string; // Real JWT token from auth provider\n}\n\nexport class ConvexClient {\n private baseUrl: string;\n private deployKey: string;\n\n constructor(convexUrl: string, deployKey: string) {\n // Convert deployment URL to HTTP endpoint\n // e.g., https://happy-otter-123.convex.cloud -> https://happy-otter-123.convex.cloud\n this.baseUrl = convexUrl;\n this.deployKey = deployKey;\n }\n\n async invoke(\n functionPath: string,\n functionType: 'query' | 'mutation' | 'action',\n args: Record<string, unknown> = {},\n options?: InvokeOptions\n ): Promise<unknown> {\n // Normalize function path: module/submodule:functionName\n const normalizedPath = this.normalizeFunctionPath(functionPath);\n\n // Build the request\n const endpoint = this.getEndpoint(functionType);\n const url = `${this.baseUrl}/${endpoint}`;\n\n // Build headers\n const headers: Record<string, string> = {\n 'Content-Type': 'application/json',\n 'Convex-Client': 'convex-devtools-1.0.1',\n };\n\n // Authentication priority:\n // 1. JWT token (authenticates as the user who owns the token)\n // 2. Deploy key (authenticates as admin)\n // 3. No auth (unauthenticated request)\n if (options?.jwtToken) {\n // Use Bearer token auth - this is what the Convex HTTP API expects for user auth\n headers['Authorization'] = `Bearer ${options.jwtToken}`;\n console.log('[ConvexClient] Using JWT token authentication');\n } else if (this.deployKey) {\n // Deploy key gives admin access but cannot impersonate users\n headers['Authorization'] = `Convex ${this.deployKey}`;\n console.log('[ConvexClient] Using deploy key (admin) authentication');\n }\n // Without any auth, calls will be unauthenticated\n\n const body = {\n path: normalizedPath,\n args: this.encodeArgs(args),\n format: 'json',\n };\n\n const response = await fetch(url, {\n method: 'POST',\n headers,\n body: JSON.stringify(body),\n });\n\n if (!response.ok) {\n const errorText = await response.text();\n let errorData;\n try {\n errorData = JSON.parse(errorText);\n } catch {\n errorData = { message: errorText };\n }\n\n const error = new Error(errorData.message || `HTTP ${response.status}`);\n (error as any).code = errorData.code;\n (error as any).data = errorData;\n throw error;\n }\n\n const result = await response.json();\n return this.decodeResult(result);\n }\n\n private normalizeFunctionPath(path: string): string {\n // Convert various formats to Convex function path format\n // Input: \"products/products:list\" or \"products.products.list\" or \"products/products/list\"\n // Output: \"products/products:list\"\n\n // Already in correct format\n if (path.includes(':')) {\n return path;\n }\n\n // Dot notation: products.products.list -> products/products:list\n if (path.includes('.')) {\n const parts = path.split('.');\n const funcName = parts.pop()!;\n return `${parts.join('/')}:${funcName}`;\n }\n\n // Slash only: products/products/list -> products/products:list\n const parts = path.split('/');\n if (parts.length > 1) {\n const funcName = parts.pop()!;\n return `${parts.join('/')}:${funcName}`;\n }\n\n return path;\n }\n\n private getEndpoint(functionType: 'query' | 'mutation' | 'action'): string {\n switch (functionType) {\n case 'query':\n return 'api/query';\n case 'mutation':\n return 'api/mutation';\n case 'action':\n return 'api/action';\n }\n }\n\n private encodeArgs(args: Record<string, unknown>): Record<string, unknown> {\n // Convex uses a special encoding format for complex types\n // For now, we'll pass through JSON-serializable values\n // Special handling for Convex types like Id, etc.\n return this.convertToConvexJson(args) as Record<string, unknown>;\n }\n\n private convertToConvexJson(value: unknown): unknown {\n if (value === null || value === undefined) {\n return value;\n }\n\n if (Array.isArray(value)) {\n return value.map((v) => this.convertToConvexJson(v));\n }\n\n if (typeof value === 'object') {\n const obj = value as Record<string, unknown>;\n\n // Check for special $type markers (Convex encoded JSON)\n if ('$type' in obj) {\n return obj; // Already encoded\n }\n\n // Regular object\n const converted: Record<string, unknown> = {};\n for (const [k, v] of Object.entries(obj)) {\n converted[k] = this.convertToConvexJson(v);\n }\n return converted;\n }\n\n // Handle BigInt\n if (typeof value === 'bigint') {\n return { $type: 'bigint', value: value.toString() };\n }\n\n return value;\n }\n\n private decodeResult(result: unknown): unknown {\n // Decode Convex-specific types back to JavaScript\n return this.convertFromConvexJson(result);\n }\n\n private convertFromConvexJson(value: unknown): unknown {\n if (value === null || value === undefined) {\n return value;\n }\n\n if (Array.isArray(value)) {\n return value.map((v) => this.convertFromConvexJson(v));\n }\n\n if (typeof value === 'object') {\n const obj = value as Record<string, unknown>;\n\n // Check for special $type markers\n if ('$type' in obj) {\n switch (obj.$type) {\n case 'bigint':\n return BigInt(obj.value as string);\n case 'bytes':\n return new Uint8Array(obj.value as number[]);\n default:\n return obj;\n }\n }\n\n // Regular object\n const result: Record<string, unknown> = {};\n for (const [k, v] of Object.entries(obj)) {\n result[k] = this.convertFromConvexJson(v);\n }\n return result;\n }\n\n return value;\n }\n}\n"],"mappings":"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","names":["parts"]}
@@ -3,6 +3,11 @@ import chokidar from "chokidar";
3
3
  import { EventEmitter } from "events";
4
4
  import fs from "fs";
5
5
  import path from "path";
6
+ import {
7
+ Node,
8
+ Project,
9
+ SyntaxKind
10
+ } from "ts-morph";
6
11
  var SchemaWatcher = class extends EventEmitter {
7
12
  projectDir;
8
13
  watcher = null;
@@ -56,7 +61,7 @@ var SchemaWatcher = class extends EventEmitter {
56
61
  this.schemaInfo = {
57
62
  modules,
58
63
  tables,
59
- lastUpdated: /* @__PURE__ */ new Date()
64
+ lastUpdated: (/* @__PURE__ */ new Date()).toISOString()
60
65
  };
61
66
  const funcCount = this.countFunctions(modules);
62
67
  console.log(
@@ -201,8 +206,8 @@ var SchemaWatcher = class extends EventEmitter {
201
206
  extractArgsFromPosition(content, startIndex, jsdocComment = "") {
202
207
  const args = [];
203
208
  const jsdocParams = this.parseJSDocParams(jsdocComment);
204
- const afterStart = content.slice(startIndex);
205
- const argsMatch = afterStart.match(/args:\s*\{([^}]*)\}/s);
209
+ const functionBlock = this.extractFunctionBlock(content, startIndex);
210
+ const argsMatch = functionBlock.match(/args:\s*\{([^}]*)\}/s);
206
211
  if (argsMatch) {
207
212
  const argsContent = argsMatch[1];
208
213
  const argPattern = /(\w+):\s*(v\.optional\()?v\.(\w+)/g;
@@ -219,7 +224,7 @@ var SchemaWatcher = class extends EventEmitter {
219
224
  });
220
225
  }
221
226
  }
222
- const hasPaginationOpts = afterStart.match(/paginationOptsValidator/s);
227
+ const hasPaginationOpts = functionBlock.match(/paginationOptsValidator/s);
223
228
  if (hasPaginationOpts) {
224
229
  args.push({
225
230
  name: "paginationOpts",
@@ -230,6 +235,101 @@ var SchemaWatcher = class extends EventEmitter {
230
235
  }
231
236
  return args;
232
237
  }
238
+ extractFunctionBlock(content, startIndex) {
239
+ const afterStart = content.slice(startIndex);
240
+ const openIndex = afterStart.indexOf("{");
241
+ if (openIndex === -1) {
242
+ return afterStart;
243
+ }
244
+ let depth = 0;
245
+ let inSingle = false;
246
+ let inDouble = false;
247
+ let inTemplate = false;
248
+ let inLineComment = false;
249
+ let inBlockComment = false;
250
+ let escape = false;
251
+ for (let i = openIndex; i < afterStart.length; i += 1) {
252
+ const char = afterStart[i];
253
+ const next = afterStart[i + 1];
254
+ if (inLineComment) {
255
+ if (char === "\n") {
256
+ inLineComment = false;
257
+ }
258
+ continue;
259
+ }
260
+ if (inBlockComment) {
261
+ if (char === "*" && next === "/") {
262
+ inBlockComment = false;
263
+ i += 1;
264
+ }
265
+ continue;
266
+ }
267
+ if (inSingle) {
268
+ if (escape) {
269
+ escape = false;
270
+ } else if (char === "\\") {
271
+ escape = true;
272
+ } else if (char === "'") {
273
+ inSingle = false;
274
+ }
275
+ continue;
276
+ }
277
+ if (inDouble) {
278
+ if (escape) {
279
+ escape = false;
280
+ } else if (char === "\\") {
281
+ escape = true;
282
+ } else if (char === '"') {
283
+ inDouble = false;
284
+ }
285
+ continue;
286
+ }
287
+ if (inTemplate) {
288
+ if (escape) {
289
+ escape = false;
290
+ } else if (char === "\\") {
291
+ escape = true;
292
+ } else if (char === "`") {
293
+ inTemplate = false;
294
+ }
295
+ continue;
296
+ }
297
+ if (char === "/" && next === "/") {
298
+ inLineComment = true;
299
+ i += 1;
300
+ continue;
301
+ }
302
+ if (char === "/" && next === "*") {
303
+ inBlockComment = true;
304
+ i += 1;
305
+ continue;
306
+ }
307
+ if (char === "'") {
308
+ inSingle = true;
309
+ escape = false;
310
+ continue;
311
+ }
312
+ if (char === '"') {
313
+ inDouble = true;
314
+ escape = false;
315
+ continue;
316
+ }
317
+ if (char === "`") {
318
+ inTemplate = true;
319
+ escape = false;
320
+ continue;
321
+ }
322
+ if (char === "{") {
323
+ depth += 1;
324
+ } else if (char === "}") {
325
+ depth -= 1;
326
+ if (depth === 0) {
327
+ return afterStart.slice(openIndex, i + 1);
328
+ }
329
+ }
330
+ }
331
+ return afterStart;
332
+ }
233
333
  async parseSchemaFile(convexDir) {
234
334
  const tables = [];
235
335
  const schemaPath = path.join(convexDir, "schema.ts");
@@ -238,13 +338,48 @@ var SchemaWatcher = class extends EventEmitter {
238
338
  }
239
339
  try {
240
340
  const content = fs.readFileSync(schemaPath, "utf-8");
241
- const tablePattern = /(\w+):\s*defineTable\(/g;
242
- let match;
243
- while ((match = tablePattern.exec(content)) !== null) {
341
+ const project = new Project({ useInMemoryFileSystem: true });
342
+ const sourceFile = project.createSourceFile(
343
+ "schema.ts",
344
+ content,
345
+ { overwrite: true }
346
+ );
347
+ const defineSchemaCall = sourceFile.getDescendantsOfKind(SyntaxKind.CallExpression).find(
348
+ (call) => call.getExpression().getText() === "defineSchema"
349
+ );
350
+ if (!defineSchemaCall) {
351
+ return tables;
352
+ }
353
+ const schemaArg = defineSchemaCall.getArguments()[0];
354
+ if (!schemaArg || !Node.isObjectLiteralExpression(schemaArg)) {
355
+ return tables;
356
+ }
357
+ for (const prop of schemaArg.getProperties()) {
358
+ if (!Node.isPropertyAssignment(prop)) continue;
359
+ const tableName = this.normalizePropertyName(prop.getName());
360
+ const initializer = prop.getInitializer();
361
+ if (!initializer) continue;
362
+ const defineTableCall = this.findDefineTableCall(initializer);
363
+ if (!defineTableCall) continue;
364
+ const fieldsArg = defineTableCall.getArguments()[0];
365
+ const fields = [];
366
+ if (fieldsArg && Node.isObjectLiteralExpression(fieldsArg)) {
367
+ for (const fieldProp of fieldsArg.getProperties()) {
368
+ if (!Node.isPropertyAssignment(fieldProp)) continue;
369
+ const fieldName = this.normalizePropertyName(fieldProp.getName());
370
+ const fieldInit = fieldProp.getInitializer();
371
+ if (!fieldInit) continue;
372
+ const parsedField = this.parseFieldInfo(fieldInit);
373
+ fields.push({
374
+ name: fieldName,
375
+ type: parsedField.type,
376
+ optional: parsedField.optional
377
+ });
378
+ }
379
+ }
244
380
  tables.push({
245
- name: match[1],
246
- fields: []
247
- // Could parse fields but keeping simple for now
381
+ name: tableName,
382
+ fields
248
383
  });
249
384
  }
250
385
  } catch (error) {
@@ -252,9 +387,54 @@ var SchemaWatcher = class extends EventEmitter {
252
387
  }
253
388
  return tables;
254
389
  }
390
+ findDefineTableCall(expr) {
391
+ if (Node.isCallExpression(expr)) {
392
+ const callee = expr.getExpression();
393
+ if (Node.isIdentifier(callee) && callee.getText() === "defineTable") {
394
+ return expr;
395
+ }
396
+ if (Node.isPropertyAccessExpression(callee)) {
397
+ const inner = callee.getExpression();
398
+ if (Node.isCallExpression(inner)) {
399
+ return this.findDefineTableCall(inner);
400
+ }
401
+ if (Node.isPropertyAccessExpression(inner)) {
402
+ return this.findDefineTableCall(inner.getExpression());
403
+ }
404
+ }
405
+ }
406
+ if (Node.isPropertyAccessExpression(expr)) {
407
+ return this.findDefineTableCall(expr.getExpression());
408
+ }
409
+ return null;
410
+ }
411
+ parseFieldInfo(expr) {
412
+ let optional = false;
413
+ let typeExpr = expr;
414
+ if (Node.isCallExpression(expr)) {
415
+ const calleeText = expr.getExpression().getText();
416
+ if (calleeText === "v.optional") {
417
+ optional = true;
418
+ const inner = expr.getArguments()[0];
419
+ if (inner && Node.isExpression(inner)) {
420
+ typeExpr = inner;
421
+ }
422
+ }
423
+ }
424
+ return {
425
+ type: typeExpr?.getText() ?? "unknown",
426
+ optional
427
+ };
428
+ }
429
+ normalizePropertyName(name) {
430
+ if (name.startsWith("'") && name.endsWith("'") || name.startsWith('"') && name.endsWith('"')) {
431
+ return name.slice(1, -1);
432
+ }
433
+ return name;
434
+ }
255
435
  };
256
436
 
257
437
  export {
258
438
  SchemaWatcher
259
439
  };
260
- //# sourceMappingURL=chunk-ENBIFEKV.js.map
440
+ //# sourceMappingURL=chunk-7LFMAA6L.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../src/server/schema-watcher.ts"],"sourcesContent":["import chokidar from 'chokidar';\nimport { EventEmitter } from 'events';\nimport fs from 'fs';\nimport path from 'path';\nimport {\n CallExpression,\n Expression,\n Node,\n Project,\n SyntaxKind,\n} from 'ts-morph';\n\nexport interface FunctionInfo {\n name: string;\n path: string; // Full path like \"products/products:list\"\n type: 'query' | 'mutation' | 'action';\n args: ArgInfo[];\n returns?: string;\n}\n\nexport interface ArgInfo {\n name: string;\n type: string;\n optional: boolean;\n description?: string;\n enumValues?: string[];\n}\n\nexport interface ModuleInfo {\n name: string;\n path: string;\n functions: FunctionInfo[];\n children: ModuleInfo[];\n}\n\nexport interface SchemaInfo {\n modules: ModuleInfo[];\n tables: TableInfo[];\n lastUpdated: string;\n}\n\nexport interface TableInfo {\n name: string;\n fields: FieldInfo[];\n}\n\nexport interface FieldInfo {\n name: string;\n type: string;\n optional: boolean;\n}\n\nexport class SchemaWatcher extends EventEmitter {\n private projectDir: string;\n private watcher: chokidar.FSWatcher | null = null;\n private schemaInfo: SchemaInfo | null = null;\n\n constructor(projectDir: string) {\n super();\n this.projectDir = projectDir;\n }\n\n async start(): Promise<void> {\n // Initial parse\n await this.parseSchema();\n\n // Watch for changes\n const convexDir = path.join(this.projectDir, 'convex');\n\n this.watcher = chokidar.watch(convexDir, {\n persistent: true,\n ignoreInitial: true,\n ignored: [\n '**/node_modules/**',\n '**/_generated/**',\n '**/test.setup.ts',\n '**/*.test.ts',\n ],\n });\n\n this.watcher.on('change', async (filePath) => {\n if (filePath.endsWith('.ts') && !filePath.includes('_generated')) {\n console.log(`[SchemaWatcher] File changed: ${filePath}`);\n await this.parseSchema();\n this.emit('schema-updated', this.schemaInfo);\n }\n });\n\n this.watcher.on('add', async (filePath) => {\n if (filePath.endsWith('.ts') && !filePath.includes('_generated')) {\n console.log(`[SchemaWatcher] File added: ${filePath}`);\n await this.parseSchema();\n this.emit('schema-updated', this.schemaInfo);\n }\n });\n }\n\n stop(): void {\n if (this.watcher) {\n this.watcher.close();\n this.watcher = null;\n }\n }\n\n getSchema(): SchemaInfo | null {\n return this.schemaInfo;\n }\n\n private async parseSchema(): Promise<void> {\n try {\n const convexDir = path.join(this.projectDir, 'convex');\n const modules = await this.parseConvexDirectory(convexDir);\n const tables = await this.parseSchemaFile(convexDir);\n\n this.schemaInfo = {\n modules,\n tables,\n lastUpdated: new Date().toISOString(),\n };\n\n const funcCount = this.countFunctions(modules);\n console.log(\n `[SchemaWatcher] Parsed ${funcCount} functions from ${modules.length} modules`\n );\n } catch (error) {\n console.error('[SchemaWatcher] Error parsing schema:', error);\n }\n }\n\n private countFunctions(modules: ModuleInfo[]): number {\n let count = 0;\n for (const mod of modules) {\n count += mod.functions.length;\n count += this.countFunctions(mod.children);\n }\n return count;\n }\n\n private async parseConvexDirectory(convexDir: string): Promise<ModuleInfo[]> {\n const modules: ModuleInfo[] = [];\n\n // Read convex directory structure\n const entries = fs.readdirSync(convexDir, { withFileTypes: true });\n\n for (const entry of entries) {\n // Skip hidden, generated, and test files\n if (\n entry.name.startsWith('.') ||\n entry.name.startsWith('_') ||\n entry.name === 'node_modules' ||\n entry.name.endsWith('.test.ts') ||\n entry.name === 'test.setup.ts'\n ) {\n continue;\n }\n\n if (entry.isDirectory()) {\n // Parse subdirectory as module\n const subModule = await this.parseSubdirectory(\n path.join(convexDir, entry.name),\n entry.name\n );\n if (subModule.functions.length > 0 || subModule.children.length > 0) {\n modules.push(subModule);\n }\n } else if (entry.isFile() && entry.name.endsWith('.ts')) {\n // Parse root-level file\n const filePath = path.join(convexDir, entry.name);\n const moduleName = entry.name.replace('.ts', '');\n const functions = await this.parseFile(filePath, moduleName);\n\n if (functions.length > 0) {\n modules.push({\n name: moduleName,\n path: moduleName,\n functions,\n children: [],\n });\n }\n }\n }\n\n return modules;\n }\n\n private async parseSubdirectory(\n dirPath: string,\n parentPath: string\n ): Promise<ModuleInfo> {\n const module: ModuleInfo = {\n name: path.basename(dirPath),\n path: parentPath,\n functions: [],\n children: [],\n };\n\n const entries = fs.readdirSync(dirPath, { withFileTypes: true });\n\n for (const entry of entries) {\n // Skip tests directory and test files\n if (\n entry.name === 'tests' ||\n entry.name.endsWith('.test.ts') ||\n entry.name.startsWith('.')\n ) {\n continue;\n }\n\n if (entry.isDirectory()) {\n const subModule = await this.parseSubdirectory(\n path.join(dirPath, entry.name),\n `${parentPath}/${entry.name}`\n );\n if (subModule.functions.length > 0 || subModule.children.length > 0) {\n module.children.push(subModule);\n }\n } else if (entry.isFile() && entry.name.endsWith('.ts')) {\n const filePath = path.join(dirPath, entry.name);\n const moduleName = entry.name.replace('.ts', '');\n const modulePath = `${parentPath}/${moduleName}`;\n const functions = await this.parseFile(filePath, modulePath);\n\n // Create a child module for each file (group by file)\n if (functions.length > 0) {\n module.children.push({\n name: moduleName,\n path: modulePath,\n functions,\n children: [],\n });\n }\n }\n }\n\n return module;\n }\n\n private async parseFile(\n filePath: string,\n modulePath: string\n ): Promise<FunctionInfo[]> {\n const functions: FunctionInfo[] = [];\n\n try {\n const content = fs.readFileSync(filePath, 'utf-8');\n\n // Parse exported functions using regex\n // Match patterns like: export const functionName = query({ or mutation({ or action({\n const exportPattern =\n /export\\s+const\\s+(\\w+)\\s*=\\s*(query|mutation|action|internalQuery|internalMutation|internalAction)\\s*\\(\\s*\\{/g;\n\n let match;\n while ((match = exportPattern.exec(content)) !== null) {\n const [, funcName, funcType] = match;\n\n // Normalize internal functions to their base type\n let normalizedType = funcType as 'query' | 'mutation' | 'action';\n if (funcType.startsWith('internal')) {\n normalizedType = funcType.replace('internal', '').toLowerCase() as\n | 'query'\n | 'mutation'\n | 'action';\n }\n\n // Extract JSDoc comment above the function\n const jsdocComment = this.extractJSDocAbove(content, match.index);\n\n // Extract args from the function definition\n const args = this.extractArgsFromPosition(\n content,\n match.index,\n jsdocComment\n );\n\n functions.push({\n name: funcName,\n path: `${modulePath}:${funcName}`,\n type: normalizedType,\n args,\n });\n }\n } catch (error) {\n console.error(`[SchemaWatcher] Error parsing file ${filePath}:`, error);\n }\n\n return functions;\n }\n\n private extractJSDocAbove(content: string, position: number): string {\n // Look backwards from position to find JSDoc comment\n // Allow some whitespace and newlines between the JSDoc and the export\n const beforePosition = content.slice(0, position);\n // Match JSDoc that ends with */ followed by optional whitespace before the export\n const jsdocMatch = beforePosition.match(/\\/\\*\\*([\\s\\S]*?)\\*\\/\\s*$/);\n if (jsdocMatch) {\n return jsdocMatch[1];\n }\n\n // Also try to find JSDoc within the last 500 chars (in case there's space between)\n const last500 = beforePosition.slice(-500);\n const jsdocMatch2 = last500.match(/\\/\\*\\*([\\s\\S]*?)\\*\\//);\n return jsdocMatch2 ? jsdocMatch2[1] : '';\n }\n\n private parseJSDocParams(\n jsdoc: string\n ): Map<string, { description: string; enumValues?: string[] }> {\n const params = new Map<\n string,\n { description: string; enumValues?: string[] }\n >();\n\n // Match @param patterns like: @param sortBy - Sort order: 'newest', 'oldest'\n const paramPattern = /@param\\s+(\\w+)\\s*-?\\s*([^@]*)/g;\n let match;\n while ((match = paramPattern.exec(jsdoc)) !== null) {\n const [, paramName, description] = match;\n const trimmedDesc = description.trim();\n\n // Extract enum values from description (quoted strings like 'value1', 'value2')\n const enumMatches = trimmedDesc.match(/'([^']+)'/g);\n const enumValues = enumMatches\n ? enumMatches.map((e) => e.replace(/'/g, ''))\n : undefined;\n\n params.set(paramName, {\n description: trimmedDesc,\n enumValues:\n enumValues && enumValues.length > 0 ? enumValues : undefined,\n });\n }\n\n return params;\n }\n\n private extractArgsFromPosition(\n content: string,\n startIndex: number,\n jsdocComment: string = ''\n ): ArgInfo[] {\n const args: ArgInfo[] = [];\n const jsdocParams = this.parseJSDocParams(jsdocComment);\n\n // Limit parsing to the current function block to avoid false positives\n const functionBlock = this.extractFunctionBlock(content, startIndex);\n\n // Find the args: { ... } section\n const argsMatch = functionBlock.match(/args:\\s*\\{([^}]*)\\}/s);\n\n if (argsMatch) {\n const argsContent = argsMatch[1];\n\n // Parse individual args\n // Matches patterns like: argName: v.string(), argName: v.optional(v.id('users'))\n const argPattern = /(\\w+):\\s*(v\\.optional\\()?v\\.(\\w+)/g;\n\n let argMatch;\n while ((argMatch = argPattern.exec(argsContent)) !== null) {\n const [, argName, isOptional, argType] = argMatch;\n const jsdocInfo = jsdocParams.get(argName);\n\n args.push({\n name: argName,\n type: argType,\n optional: !!isOptional,\n description: jsdocInfo?.description,\n enumValues: jsdocInfo?.enumValues,\n });\n }\n }\n\n // Check for paginationOpts (built-in Convex pagination)\n const hasPaginationOpts = functionBlock.match(/paginationOptsValidator/s);\n if (hasPaginationOpts) {\n // Add paginationOpts as a synthetic argument\n args.push({\n name: 'paginationOpts',\n type: 'PaginationOptions',\n optional: false,\n description: 'Pagination options with cursor and numItems',\n });\n }\n\n return args;\n }\n\n private extractFunctionBlock(content: string, startIndex: number): string {\n const afterStart = content.slice(startIndex);\n const openIndex = afterStart.indexOf('{');\n if (openIndex === -1) {\n return afterStart;\n }\n\n let depth = 0;\n let inSingle = false;\n let inDouble = false;\n let inTemplate = false;\n let inLineComment = false;\n let inBlockComment = false;\n let escape = false;\n\n for (let i = openIndex; i < afterStart.length; i += 1) {\n const char = afterStart[i];\n const next = afterStart[i + 1];\n\n if (inLineComment) {\n if (char === '\\n') {\n inLineComment = false;\n }\n continue;\n }\n\n if (inBlockComment) {\n if (char === '*' && next === '/') {\n inBlockComment = false;\n i += 1;\n }\n continue;\n }\n\n if (inSingle) {\n if (escape) {\n escape = false;\n } else if (char === '\\\\') {\n escape = true;\n } else if (char === \"'\") {\n inSingle = false;\n }\n continue;\n }\n\n if (inDouble) {\n if (escape) {\n escape = false;\n } else if (char === '\\\\') {\n escape = true;\n } else if (char === '\"') {\n inDouble = false;\n }\n continue;\n }\n\n if (inTemplate) {\n if (escape) {\n escape = false;\n } else if (char === '\\\\') {\n escape = true;\n } else if (char === '`') {\n inTemplate = false;\n }\n continue;\n }\n\n if (char === '/' && next === '/') {\n inLineComment = true;\n i += 1;\n continue;\n }\n\n if (char === '/' && next === '*') {\n inBlockComment = true;\n i += 1;\n continue;\n }\n\n if (char === \"'\") {\n inSingle = true;\n escape = false;\n continue;\n }\n\n if (char === '\"') {\n inDouble = true;\n escape = false;\n continue;\n }\n\n if (char === '`') {\n inTemplate = true;\n escape = false;\n continue;\n }\n\n if (char === '{') {\n depth += 1;\n } else if (char === '}') {\n depth -= 1;\n if (depth === 0) {\n return afterStart.slice(openIndex, i + 1);\n }\n }\n }\n\n return afterStart;\n }\n\n private async parseSchemaFile(convexDir: string): Promise<TableInfo[]> {\n const tables: TableInfo[] = [];\n const schemaPath = path.join(convexDir, 'schema.ts');\n\n if (!fs.existsSync(schemaPath)) {\n return tables;\n }\n\n try {\n const content = fs.readFileSync(schemaPath, 'utf-8');\n const project = new Project({ useInMemoryFileSystem: true });\n const sourceFile = project.createSourceFile(\n 'schema.ts',\n content,\n { overwrite: true }\n );\n\n const defineSchemaCall = sourceFile\n .getDescendantsOfKind(SyntaxKind.CallExpression)\n .find(\n (call) => call.getExpression().getText() === 'defineSchema'\n );\n\n if (!defineSchemaCall) {\n return tables;\n }\n\n const schemaArg = defineSchemaCall.getArguments()[0];\n if (!schemaArg || !Node.isObjectLiteralExpression(schemaArg)) {\n return tables;\n }\n\n for (const prop of schemaArg.getProperties()) {\n if (!Node.isPropertyAssignment(prop)) continue;\n\n const tableName = this.normalizePropertyName(prop.getName());\n const initializer = prop.getInitializer();\n if (!initializer) continue;\n\n const defineTableCall = this.findDefineTableCall(initializer);\n if (!defineTableCall) continue;\n\n const fieldsArg = defineTableCall.getArguments()[0];\n const fields: FieldInfo[] = [];\n\n if (fieldsArg && Node.isObjectLiteralExpression(fieldsArg)) {\n for (const fieldProp of fieldsArg.getProperties()) {\n if (!Node.isPropertyAssignment(fieldProp)) continue;\n const fieldName = this.normalizePropertyName(fieldProp.getName());\n const fieldInit = fieldProp.getInitializer();\n if (!fieldInit) continue;\n\n const parsedField = this.parseFieldInfo(fieldInit);\n fields.push({\n name: fieldName,\n type: parsedField.type,\n optional: parsedField.optional,\n });\n }\n }\n\n tables.push({\n name: tableName,\n fields,\n });\n }\n } catch (error) {\n console.error('[SchemaWatcher] Error parsing schema file:', error);\n }\n\n return tables;\n }\n\n private findDefineTableCall(\n expr: Expression\n ): CallExpression | null {\n if (Node.isCallExpression(expr)) {\n const callee = expr.getExpression();\n if (Node.isIdentifier(callee) && callee.getText() === 'defineTable') {\n return expr;\n }\n if (Node.isPropertyAccessExpression(callee)) {\n const inner = callee.getExpression();\n if (Node.isCallExpression(inner)) {\n return this.findDefineTableCall(inner);\n }\n if (Node.isPropertyAccessExpression(inner)) {\n return this.findDefineTableCall(inner.getExpression());\n }\n }\n }\n\n if (Node.isPropertyAccessExpression(expr)) {\n return this.findDefineTableCall(expr.getExpression());\n }\n\n return null;\n }\n\n private parseFieldInfo(\n expr: Expression\n ): { type: string; optional: boolean } {\n let optional = false;\n let typeExpr: Expression | undefined = expr;\n\n if (Node.isCallExpression(expr)) {\n const calleeText = expr.getExpression().getText();\n if (calleeText === 'v.optional') {\n optional = true;\n const inner = expr.getArguments()[0];\n if (inner && Node.isExpression(inner)) {\n typeExpr = inner;\n }\n }\n }\n\n return {\n type: typeExpr?.getText() ?? 'unknown',\n optional,\n };\n }\n\n private normalizePropertyName(name: string): string {\n if (\n (name.startsWith(\"'\") && name.endsWith(\"'\")) ||\n (name.startsWith('\"') && name.endsWith('\"'))\n ) {\n return name.slice(1, -1);\n }\n return name;\n }\n}\n"],"mappings":"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","names":[]}