convex-devtools 1.0.1 → 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/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
- > 💡 **Note**: This tool is designed for **development and testing**. A deploy key is optional but enables identity mocking features.
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
  {
@@ -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(
@@ -333,13 +338,48 @@ var SchemaWatcher = class extends EventEmitter {
333
338
  }
334
339
  try {
335
340
  const content = fs.readFileSync(schemaPath, "utf-8");
336
- const tablePattern = /(\w+):\s*defineTable\(/g;
337
- let match;
338
- 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
+ }
339
380
  tables.push({
340
- name: match[1],
341
- fields: []
342
- // Could parse fields but keeping simple for now
381
+ name: tableName,
382
+ fields
343
383
  });
344
384
  }
345
385
  } catch (error) {
@@ -347,9 +387,54 @@ var SchemaWatcher = class extends EventEmitter {
347
387
  }
348
388
  return tables;
349
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
+ }
350
435
  };
351
436
 
352
437
  export {
353
438
  SchemaWatcher
354
439
  };
355
- //# sourceMappingURL=chunk-6H4WSVWY.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":[]}
@@ -5,6 +5,7 @@ import {
5
5
  // src/server/index.ts
6
6
  import cors from "cors";
7
7
  import express from "express";
8
+ import fs2 from "fs";
8
9
  import http from "http";
9
10
  import path2 from "path";
10
11
  import { fileURLToPath } from "url";
@@ -17,7 +18,8 @@ import path from "path";
17
18
  import initSqlJs from "sql.js";
18
19
  var DEFAULT_DATA = {
19
20
  collections: [],
20
- history: []
21
+ history: [],
22
+ dataHistory: []
21
23
  };
22
24
  var PersistenceDb = class _PersistenceDb {
23
25
  db;
@@ -73,7 +75,8 @@ var PersistenceDb = class _PersistenceDb {
73
75
  const parsed = JSON.parse(value);
74
76
  return {
75
77
  collections: Array.isArray(parsed.collections) ? parsed.collections : [],
76
- history: Array.isArray(parsed.history) ? parsed.history : []
78
+ history: Array.isArray(parsed.history) ? parsed.history : [],
79
+ dataHistory: Array.isArray(parsed.dataHistory) ? parsed.dataHistory : []
77
80
  };
78
81
  } catch {
79
82
  return DEFAULT_DATA;
@@ -82,7 +85,8 @@ var PersistenceDb = class _PersistenceDb {
82
85
  setData(data) {
83
86
  const payload = JSON.stringify({
84
87
  collections: Array.isArray(data.collections) ? data.collections : [],
85
- history: Array.isArray(data.history) ? data.history : []
88
+ history: Array.isArray(data.history) ? data.history : [],
89
+ dataHistory: Array.isArray(data.dataHistory) ? data.dataHistory : []
86
90
  });
87
91
  this.db.run(
88
92
  "INSERT INTO kv (key, value, updated_at) VALUES (?, ?, ?) ON CONFLICT(key) DO UPDATE SET value = excluded.value, updated_at = excluded.updated_at",
@@ -128,13 +132,184 @@ async function createServer(config) {
128
132
  res.json(persistenceDb.getData());
129
133
  });
130
134
  app.put("/api/persistence", (req, res) => {
131
- const { collections, history } = req.body || {};
135
+ const { collections, history, dataHistory } = req.body || {};
132
136
  persistenceDb.setData({
133
137
  collections: Array.isArray(collections) ? collections : [],
134
- history: Array.isArray(history) ? history : []
138
+ history: Array.isArray(history) ? history : [],
139
+ dataHistory: Array.isArray(dataHistory) ? dataHistory : []
135
140
  });
136
141
  res.json({ success: true });
137
142
  });
143
+ const devtoolsModulePath = path2.join(
144
+ config.projectDir,
145
+ "convex",
146
+ "devtools.ts"
147
+ );
148
+ const devtoolsModuleTemplate = `import { v } from "convex/values";
149
+ import { query } from "./_generated/server";
150
+
151
+ type FilterOp = "eq" | "neq" | "gt" | "gte" | "lt" | "lte";
152
+
153
+ export const runQuery = query({
154
+ args: {
155
+ table: v.string(),
156
+ filters: v.optional(
157
+ v.array(
158
+ v.object({
159
+ field: v.string(),
160
+ op: v.string(),
161
+ value: v.any(),
162
+ })
163
+ )
164
+ ),
165
+ order: v.optional(v.union(v.literal("asc"), v.literal("desc"))),
166
+ limit: v.optional(v.number()),
167
+ pagination: v.optional(
168
+ v.object({
169
+ cursor: v.union(v.string(), v.null()),
170
+ numItems: v.number(),
171
+ })
172
+ ),
173
+ },
174
+ handler: async (ctx, args) => {
175
+ let q = ctx.db.query(args.table as any);
176
+
177
+ if (args.filters && args.filters.length > 0) {
178
+ q = q.filter((qb) => {
179
+ const expressions = args.filters!.map((filter) => {
180
+ const field = qb.field(filter.field);
181
+ switch (filter.op as FilterOp) {
182
+ case "eq":
183
+ return qb.eq(field, filter.value);
184
+ case "neq":
185
+ return qb.neq(field, filter.value);
186
+ case "gt":
187
+ return qb.gt(field, filter.value);
188
+ case "gte":
189
+ return qb.gte(field, filter.value);
190
+ case "lt":
191
+ return qb.lt(field, filter.value);
192
+ case "lte":
193
+ return qb.lte(field, filter.value);
194
+ default:
195
+ throw new Error(\`Unsupported filter op: \${filter.op}\`);
196
+ }
197
+ });
198
+
199
+ if (expressions.length === 1) {
200
+ return expressions[0];
201
+ }
202
+
203
+ return qb.and(...expressions);
204
+ });
205
+ }
206
+
207
+ const ordered = args.order ? q.order(args.order) : q;
208
+
209
+ if (args.pagination) {
210
+ return await ordered.paginate({
211
+ cursor: args.pagination.cursor,
212
+ numItems: args.pagination.numItems,
213
+ });
214
+ }
215
+
216
+ if (args.limit !== undefined) {
217
+ return await ordered.take(args.limit);
218
+ }
219
+
220
+ return await ordered.collect();
221
+ },
222
+ });
223
+ `;
224
+ app.get("/api/devtools/status", (_req, res) => {
225
+ res.json({
226
+ installed: fs2.existsSync(devtoolsModulePath),
227
+ path: devtoolsModulePath
228
+ });
229
+ });
230
+ app.post("/api/devtools/install", (_req, res) => {
231
+ if (fs2.existsSync(devtoolsModulePath)) {
232
+ res.json({ installed: true, alreadyExisted: true });
233
+ return;
234
+ }
235
+ try {
236
+ fs2.writeFileSync(devtoolsModulePath, devtoolsModuleTemplate, "utf-8");
237
+ res.json({ installed: true, created: true });
238
+ } catch (error) {
239
+ res.status(500).json({
240
+ installed: false,
241
+ error: error?.message || "Failed to write devtools module"
242
+ });
243
+ }
244
+ });
245
+ app.post("/api/devtools/query", async (req, res) => {
246
+ const { table, filters, order, limit, pagination, jwtToken } = req.body || {};
247
+ if (!table) {
248
+ res.status(400).json({
249
+ error: "Missing table",
250
+ timestamp: (/* @__PURE__ */ new Date()).toISOString()
251
+ });
252
+ return;
253
+ }
254
+ if (!config.deployKey) {
255
+ res.status(403).json({
256
+ error: "CONVEX_DEPLOY_KEY is required to run devtools queries. Set it in .env.local and restart DevTools.",
257
+ timestamp: (/* @__PURE__ */ new Date()).toISOString()
258
+ });
259
+ return;
260
+ }
261
+ if (!fs2.existsSync(devtoolsModulePath)) {
262
+ res.status(428).json({
263
+ error: "Devtools query helper is not installed. Install it from the UI first.",
264
+ timestamp: (/* @__PURE__ */ new Date()).toISOString()
265
+ });
266
+ return;
267
+ }
268
+ try {
269
+ const startTime = Date.now();
270
+ const result = await convexClient.invoke(
271
+ "devtools:runQuery",
272
+ "query",
273
+ {
274
+ table,
275
+ filters,
276
+ order,
277
+ limit,
278
+ pagination
279
+ },
280
+ { jwtToken }
281
+ );
282
+ const duration = Date.now() - startTime;
283
+ let payload = result;
284
+ if (payload && typeof payload === "object" && "status" in payload && payload.status === "success" && "value" in payload) {
285
+ payload = payload.value;
286
+ }
287
+ let pageInfo = payload && typeof payload === "object" && "pageInfo" in payload ? payload.pageInfo : void 0;
288
+ if (!pageInfo && payload && typeof payload === "object" && "continueCursor" in payload) {
289
+ const cursor = payload.continueCursor ?? null;
290
+ const isDone = !!payload.isDone;
291
+ pageInfo = { cursor, hasNextPage: !isDone };
292
+ }
293
+ const data = payload && typeof payload === "object" && "page" in payload ? payload.page : payload;
294
+ res.json({
295
+ success: true,
296
+ result: data,
297
+ pageInfo,
298
+ duration,
299
+ timestamp: (/* @__PURE__ */ new Date()).toISOString()
300
+ });
301
+ } catch (error) {
302
+ res.json({
303
+ success: false,
304
+ error: {
305
+ message: error.message,
306
+ code: error.code,
307
+ data: error.data
308
+ },
309
+ timestamp: (/* @__PURE__ */ new Date()).toISOString()
310
+ });
311
+ }
312
+ });
138
313
  app.post("/api/invoke", async (req, res) => {
139
314
  const { functionPath, functionType, args, jwtToken } = req.body;
140
315
  if (!functionPath || !functionType) {
@@ -202,4 +377,4 @@ async function createServer(config) {
202
377
  export {
203
378
  createServer
204
379
  };
205
- //# sourceMappingURL=chunk-7RO6B2AJ.js.map
380
+ //# sourceMappingURL=chunk-C34UOOQW.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../src/server/index.ts","../../src/server/persistence-db.ts"],"sourcesContent":["import cors from 'cors';\nimport express from 'express';\nimport fs from 'fs';\nimport http from 'http';\nimport path from 'path';\nimport { fileURLToPath } from 'url';\nimport { WebSocket, WebSocketServer } from 'ws';\nimport { ConvexClient } from './convex-client.js';\nimport { PersistenceDb } from './persistence-db.js';\nimport { SchemaWatcher } from './schema-watcher.js';\n\nconst __dirname = path.dirname(fileURLToPath(import.meta.url));\n\nexport interface ServerConfig {\n port: number;\n projectDir: string;\n convexUrl: string;\n deployKey: string;\n schemaWatcher: SchemaWatcher;\n persistencePath?: string;\n}\n\nexport async function createServer(config: ServerConfig): Promise<http.Server> {\n const app = express();\n\n app.use(cors());\n app.use(express.json({ limit: '10mb' }));\n\n // Serve static UI files in production\n const uiPath = path.join(__dirname, '..', 'ui');\n app.use(express.static(uiPath));\n\n // Create Convex client\n const convexClient = new ConvexClient(config.convexUrl, config.deployKey);\n\n // Shared persistence database (collections/history)\n const persistenceDb = await PersistenceDb.create(\n config.persistencePath ??\n path.join(config.projectDir, '.convex-devtools', 'devtools.sqlite')\n );\n\n // API Routes\n app.get('/api/schema', (_req, res) => {\n const schema = config.schemaWatcher.getSchema();\n if (!schema) {\n res.status(503).json({ error: 'Schema not yet loaded' });\n return;\n }\n res.json(schema);\n });\n\n app.get('/api/health', (_req, res) => {\n res.json({\n status: 'ok',\n convexUrl: config.convexUrl,\n projectDir: config.projectDir,\n projectName: path.basename(config.projectDir),\n });\n });\n\n app.get('/api/persistence', (_req, res) => {\n res.json(persistenceDb.getData());\n });\n\n app.put('/api/persistence', (req, res) => {\n const { collections, history, dataHistory } = req.body || {};\n persistenceDb.setData({\n collections: Array.isArray(collections) ? collections : [],\n history: Array.isArray(history) ? history : [],\n dataHistory: Array.isArray(dataHistory) ? dataHistory : [],\n });\n res.json({ success: true });\n });\n\n const devtoolsModulePath = path.join(\n config.projectDir,\n 'convex',\n 'devtools.ts'\n );\n\n const devtoolsModuleTemplate = `import { v } from \"convex/values\";\nimport { query } from \"./_generated/server\";\n\ntype FilterOp = \"eq\" | \"neq\" | \"gt\" | \"gte\" | \"lt\" | \"lte\";\n\nexport const runQuery = query({\n args: {\n table: v.string(),\n filters: v.optional(\n v.array(\n v.object({\n field: v.string(),\n op: v.string(),\n value: v.any(),\n })\n )\n ),\n order: v.optional(v.union(v.literal(\"asc\"), v.literal(\"desc\"))),\n limit: v.optional(v.number()),\n pagination: v.optional(\n v.object({\n cursor: v.union(v.string(), v.null()),\n numItems: v.number(),\n })\n ),\n },\n handler: async (ctx, args) => {\n let q = ctx.db.query(args.table as any);\n\n if (args.filters && args.filters.length > 0) {\n q = q.filter((qb) => {\n const expressions = args.filters!.map((filter) => {\n const field = qb.field(filter.field);\n switch (filter.op as FilterOp) {\n case \"eq\":\n return qb.eq(field, filter.value);\n case \"neq\":\n return qb.neq(field, filter.value);\n case \"gt\":\n return qb.gt(field, filter.value);\n case \"gte\":\n return qb.gte(field, filter.value);\n case \"lt\":\n return qb.lt(field, filter.value);\n case \"lte\":\n return qb.lte(field, filter.value);\n default:\n throw new Error(\\`Unsupported filter op: \\${filter.op}\\`);\n }\n });\n\n if (expressions.length === 1) {\n return expressions[0];\n }\n\n return qb.and(...expressions);\n });\n }\n\n const ordered = args.order ? q.order(args.order) : q;\n\n if (args.pagination) {\n return await ordered.paginate({\n cursor: args.pagination.cursor,\n numItems: args.pagination.numItems,\n });\n }\n\n if (args.limit !== undefined) {\n return await ordered.take(args.limit);\n }\n\n return await ordered.collect();\n },\n});\n`;\n\n app.get('/api/devtools/status', (_req, res) => {\n res.json({\n installed: fs.existsSync(devtoolsModulePath),\n path: devtoolsModulePath,\n });\n });\n\n app.post('/api/devtools/install', (_req, res) => {\n if (fs.existsSync(devtoolsModulePath)) {\n res.json({ installed: true, alreadyExisted: true });\n return;\n }\n\n try {\n fs.writeFileSync(devtoolsModulePath, devtoolsModuleTemplate, 'utf-8');\n res.json({ installed: true, created: true });\n } catch (error: any) {\n res.status(500).json({\n installed: false,\n error: error?.message || 'Failed to write devtools module',\n });\n }\n });\n\n app.post('/api/devtools/query', async (req, res) => {\n const { table, filters, order, limit, pagination, jwtToken } =\n req.body || {};\n\n if (!table) {\n res.status(400).json({\n error: 'Missing table',\n timestamp: new Date().toISOString(),\n });\n return;\n }\n\n if (!config.deployKey) {\n res.status(403).json({\n error:\n 'CONVEX_DEPLOY_KEY is required to run devtools queries. Set it in .env.local and restart DevTools.',\n timestamp: new Date().toISOString(),\n });\n return;\n }\n\n if (!fs.existsSync(devtoolsModulePath)) {\n res.status(428).json({\n error:\n 'Devtools query helper is not installed. Install it from the UI first.',\n timestamp: new Date().toISOString(),\n });\n return;\n }\n\n try {\n const startTime = Date.now();\n const result = await convexClient.invoke(\n 'devtools:runQuery',\n 'query',\n {\n table,\n filters,\n order,\n limit,\n pagination,\n },\n { jwtToken }\n );\n const duration = Date.now() - startTime;\n\n let payload: unknown = result;\n\n if (\n payload &&\n typeof payload === 'object' &&\n 'status' in (payload as Record<string, unknown>) &&\n (payload as { status?: string }).status === 'success' &&\n 'value' in (payload as Record<string, unknown>)\n ) {\n payload = (payload as { value?: unknown }).value;\n }\n\n let pageInfo =\n payload &&\n typeof payload === 'object' &&\n 'pageInfo' in (payload as Record<string, unknown>)\n ? (payload as { pageInfo?: unknown }).pageInfo\n : undefined;\n\n if (\n !pageInfo &&\n payload &&\n typeof payload === 'object' &&\n 'continueCursor' in (payload as Record<string, unknown>)\n ) {\n const cursor = (payload as { continueCursor?: string | null })\n .continueCursor ?? null;\n const isDone = !!(payload as { isDone?: boolean }).isDone;\n pageInfo = { cursor, hasNextPage: !isDone };\n }\n\n const data =\n payload &&\n typeof payload === 'object' &&\n 'page' in (payload as Record<string, unknown>)\n ? (payload as { page?: unknown }).page\n : payload;\n\n res.json({\n success: true,\n result: data,\n pageInfo,\n duration,\n timestamp: new Date().toISOString(),\n });\n } catch (error: any) {\n res.json({\n success: false,\n error: {\n message: error.message,\n code: error.code,\n data: error.data,\n },\n timestamp: new Date().toISOString(),\n });\n }\n });\n\n // Invoke a function\n app.post('/api/invoke', async (req, res) => {\n const { functionPath, functionType, args, jwtToken } = req.body;\n\n if (!functionPath || !functionType) {\n res.status(400).json({ error: 'Missing functionPath or functionType' });\n return;\n }\n\n try {\n const startTime = Date.now();\n const result = await convexClient.invoke(\n functionPath,\n functionType,\n args || {},\n { jwtToken }\n );\n const duration = Date.now() - startTime;\n\n res.json({\n success: true,\n result,\n duration,\n timestamp: new Date().toISOString(),\n });\n } catch (error: any) {\n res.json({\n success: false,\n error: {\n message: error.message,\n code: error.code,\n data: error.data,\n },\n timestamp: new Date().toISOString(),\n });\n }\n });\n\n // Create HTTP server\n const server = http.createServer(app);\n\n // WebSocket for real-time schema updates\n const wss = new WebSocketServer({ server, path: '/ws' });\n\n const clients = new Set<WebSocket>();\n\n wss.on('connection', (ws) => {\n clients.add(ws);\n\n // Send initial schema\n const schema = config.schemaWatcher.getSchema();\n if (schema) {\n ws.send(JSON.stringify({ type: 'schema', data: schema }));\n }\n\n ws.on('close', () => {\n clients.delete(ws);\n });\n });\n\n // Broadcast schema updates\n config.schemaWatcher.on('schema-updated', (schema) => {\n const message = JSON.stringify({ type: 'schema', data: schema });\n for (const client of clients) {\n if (client.readyState === WebSocket.OPEN) {\n client.send(message);\n }\n }\n });\n\n // SPA fallback - serve index.html for non-API routes\n app.get('*', (_req, res) => {\n res.sendFile(path.join(uiPath, 'index.html'));\n });\n\n return new Promise((resolve) => {\n server.listen(config.port, () => {\n resolve(server);\n });\n });\n}\n","import fs from 'fs';\nimport { createRequire } from 'module';\nimport path from 'path';\nimport initSqlJs, { Database, SqlJsStatic } from 'sql.js';\n\nexport interface PersistenceData {\n collections: unknown[];\n history: unknown[];\n dataHistory: unknown[];\n}\n\nconst DEFAULT_DATA: PersistenceData = {\n collections: [],\n history: [],\n dataHistory: [],\n};\n\nexport class PersistenceDb {\n private db: Database;\n private dbFilePath: string;\n private SQL: SqlJsStatic;\n\n private constructor(db: Database, dbFilePath: string, SQL: SqlJsStatic) {\n this.db = db;\n this.dbFilePath = dbFilePath;\n this.SQL = SQL;\n this.db.exec(\n 'CREATE TABLE IF NOT EXISTS kv (key TEXT PRIMARY KEY, value TEXT NOT NULL, updated_at TEXT NOT NULL)'\n );\n }\n\n static async create(dbFilePath: string): Promise<PersistenceDb> {\n const dir = path.dirname(dbFilePath);\n if (!fs.existsSync(dir)) {\n fs.mkdirSync(dir, { recursive: true });\n }\n\n const require = createRequire(import.meta.url);\n const wasmPath = require.resolve('sql.js/dist/sql-wasm.wasm');\n\n const SQL: SqlJsStatic = await initSqlJs({\n locateFile: () => wasmPath,\n });\n\n let db: Database;\n if (fs.existsSync(dbFilePath)) {\n const fileBuffer = fs.readFileSync(dbFilePath);\n db = new SQL.Database(new Uint8Array(fileBuffer));\n } else {\n db = new SQL.Database();\n }\n\n return new PersistenceDb(db, dbFilePath, SQL);\n }\n\n /** Reload database from disk to pick up changes from other processes */\n private reloadFromDisk(): void {\n if (fs.existsSync(this.dbFilePath)) {\n const fileBuffer = fs.readFileSync(this.dbFilePath);\n this.db = new this.SQL.Database(new Uint8Array(fileBuffer));\n }\n }\n\n getData(): PersistenceData {\n // Reload from disk to get latest data from other server instances\n this.reloadFromDisk();\n\n const result = this.db.exec(\n \"SELECT value FROM kv WHERE key = 'persistence'\"\n );\n\n if (!result.length || !result[0].values.length) {\n return DEFAULT_DATA;\n }\n\n const value = result[0].values[0][0];\n if (typeof value !== 'string') {\n return DEFAULT_DATA;\n }\n\n try {\n const parsed = JSON.parse(value) as PersistenceData;\n return {\n collections: Array.isArray(parsed.collections)\n ? parsed.collections\n : [],\n history: Array.isArray(parsed.history) ? parsed.history : [],\n dataHistory: Array.isArray(parsed.dataHistory) ? parsed.dataHistory : [],\n };\n } catch {\n return DEFAULT_DATA;\n }\n }\n\n setData(data: PersistenceData): void {\n const payload = JSON.stringify({\n collections: Array.isArray(data.collections) ? data.collections : [],\n history: Array.isArray(data.history) ? data.history : [],\n dataHistory: Array.isArray(data.dataHistory) ? data.dataHistory : [],\n });\n\n this.db.run(\n 'INSERT INTO kv (key, value, updated_at) VALUES (?, ?, ?) ON CONFLICT(key) DO UPDATE SET value = excluded.value, updated_at = excluded.updated_at',\n ['persistence', payload, new Date().toISOString()]\n );\n\n this.persistToDisk();\n }\n\n private persistToDisk(): void {\n const data = this.db.export();\n fs.writeFileSync(this.dbFilePath, Buffer.from(data));\n }\n}\n"],"mappings":"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","names":["fs","path","require","path","fs"]}
package/dist/cli/index.js CHANGED
@@ -1,10 +1,10 @@
1
1
  #!/usr/bin/env node
2
2
  import {
3
3
  createServer
4
- } from "./chunk-7RO6B2AJ.js";
4
+ } from "./chunk-C34UOOQW.js";
5
5
  import {
6
6
  SchemaWatcher
7
- } from "./chunk-6H4WSVWY.js";
7
+ } from "./chunk-7LFMAA6L.js";
8
8
  import "./chunk-5B55IAKY.js";
9
9
 
10
10
  // src/cli/index.ts
@@ -16,7 +16,7 @@ import open from "open";
16
16
  import os from "os";
17
17
  import path from "path";
18
18
  var program = new Command();
19
- program.name("convex-devtools").description("A standalone development tool for testing Convex functions").version("1.0.1");
19
+ program.name("convex-devtools").description("A standalone development tool for testing Convex functions").version("1.1.0");
20
20
  program.option("-p, --port <number>", "Port for the devtools server", "5173").option("-d, --dir <path>", "Path to Convex project directory", ".").option(
21
21
  "--storage <mode>",
22
22
  "Storage scope: project (default), global, or path",
@@ -77,7 +77,7 @@ program.option("-p, --port <number>", "Port for the devtools server", "5173").op
77
77
  }
78
78
  console.log(chalk.cyan("\u2554\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2557"));
79
79
  console.log(
80
- chalk.cyan("\u2551") + chalk.white.bold(" Convex DevTools v1.0.0 ") + chalk.cyan("\u2551")
80
+ chalk.cyan("\u2551") + chalk.white.bold(" Convex DevTools v1.1.0 ") + chalk.cyan("\u2551")
81
81
  );
82
82
  console.log(chalk.cyan("\u255A\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u255D"));
83
83
  console.log();
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/cli/index.ts"],"sourcesContent":["#!/usr/bin/env node\n\nimport chalk from 'chalk';\nimport { Command } from 'commander';\nimport dotenv from 'dotenv';\nimport fs from 'fs';\nimport open from 'open';\nimport os from 'os';\nimport path from 'path';\nimport { createServer } from '../server/index.js';\nimport { SchemaWatcher } from '../server/schema-watcher.js';\n\nconst program = new Command();\n\nprogram\n .name('convex-devtools')\n .description('A standalone development tool for testing Convex functions')\n .version('1.0.1');\n\nprogram\n .option('-p, --port <number>', 'Port for the devtools server', '5173')\n .option('-d, --dir <path>', 'Path to Convex project directory', '.')\n .option(\n '--storage <mode>',\n 'Storage scope: project (default), global, or path',\n 'project'\n )\n .option(\n '--storage-path <path>',\n 'Custom storage path when using --storage path'\n )\n .option('--no-open', 'Do not open browser automatically')\n .action(async (options) => {\n const projectDir = path.resolve(options.dir);\n\n // Load .env.local from the project directory\n const envLocalPath = path.join(projectDir, '.env.local');\n const envPath = path.join(projectDir, '.env');\n\n if (fs.existsSync(envLocalPath)) {\n dotenv.config({ path: envLocalPath });\n } else if (fs.existsSync(envPath)) {\n dotenv.config({ path: envPath });\n }\n\n // Check for required environment variables\n if (process.env.CONVEX_DEVTOOLS_ENABLED !== 'true') {\n console.error(\n chalk.red('✗ CONVEX_DEVTOOLS_ENABLED is not set to \"true\"')\n );\n console.error(\n chalk.yellow(\n ' Add CONVEX_DEVTOOLS_ENABLED=true to your .env.local file'\n )\n );\n console.error(\n chalk.yellow(' This tool is intended for local development only.')\n );\n process.exit(1);\n }\n\n const convexUrl = process.env.CONVEX_URL;\n if (!convexUrl) {\n console.error(chalk.red('✗ CONVEX_URL is not set'));\n console.error(\n chalk.yellow(\n ' Make sure you have a valid Convex deployment URL in your .env.local'\n )\n );\n process.exit(1);\n }\n\n // Deploy key is optional for local development\n // Without it, identity mocking won't work but you can still invoke functions\n const deployKey = process.env.CONVEX_DEPLOY_KEY || '';\n if (!deployKey) {\n console.log(chalk.yellow('⚠ CONVEX_DEPLOY_KEY is not set'));\n console.log(chalk.yellow(' Identity mocking will be disabled.'));\n console.log(\n chalk.yellow(\n ' To enable, add to .env.local: CONVEX_DEPLOY_KEY=prod:xxx or dev:xxx'\n )\n );\n console.log();\n }\n\n // Check for convex/_generated directory\n const generatedDir = path.join(projectDir, 'convex', '_generated');\n if (!fs.existsSync(generatedDir)) {\n console.error(chalk.red('✗ Convex generated files not found'));\n console.error(chalk.yellow(` Expected: ${generatedDir}`));\n console.error(chalk.yellow(' Run \"npx convex dev\" to generate them.'));\n process.exit(1);\n }\n\n console.log(chalk.cyan('╔══════════════════════════════════════════╗'));\n console.log(\n chalk.cyan('║') +\n chalk.white.bold(' Convex DevTools v1.0.0 ') +\n chalk.cyan('║')\n );\n console.log(chalk.cyan('╚══════════════════════════════════════════╝'));\n console.log();\n console.log(chalk.green('✓') + ' Environment validated');\n console.log(chalk.green('✓') + ` Convex URL: ${chalk.dim(convexUrl)}`);\n console.log(chalk.green('✓') + ` Project: ${chalk.dim(projectDir)}`);\n console.log();\n\n const storageMode = String(options.storage || 'project');\n let persistencePath: string | undefined;\n\n if (storageMode === 'project') {\n persistencePath = path.join(\n projectDir,\n '.convex-devtools',\n 'devtools.sqlite'\n );\n } else if (storageMode === 'global') {\n persistencePath = path.join(\n os.homedir(),\n '.convex-devtools',\n 'devtools.sqlite'\n );\n } else if (storageMode === 'path') {\n if (!options.storagePath) {\n console.error(\n chalk.red('✗ --storage path requires --storage-path <path>')\n );\n process.exit(1);\n }\n persistencePath = path.resolve(String(options.storagePath));\n } else {\n console.error(\n chalk.red('✗ Invalid --storage value. Use project, global, or path.')\n );\n process.exit(1);\n }\n\n // Start schema watcher\n const schemaWatcher = new SchemaWatcher(projectDir);\n await schemaWatcher.start();\n\n // Start server\n const port = parseInt(options.port, 10);\n const server = await createServer({\n port,\n projectDir,\n convexUrl,\n deployKey,\n schemaWatcher,\n persistencePath,\n });\n\n console.log(\n chalk.green('✓') +\n ` DevTools running at ${chalk.cyan(`http://localhost:${port}`)}`\n );\n console.log();\n console.log(chalk.dim('Press Ctrl+C to stop'));\n\n if (options.open !== false) {\n await open(`http://localhost:${port}`);\n }\n\n // Handle shutdown\n process.on('SIGINT', async () => {\n console.log(chalk.dim('\\nShutting down...'));\n schemaWatcher.stop();\n server.close();\n process.exit(0);\n });\n });\n\nprogram.parse();\n"],"mappings":"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","names":[]}
1
+ {"version":3,"sources":["../../src/cli/index.ts"],"sourcesContent":["#!/usr/bin/env node\n\nimport chalk from 'chalk';\nimport { Command } from 'commander';\nimport dotenv from 'dotenv';\nimport fs from 'fs';\nimport open from 'open';\nimport os from 'os';\nimport path from 'path';\nimport { createServer } from '../server/index.js';\nimport { SchemaWatcher } from '../server/schema-watcher.js';\n\nconst program = new Command();\n\nprogram\n .name('convex-devtools')\n .description('A standalone development tool for testing Convex functions')\n .version('1.1.0');\n\nprogram\n .option('-p, --port <number>', 'Port for the devtools server', '5173')\n .option('-d, --dir <path>', 'Path to Convex project directory', '.')\n .option(\n '--storage <mode>',\n 'Storage scope: project (default), global, or path',\n 'project'\n )\n .option(\n '--storage-path <path>',\n 'Custom storage path when using --storage path'\n )\n .option('--no-open', 'Do not open browser automatically')\n .action(async (options) => {\n const projectDir = path.resolve(options.dir);\n\n // Load .env.local from the project directory\n const envLocalPath = path.join(projectDir, '.env.local');\n const envPath = path.join(projectDir, '.env');\n\n if (fs.existsSync(envLocalPath)) {\n dotenv.config({ path: envLocalPath });\n } else if (fs.existsSync(envPath)) {\n dotenv.config({ path: envPath });\n }\n\n // Check for required environment variables\n if (process.env.CONVEX_DEVTOOLS_ENABLED !== 'true') {\n console.error(\n chalk.red('✗ CONVEX_DEVTOOLS_ENABLED is not set to \"true\"')\n );\n console.error(\n chalk.yellow(\n ' Add CONVEX_DEVTOOLS_ENABLED=true to your .env.local file'\n )\n );\n console.error(\n chalk.yellow(' This tool is intended for local development only.')\n );\n process.exit(1);\n }\n\n const convexUrl = process.env.CONVEX_URL;\n if (!convexUrl) {\n console.error(chalk.red('✗ CONVEX_URL is not set'));\n console.error(\n chalk.yellow(\n ' Make sure you have a valid Convex deployment URL in your .env.local'\n )\n );\n process.exit(1);\n }\n\n // Deploy key is optional for local development\n // Without it, identity mocking won't work but you can still invoke functions\n const deployKey = process.env.CONVEX_DEPLOY_KEY || '';\n if (!deployKey) {\n console.log(chalk.yellow('⚠ CONVEX_DEPLOY_KEY is not set'));\n console.log(chalk.yellow(' Identity mocking will be disabled.'));\n console.log(\n chalk.yellow(\n ' To enable, add to .env.local: CONVEX_DEPLOY_KEY=prod:xxx or dev:xxx'\n )\n );\n console.log();\n }\n\n // Check for convex/_generated directory\n const generatedDir = path.join(projectDir, 'convex', '_generated');\n if (!fs.existsSync(generatedDir)) {\n console.error(chalk.red('✗ Convex generated files not found'));\n console.error(chalk.yellow(` Expected: ${generatedDir}`));\n console.error(chalk.yellow(' Run \"npx convex dev\" to generate them.'));\n process.exit(1);\n }\n\n console.log(chalk.cyan('╔══════════════════════════════════════════╗'));\n console.log(\n chalk.cyan('║') +\n chalk.white.bold(' Convex DevTools v1.1.0 ') +\n chalk.cyan('║')\n );\n console.log(chalk.cyan('╚══════════════════════════════════════════╝'));\n console.log();\n console.log(chalk.green('✓') + ' Environment validated');\n console.log(chalk.green('✓') + ` Convex URL: ${chalk.dim(convexUrl)}`);\n console.log(chalk.green('✓') + ` Project: ${chalk.dim(projectDir)}`);\n console.log();\n\n const storageMode = String(options.storage || 'project');\n let persistencePath: string | undefined;\n\n if (storageMode === 'project') {\n persistencePath = path.join(\n projectDir,\n '.convex-devtools',\n 'devtools.sqlite'\n );\n } else if (storageMode === 'global') {\n persistencePath = path.join(\n os.homedir(),\n '.convex-devtools',\n 'devtools.sqlite'\n );\n } else if (storageMode === 'path') {\n if (!options.storagePath) {\n console.error(\n chalk.red('✗ --storage path requires --storage-path <path>')\n );\n process.exit(1);\n }\n persistencePath = path.resolve(String(options.storagePath));\n } else {\n console.error(\n chalk.red('✗ Invalid --storage value. Use project, global, or path.')\n );\n process.exit(1);\n }\n\n // Start schema watcher\n const schemaWatcher = new SchemaWatcher(projectDir);\n await schemaWatcher.start();\n\n // Start server\n const port = parseInt(options.port, 10);\n const server = await createServer({\n port,\n projectDir,\n convexUrl,\n deployKey,\n schemaWatcher,\n persistencePath,\n });\n\n console.log(\n chalk.green('✓') +\n ` DevTools running at ${chalk.cyan(`http://localhost:${port}`)}`\n );\n console.log();\n console.log(chalk.dim('Press Ctrl+C to stop'));\n\n if (options.open !== false) {\n await open(`http://localhost:${port}`);\n }\n\n // Handle shutdown\n process.on('SIGINT', async () => {\n console.log(chalk.dim('\\nShutting down...'));\n schemaWatcher.stop();\n server.close();\n process.exit(0);\n });\n });\n\nprogram.parse();\n"],"mappings":"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","names":[]}
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  createServer
3
- } from "../chunk-7RO6B2AJ.js";
3
+ } from "../chunk-C34UOOQW.js";
4
4
  import "../chunk-5B55IAKY.js";
5
5
  export {
6
6
  createServer
@@ -23,7 +23,7 @@ interface ModuleInfo {
23
23
  interface SchemaInfo {
24
24
  modules: ModuleInfo[];
25
25
  tables: TableInfo[];
26
- lastUpdated: Date;
26
+ lastUpdated: string;
27
27
  }
28
28
  interface TableInfo {
29
29
  name: string;
@@ -52,6 +52,9 @@ declare class SchemaWatcher extends EventEmitter {
52
52
  private extractArgsFromPosition;
53
53
  private extractFunctionBlock;
54
54
  private parseSchemaFile;
55
+ private findDefineTableCall;
56
+ private parseFieldInfo;
57
+ private normalizePropertyName;
55
58
  }
56
59
 
57
60
  export { type ArgInfo, type FieldInfo, type FunctionInfo, type ModuleInfo, type SchemaInfo, SchemaWatcher, type TableInfo };
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  SchemaWatcher
3
- } from "../chunk-6H4WSVWY.js";
3
+ } from "../chunk-7LFMAA6L.js";
4
4
  export {
5
5
  SchemaWatcher
6
6
  };