convex-devtools 1.0.0 → 1.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +29 -3
- package/dist/cli/{chunk-4PBPPYEF.js → chunk-5B55IAKY.js} +2 -2
- package/dist/cli/{chunk-4PBPPYEF.js.map → chunk-5B55IAKY.js.map} +1 -1
- package/dist/cli/{chunk-ENBIFEKV.js → chunk-6H4WSVWY.js} +99 -4
- package/dist/cli/chunk-6H4WSVWY.js.map +1 -0
- package/dist/cli/{chunk-75H32MO3.js → chunk-7RO6B2AJ.js} +2 -2
- package/dist/cli/index.js +4 -4
- package/dist/cli/index.js.map +1 -1
- package/dist/cli/server/convex-client.js +1 -1
- package/dist/cli/server/index.js +2 -2
- package/dist/cli/server/schema-watcher.d.ts +1 -0
- package/dist/cli/server/schema-watcher.js +1 -1
- package/package.json +1 -1
- package/dist/cli/chunk-ENBIFEKV.js.map +0 -1
- /package/dist/cli/{chunk-75H32MO3.js.map → chunk-7RO6B2AJ.js.map} +0 -0
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
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
|
|
9
9
|
A standalone development tool for testing Convex queries, mutations, and actions with identity mocking, request saving, and auto-reloading schema discovery.
|
|
10
10
|
|
|
11
|
-
>
|
|
11
|
+
> 💡 **Note**: This tool is designed for **development and testing**. A deploy key is optional but enables identity mocking features.
|
|
12
12
|
|
|
13
13
|
## Features
|
|
14
14
|
|
|
@@ -275,6 +275,10 @@ npm publish
|
|
|
275
275
|
npm publish --access public
|
|
276
276
|
```
|
|
277
277
|
|
|
278
|
+
> Note: If your npm account requires 2FA for publishing, you can either enable it with
|
|
279
|
+
> `npm profile enable-2fa auth-only` (then publish with your OTP), or use a granular
|
|
280
|
+
> access token with “bypass 2FA for publish” enabled.
|
|
281
|
+
|
|
278
282
|
### Testing Before Publishing
|
|
279
283
|
|
|
280
284
|
```bash
|
|
@@ -328,9 +332,31 @@ Use the `--port` flag to specify a different port:
|
|
|
328
332
|
convex-devtools --port 3001
|
|
329
333
|
```
|
|
330
334
|
|
|
335
|
+
### "command not found: convex-devtools" after global install
|
|
336
|
+
|
|
337
|
+
If you installed globally but the command isn't found, npm's global bin directory may not be in your PATH.
|
|
338
|
+
|
|
339
|
+
1. Find your npm global bin path:
|
|
340
|
+
|
|
341
|
+
```bash
|
|
342
|
+
npm config get prefix
|
|
343
|
+
```
|
|
344
|
+
|
|
345
|
+
2. Add the bin folder to your PATH. For example, on macOS/Linux with zsh:
|
|
346
|
+
|
|
347
|
+
```bash
|
|
348
|
+
echo 'export PATH="$(npm config get prefix)/bin:$PATH"' >> ~/.zshrc
|
|
349
|
+
source ~/.zshrc
|
|
350
|
+
```
|
|
351
|
+
|
|
352
|
+
3. Alternatively, use `npx` which doesn't require PATH setup:
|
|
353
|
+
```bash
|
|
354
|
+
npx convex-devtools
|
|
355
|
+
```
|
|
356
|
+
|
|
331
357
|
## Contributing
|
|
332
358
|
|
|
333
|
-
Contributions are welcome! Please
|
|
359
|
+
Contributions are welcome! Please read our [Contributing Guide](CONTRIBUTING.md) before submitting a Pull Request.
|
|
334
360
|
|
|
335
361
|
1. Fork the repository
|
|
336
362
|
2. Create your feature branch (`git checkout -b feature/amazing-feature`)
|
|
@@ -340,7 +366,7 @@ Contributions are welcome! Please feel free to submit a Pull Request.
|
|
|
340
366
|
|
|
341
367
|
## License
|
|
342
368
|
|
|
343
|
-
MIT
|
|
369
|
+
MIT License - see the [LICENSE](LICENSE) file for details.
|
|
344
370
|
|
|
345
371
|
## Related Projects
|
|
346
372
|
|
|
@@ -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.
|
|
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-
|
|
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.
|
|
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"]}
|
|
@@ -201,8 +201,8 @@ var SchemaWatcher = class extends EventEmitter {
|
|
|
201
201
|
extractArgsFromPosition(content, startIndex, jsdocComment = "") {
|
|
202
202
|
const args = [];
|
|
203
203
|
const jsdocParams = this.parseJSDocParams(jsdocComment);
|
|
204
|
-
const
|
|
205
|
-
const argsMatch =
|
|
204
|
+
const functionBlock = this.extractFunctionBlock(content, startIndex);
|
|
205
|
+
const argsMatch = functionBlock.match(/args:\s*\{([^}]*)\}/s);
|
|
206
206
|
if (argsMatch) {
|
|
207
207
|
const argsContent = argsMatch[1];
|
|
208
208
|
const argPattern = /(\w+):\s*(v\.optional\()?v\.(\w+)/g;
|
|
@@ -219,7 +219,7 @@ var SchemaWatcher = class extends EventEmitter {
|
|
|
219
219
|
});
|
|
220
220
|
}
|
|
221
221
|
}
|
|
222
|
-
const hasPaginationOpts =
|
|
222
|
+
const hasPaginationOpts = functionBlock.match(/paginationOptsValidator/s);
|
|
223
223
|
if (hasPaginationOpts) {
|
|
224
224
|
args.push({
|
|
225
225
|
name: "paginationOpts",
|
|
@@ -230,6 +230,101 @@ var SchemaWatcher = class extends EventEmitter {
|
|
|
230
230
|
}
|
|
231
231
|
return args;
|
|
232
232
|
}
|
|
233
|
+
extractFunctionBlock(content, startIndex) {
|
|
234
|
+
const afterStart = content.slice(startIndex);
|
|
235
|
+
const openIndex = afterStart.indexOf("{");
|
|
236
|
+
if (openIndex === -1) {
|
|
237
|
+
return afterStart;
|
|
238
|
+
}
|
|
239
|
+
let depth = 0;
|
|
240
|
+
let inSingle = false;
|
|
241
|
+
let inDouble = false;
|
|
242
|
+
let inTemplate = false;
|
|
243
|
+
let inLineComment = false;
|
|
244
|
+
let inBlockComment = false;
|
|
245
|
+
let escape = false;
|
|
246
|
+
for (let i = openIndex; i < afterStart.length; i += 1) {
|
|
247
|
+
const char = afterStart[i];
|
|
248
|
+
const next = afterStart[i + 1];
|
|
249
|
+
if (inLineComment) {
|
|
250
|
+
if (char === "\n") {
|
|
251
|
+
inLineComment = false;
|
|
252
|
+
}
|
|
253
|
+
continue;
|
|
254
|
+
}
|
|
255
|
+
if (inBlockComment) {
|
|
256
|
+
if (char === "*" && next === "/") {
|
|
257
|
+
inBlockComment = false;
|
|
258
|
+
i += 1;
|
|
259
|
+
}
|
|
260
|
+
continue;
|
|
261
|
+
}
|
|
262
|
+
if (inSingle) {
|
|
263
|
+
if (escape) {
|
|
264
|
+
escape = false;
|
|
265
|
+
} else if (char === "\\") {
|
|
266
|
+
escape = true;
|
|
267
|
+
} else if (char === "'") {
|
|
268
|
+
inSingle = false;
|
|
269
|
+
}
|
|
270
|
+
continue;
|
|
271
|
+
}
|
|
272
|
+
if (inDouble) {
|
|
273
|
+
if (escape) {
|
|
274
|
+
escape = false;
|
|
275
|
+
} else if (char === "\\") {
|
|
276
|
+
escape = true;
|
|
277
|
+
} else if (char === '"') {
|
|
278
|
+
inDouble = false;
|
|
279
|
+
}
|
|
280
|
+
continue;
|
|
281
|
+
}
|
|
282
|
+
if (inTemplate) {
|
|
283
|
+
if (escape) {
|
|
284
|
+
escape = false;
|
|
285
|
+
} else if (char === "\\") {
|
|
286
|
+
escape = true;
|
|
287
|
+
} else if (char === "`") {
|
|
288
|
+
inTemplate = false;
|
|
289
|
+
}
|
|
290
|
+
continue;
|
|
291
|
+
}
|
|
292
|
+
if (char === "/" && next === "/") {
|
|
293
|
+
inLineComment = true;
|
|
294
|
+
i += 1;
|
|
295
|
+
continue;
|
|
296
|
+
}
|
|
297
|
+
if (char === "/" && next === "*") {
|
|
298
|
+
inBlockComment = true;
|
|
299
|
+
i += 1;
|
|
300
|
+
continue;
|
|
301
|
+
}
|
|
302
|
+
if (char === "'") {
|
|
303
|
+
inSingle = true;
|
|
304
|
+
escape = false;
|
|
305
|
+
continue;
|
|
306
|
+
}
|
|
307
|
+
if (char === '"') {
|
|
308
|
+
inDouble = true;
|
|
309
|
+
escape = false;
|
|
310
|
+
continue;
|
|
311
|
+
}
|
|
312
|
+
if (char === "`") {
|
|
313
|
+
inTemplate = true;
|
|
314
|
+
escape = false;
|
|
315
|
+
continue;
|
|
316
|
+
}
|
|
317
|
+
if (char === "{") {
|
|
318
|
+
depth += 1;
|
|
319
|
+
} else if (char === "}") {
|
|
320
|
+
depth -= 1;
|
|
321
|
+
if (depth === 0) {
|
|
322
|
+
return afterStart.slice(openIndex, i + 1);
|
|
323
|
+
}
|
|
324
|
+
}
|
|
325
|
+
}
|
|
326
|
+
return afterStart;
|
|
327
|
+
}
|
|
233
328
|
async parseSchemaFile(convexDir) {
|
|
234
329
|
const tables = [];
|
|
235
330
|
const schemaPath = path.join(convexDir, "schema.ts");
|
|
@@ -257,4 +352,4 @@ var SchemaWatcher = class extends EventEmitter {
|
|
|
257
352
|
export {
|
|
258
353
|
SchemaWatcher
|
|
259
354
|
};
|
|
260
|
-
//# sourceMappingURL=chunk-
|
|
355
|
+
//# sourceMappingURL=chunk-6H4WSVWY.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';\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: Date;\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(),\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\n // Match table definitions: tableName: defineTable(\n const tablePattern = /(\\w+):\\s*defineTable\\(/g;\n\n let match;\n while ((match = tablePattern.exec(content)) !== null) {\n tables.push({\n name: match[1],\n fields: [], // Could parse fields but keeping simple for now\n });\n }\n } catch (error) {\n console.error('[SchemaWatcher] Error parsing schema file:', error);\n }\n\n return tables;\n }\n}\n"],"mappings":"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","names":[]}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import {
|
|
2
2
|
ConvexClient
|
|
3
|
-
} from "./chunk-
|
|
3
|
+
} from "./chunk-5B55IAKY.js";
|
|
4
4
|
|
|
5
5
|
// src/server/index.ts
|
|
6
6
|
import cors from "cors";
|
|
@@ -202,4 +202,4 @@ async function createServer(config) {
|
|
|
202
202
|
export {
|
|
203
203
|
createServer
|
|
204
204
|
};
|
|
205
|
-
//# sourceMappingURL=chunk-
|
|
205
|
+
//# sourceMappingURL=chunk-7RO6B2AJ.js.map
|
package/dist/cli/index.js
CHANGED
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import {
|
|
3
3
|
createServer
|
|
4
|
-
} from "./chunk-
|
|
4
|
+
} from "./chunk-7RO6B2AJ.js";
|
|
5
5
|
import {
|
|
6
6
|
SchemaWatcher
|
|
7
|
-
} from "./chunk-
|
|
8
|
-
import "./chunk-
|
|
7
|
+
} from "./chunk-6H4WSVWY.js";
|
|
8
|
+
import "./chunk-5B55IAKY.js";
|
|
9
9
|
|
|
10
10
|
// src/cli/index.ts
|
|
11
11
|
import chalk from "chalk";
|
|
@@ -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.
|
|
19
|
+
program.name("convex-devtools").description("A standalone development tool for testing Convex functions").version("1.0.1");
|
|
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",
|
package/dist/cli/index.js.map
CHANGED
|
@@ -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
|
+
{"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":";;;;;;;;;;AAEA,OAAO,WAAW;AAClB,SAAS,eAAe;AACxB,OAAO,YAAY;AACnB,OAAO,QAAQ;AACf,OAAO,UAAU;AACjB,OAAO,QAAQ;AACf,OAAO,UAAU;AAIjB,IAAM,UAAU,IAAI,QAAQ;AAE5B,QACG,KAAK,iBAAiB,EACtB,YAAY,4DAA4D,EACxE,QAAQ,OAAO;AAElB,QACG,OAAO,uBAAuB,gCAAgC,MAAM,EACpE,OAAO,oBAAoB,oCAAoC,GAAG,EAClE;AAAA,EACC;AAAA,EACA;AAAA,EACA;AACF,EACC;AAAA,EACC;AAAA,EACA;AACF,EACC,OAAO,aAAa,mCAAmC,EACvD,OAAO,OAAO,YAAY;AACzB,QAAM,aAAa,KAAK,QAAQ,QAAQ,GAAG;AAG3C,QAAM,eAAe,KAAK,KAAK,YAAY,YAAY;AACvD,QAAM,UAAU,KAAK,KAAK,YAAY,MAAM;AAE5C,MAAI,GAAG,WAAW,YAAY,GAAG;AAC/B,WAAO,OAAO,EAAE,MAAM,aAAa,CAAC;AAAA,EACtC,WAAW,GAAG,WAAW,OAAO,GAAG;AACjC,WAAO,OAAO,EAAE,MAAM,QAAQ,CAAC;AAAA,EACjC;AAGA,MAAI,QAAQ,IAAI,4BAA4B,QAAQ;AAClD,YAAQ;AAAA,MACN,MAAM,IAAI,qDAAgD;AAAA,IAC5D;AACA,YAAQ;AAAA,MACN,MAAM;AAAA,QACJ;AAAA,MACF;AAAA,IACF;AACA,YAAQ;AAAA,MACN,MAAM,OAAO,qDAAqD;AAAA,IACpE;AACA,YAAQ,KAAK,CAAC;AAAA,EAChB;AAEA,QAAM,YAAY,QAAQ,IAAI;AAC9B,MAAI,CAAC,WAAW;AACd,YAAQ,MAAM,MAAM,IAAI,8BAAyB,CAAC;AAClD,YAAQ;AAAA,MACN,MAAM;AAAA,QACJ;AAAA,MACF;AAAA,IACF;AACA,YAAQ,KAAK,CAAC;AAAA,EAChB;AAIA,QAAM,YAAY,QAAQ,IAAI,qBAAqB;AACnD,MAAI,CAAC,WAAW;AACd,YAAQ,IAAI,MAAM,OAAO,qCAAgC,CAAC;AAC1D,YAAQ,IAAI,MAAM,OAAO,sCAAsC,CAAC;AAChE,YAAQ;AAAA,MACN,MAAM;AAAA,QACJ;AAAA,MACF;AAAA,IACF;AACA,YAAQ,IAAI;AAAA,EACd;AAGA,QAAM,eAAe,KAAK,KAAK,YAAY,UAAU,YAAY;AACjE,MAAI,CAAC,GAAG,WAAW,YAAY,GAAG;AAChC,YAAQ,MAAM,MAAM,IAAI,yCAAoC,CAAC;AAC7D,YAAQ,MAAM,MAAM,OAAO,eAAe,YAAY,EAAE,CAAC;AACzD,YAAQ,MAAM,MAAM,OAAO,0CAA0C,CAAC;AACtE,YAAQ,KAAK,CAAC;AAAA,EAChB;AAEA,UAAQ,IAAI,MAAM,KAAK,0QAA8C,CAAC;AACtE,UAAQ;AAAA,IACN,MAAM,KAAK,QAAG,IACZ,MAAM,MAAM,KAAK,4CAA4C,IAC7D,MAAM,KAAK,QAAG;AAAA,EAClB;AACA,UAAQ,IAAI,MAAM,KAAK,0QAA8C,CAAC;AACtE,UAAQ,IAAI;AACZ,UAAQ,IAAI,MAAM,MAAM,QAAG,IAAI,wBAAwB;AACvD,UAAQ,IAAI,MAAM,MAAM,QAAG,IAAI,gBAAgB,MAAM,IAAI,SAAS,CAAC,EAAE;AACrE,UAAQ,IAAI,MAAM,MAAM,QAAG,IAAI,aAAa,MAAM,IAAI,UAAU,CAAC,EAAE;AACnE,UAAQ,IAAI;AAEZ,QAAM,cAAc,OAAO,QAAQ,WAAW,SAAS;AACvD,MAAI;AAEJ,MAAI,gBAAgB,WAAW;AAC7B,sBAAkB,KAAK;AAAA,MACrB;AAAA,MACA;AAAA,MACA;AAAA,IACF;AAAA,EACF,WAAW,gBAAgB,UAAU;AACnC,sBAAkB,KAAK;AAAA,MACrB,GAAG,QAAQ;AAAA,MACX;AAAA,MACA;AAAA,IACF;AAAA,EACF,WAAW,gBAAgB,QAAQ;AACjC,QAAI,CAAC,QAAQ,aAAa;AACxB,cAAQ;AAAA,QACN,MAAM,IAAI,sDAAiD;AAAA,MAC7D;AACA,cAAQ,KAAK,CAAC;AAAA,IAChB;AACA,sBAAkB,KAAK,QAAQ,OAAO,QAAQ,WAAW,CAAC;AAAA,EAC5D,OAAO;AACL,YAAQ;AAAA,MACN,MAAM,IAAI,+DAA0D;AAAA,IACtE;AACA,YAAQ,KAAK,CAAC;AAAA,EAChB;AAGA,QAAM,gBAAgB,IAAI,cAAc,UAAU;AAClD,QAAM,cAAc,MAAM;AAG1B,QAAM,OAAO,SAAS,QAAQ,MAAM,EAAE;AACtC,QAAM,SAAS,MAAM,aAAa;AAAA,IAChC;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,EACF,CAAC;AAED,UAAQ;AAAA,IACN,MAAM,MAAM,QAAG,IACb,wBAAwB,MAAM,KAAK,oBAAoB,IAAI,EAAE,CAAC;AAAA,EAClE;AACA,UAAQ,IAAI;AACZ,UAAQ,IAAI,MAAM,IAAI,sBAAsB,CAAC;AAE7C,MAAI,QAAQ,SAAS,OAAO;AAC1B,UAAM,KAAK,oBAAoB,IAAI,EAAE;AAAA,EACvC;AAGA,UAAQ,GAAG,UAAU,YAAY;AAC/B,YAAQ,IAAI,MAAM,IAAI,oBAAoB,CAAC;AAC3C,kBAAc,KAAK;AACnB,WAAO,MAAM;AACb,YAAQ,KAAK,CAAC;AAAA,EAChB,CAAC;AACH,CAAC;AAEH,QAAQ,MAAM;","names":[]}
|
package/dist/cli/server/index.js
CHANGED
package/package.json
CHANGED
|
@@ -1 +0,0 @@
|
|
|
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';\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: Date;\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(),\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 // Find the args: { ... } section\n const afterStart = content.slice(startIndex);\n const argsMatch = afterStart.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 = afterStart.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 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\n // Match table definitions: tableName: defineTable(\n const tablePattern = /(\\w+):\\s*defineTable\\(/g;\n\n let match;\n while ((match = tablePattern.exec(content)) !== null) {\n tables.push({\n name: match[1],\n fields: [], // Could parse fields but keeping simple for now\n });\n }\n } catch (error) {\n console.error('[SchemaWatcher] Error parsing schema file:', error);\n }\n\n return tables;\n }\n}\n"],"mappings":"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","names":[]}
|
|
File without changes
|