Skip to content

Commit ae1ed25

Browse files
fjakobsclaude
andauthored
MCP: Sync Templates & Documentation from Rust Version (#3943)
## Summary ## Changes ### Template Files (Synchronized with Rust Version) - **`lib/templates/trpc/CLAUDE.md`** - AI assistant guidance for the tRPC template - Testing guidelines with Node.js test runner - Databricks type handling and best practices - Frontend styling guidelines with Tailwind CSS - Data visualization patterns with Recharts - **`lib/templates/trpc/server/src/server.test.ts`** - Enhanced test structure - Basic server healthcheck test - Commented examples for testing tRPC procedures directly - Proper environment variable setup - **`lib/templates/trpc/server/src/databricks.ts`** - Improved Databricks client - Better documentation and usage examples - Added `mapRows` helper utility for manual row mapping - Proper Zod schema validation patterns ## Testing - ✅ All Go tests pass - ✅ Template files verified to match Rust version - ✅ Documentation reviewed for accuracy and completeness ## Related - Implements Phase 5 from `docs/porting-plan/phase-5-templates-docs.md` - Template files synchronized with `/Users/fabian.jakobs/Workspaces/agent/edda/edda_templates/template_trpc/` 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-authored-by: Claude <[email protected]>
1 parent 2fde652 commit ae1ed25

File tree

3 files changed

+105
-6
lines changed

3 files changed

+105
-6
lines changed

experimental/apps-mcp/lib/templates/trpc/CLAUDE.md

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,12 +14,39 @@ import { strict as assert } from "node:assert";
1414

1515
## Databricks Type Handling:
1616

17+
- **executeQuery REQUIRES Zod schema**: Pass the Zod schema object as second parameter, NOT a TypeScript type annotation
18+
```typescript
19+
// ❌ WRONG - Do NOT use generic type parameter
20+
const result = await client.executeQuery<MyType>(sql);
21+
22+
// ✅ CORRECT - Pass Zod schema as parameter
23+
const mySchema = z.object({ id: z.number(), name: z.string() });
24+
const result = await client.executeQuery(sql, mySchema);
25+
```
1726
- **QueryResult access**: `executeQuery()` returns `{rows: T[], rowCount: number}`. Always use `.rows` property: `const {rows} = await client.executeQuery(...)` or `result.rows.map(...)`
1827
- **Type imports**: Use `import type { T }` (not `import { T }`) when `verbatimModuleSyntax` is enabled
1928
- **Column access**: Use bracket notation `row['column_name']` (TypeScript strict mode requirement)
2029
- **DATE/TIMESTAMP columns**: Databricks returns Date objects. Use `z.coerce.date()` in schemas (never `z.string()` for dates)
2130
- **Dynamic properties**: Cast explicitly `row['order_id'] as number`
2231

32+
### Helper Utilities:
33+
34+
**mapRows<T>(rows, schema)** - Validates and maps raw SQL rows using Zod schema:
35+
```typescript
36+
import { mapRows } from './databricks';
37+
38+
// When you have raw rows and need manual mapping
39+
const rawRows = [{id: 1, name: "Alice"}, {id: 2, name: "Bob"}];
40+
const userSchema = z.object({ id: z.number(), name: z.string() });
41+
const users = mapRows(rawRows, userSchema);
42+
// users is now typed as { id: number; name: string }[]
43+
```
44+
45+
Use this when:
46+
- Processing nested query results
47+
- Manually mapping row data before returning from tRPC
48+
- Need to validate data from non-Databricks sources
49+
2350
## Frontend Styling Guidelines:
2451

2552
### Component Structure Pattern:

experimental/apps-mcp/lib/templates/trpc/server/src/databricks.ts

Lines changed: 42 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -5,12 +5,18 @@
55
// const myTableSchema = z.object({
66
// id: z.number(),
77
// name: z.string(),
8-
// created_at: z.string(),
8+
// created_at: z.coerce.date(),
99
// });
1010
//
1111
// const client = new DatabricksClient();
12+
//
13+
// // ✅ CORRECT - Pass Zod schema (not TypeScript type)
1214
// const result = await client.executeQuery("SELECT * FROM my_table", myTableSchema);
13-
// // result.rows is now validated and typed as MyTable[]
15+
// // result.rows is now validated and typed as z.infer<typeof myTableSchema>[]
16+
//
17+
// // ❌ WRONG - Do NOT use generic type parameter alone
18+
// // const result = await client.executeQuery<MyType>("SELECT ...");
19+
// // This will cause runtime errors!
1420

1521
import { DBSQLClient } from "@databricks/sql";
1622
import type { ConnectionOptions } from "@databricks/sql/dist/contracts/IDBSQLClient";
@@ -75,9 +81,21 @@ export class DatabricksClient {
7581
}
7682
}
7783

78-
async executeQuery<T extends z.ZodTypeAny = typeof defaultRowSchema>(
84+
/**
85+
* Execute a SQL query against Databricks and validate results with Zod schema.
86+
*
87+
* @param sql - SQL query string
88+
* @param schema - Zod schema for row validation (REQUIRED - pass the schema, not a TypeScript type)
89+
* @returns QueryResult with validated and typed rows
90+
*
91+
* @example
92+
* const schema = z.object({ id: z.number(), name: z.string() });
93+
* const result = await client.executeQuery("SELECT id, name FROM users", schema);
94+
* // result.rows is typed as { id: number; name: string }[]
95+
*/
96+
async executeQuery<T extends z.ZodTypeAny>(
7997
sql: string,
80-
schema?: T,
98+
schema: T,
8199
): Promise<QueryResult<z.infer<T>>> {
82100
try {
83101
const client = new DBSQLClient();
@@ -92,8 +110,8 @@ export class DatabricksClient {
92110
await session.close();
93111
await connection.close();
94112

95-
// Apply schema validation if provided
96-
const rows = schema ? result.map((row) => schema.parse(row)) : result;
113+
// Apply schema validation
114+
const rows = result.map((row) => schema.parse(row));
97115
return { rows: rows as z.infer<T>[], rowCount: rows.length };
98116
} catch (error) {
99117
console.error("Databricks SQL query error:", error);
@@ -106,3 +124,21 @@ export class DatabricksClient {
106124
}
107125
}
108126
}
127+
128+
/**
129+
* Helper utility to map and validate raw SQL rows using a Zod schema.
130+
* Useful when you have raw rows from nested queries or need manual mapping.
131+
*
132+
* @param rows - Array of raw SQL rows (Record<string, SqlValue>)
133+
* @param schema - Zod schema for validation
134+
* @returns Array of validated and typed objects
135+
*
136+
* @example
137+
* const rawRows = [{id: 1, name: "Alice"}, {id: 2, name: "Bob"}];
138+
* const schema = z.object({ id: z.number(), name: z.string() });
139+
* const users = mapRows(rawRows, schema);
140+
* // users is typed as { id: number; name: string }[]
141+
*/
142+
export function mapRows<T>(rows: SqlRow[], schema: z.ZodSchema<T>): T[] {
143+
return rows.map((row) => schema.parse(row));
144+
}

experimental/apps-mcp/lib/templates/trpc/server/src/server.test.ts

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,8 @@ import type { Server } from "node:http";
66
process.env["DATABRICKS_HOST"] =
77
process.env["DATABRICKS_HOST"] || "https://dummy.databricks.com";
88
process.env["DATABRICKS_TOKEN"] = process.env["DATABRICKS_TOKEN"] || "dummy_token";
9+
process.env["DATABRICKS_WAREHOUSE_ID"] =
10+
process.env["DATABRICKS_WAREHOUSE_ID"] || "dummy_warehouse_id";
911

1012
test("server starts and responds to healthcheck", async () => {
1113
// dynamic import to ensure env vars are set first
@@ -34,3 +36,37 @@ test("server starts and responds to healthcheck", async () => {
3436
}
3537
}
3638
});
39+
40+
// Example: Testing tRPC procedures directly without HTTP server
41+
// This is faster and simpler for most tests
42+
//
43+
// test("getUsers returns array of users", async () => {
44+
// const { appRouter } = await import("./index");
45+
// const { initTRPC } = await import("@trpc/server");
46+
//
47+
// // create tRPC caller - no HTTP server needed
48+
// const t = initTRPC.create();
49+
// const caller = t.createCallerFactory(appRouter)({});
50+
//
51+
// const result = await caller.getUsers();
52+
//
53+
// // validate structure
54+
// assert.ok(Array.isArray(result));
55+
// if (result.length > 0) {
56+
// assert.ok(result[0].id);
57+
// assert.ok(result[0].name);
58+
// }
59+
// });
60+
//
61+
// test("getMetrics with input parameter", async () => {
62+
// const { appRouter } = await import("./index");
63+
// const { initTRPC } = await import("@trpc/server");
64+
//
65+
// const t = initTRPC.create();
66+
// const caller = t.createCallerFactory(appRouter)({});
67+
//
68+
// const result = await caller.getMetrics({ category: "sales" });
69+
//
70+
// assert.ok(Array.isArray(result));
71+
// // add assertions for your expected data structure
72+
// });

0 commit comments

Comments
 (0)