Skip to content

Conversation

@ditadi
Copy link
Contributor

@ditadi ditadi commented Dec 8, 2025

Type-Safe SQL Queries with QueryRegistry

Summary

This PR introduces a compile-time type system for SQL queries, providing full type safety from query definition to result consumption. The system generates TypeScript types by analyzing SQL files and using DESCRIBE QUERY against Databricks to infer result schemas without executing the actual queries.

Features

QueryRegistry Type Generation

Automatically generates TypeScript interfaces for all SQL queries in your project:

interface QueryRegistry {
  spend_data: {
    name: "spend_data";
    parameters: {
      /** DATE - use sql.date() */
      startDate: SQLDateMarker;
      /** STRING - use sql.string() */
      groupBy: SQLStringMarker;
    };
    result: Array<{
      /** @sqlType STRING */
      group_key: string;
      /** @sqlType DECIMAL */
      cost_usd: number;
    }>;
  };
}

SQL Parameter Type Safety

  • Marker Types: Specific interfaces for each SQL type (SQLDateMarker, SQLStringMarker, SQLNumberMarker, SQLBooleanMarker, SQLTimestampMarker, SQLBinaryMarker)
  • Numeric Type Support: All numeric types (INT, BIGINT, DECIMAL, FLOAT, DOUBLE, etc.) map to SQLNumberMarker with sql.number() helper
  • @param Annotations: Declare parameter types in SQL files:
    -- @param startDate DATE
    -- @param endDate DATE
    -- @param groupBy STRING
    SELECT * FROM spend WHERE date BETWEEN :startDate AND :endDate
  • JSDoc Hints: Generated types include inline documentation showing which sql.*() helper to use
  • Graceful Fallback: Unannotated parameters use SQLTypeMarker union, still enforcing helper usage

Type-Safe Query Hook

useAnalyticsQuery now provides full IntelliSense:

// ✅ Type-safe: IDE shows required parameters and their types
const { data, loading, error } = useAnalyticsQuery("spend_data", {
  startDate: sql.date("2024-01-01"),
  endDate: sql.date("2024-12-31"),
  groupBy: sql.string("app_name"),
});

// data is typed as Array<{ group_key: string; cost_usd: number }>

Result Schema Inference

  • Uses DESCRIBE QUERY to extract column metadata without executing queries
  • Normalizes parameterized types (DECIMAL(38,6)DECIMAL, ARRAY<STRING>ARRAY)
  • Maps all Databricks types to TypeScript (STRING, DECIMAL, INT, BIGINT, ARRAY, MAP, STRUCT, etc.)
  • Adds @sqlType JSDoc comments with full type info (e.g., @sqlType DECIMAL(38,6))
  • Intelligent caching to avoid redundant queries (invalidates when SQL changes)

Developer Experience

Before After
Parameters typed as any Specific marker types with hints
Runtime errors for wrong types Compile-time validation
No autocomplete for results Full IntelliSense on data
Manual type assertions Automatic type inference

Architecture

SQL Files (with @param annotations)
        ↓
Type Generator (npx appkit-generate-types)
    ├─ Parse @param comments → Parameter types
    └─ DESCRIBE QUERY → Result schema (no execution)
        ↓
Generated appKitTypes.d.ts
        ↓
Module Augmentation (QueryRegistry)
        ↓
Type-safe useAnalyticsQuery<K>
    ├─ InferParams<K> → Required parameters
    └─ InferResult<K> → Typed results

Test Plan

  • Run npx appkit-generate-types and verify appKitTypes.d.ts is generated correctly
  • Confirm IDE autocomplete works for query names in useAnalyticsQuery
  • Verify parameter types match @param annotations
  • Test compile error when using wrong sql.*() helper
  • Validate result types match actual query output
  • Ensure cache invalidation works when SQL files change

@ditadi ditadi requested review from a team and fjakobs December 8, 2025 15:42
- Generate TypeScript types from SQL files using LIMIT 0 queries
- Add specific marker types (SQLDateMarker, SQLStringMarker, etc.)
- Parse @param annotations in SQL files for parameter types
- Generate JSDoc hints showing which sql.*() helper to use
- Add intelligent caching to avoid redundant Databricks queries
- Integrate with useAnalyticsQuery for full type inference
@ditadi ditadi force-pushed the feat/type-safety-build-time branch from 0a07b6b to 2e680fe Compare December 8, 2025 15:48
@ditadi ditadi requested a review from fjakobs December 8, 2025 18:15
// numeric
NUMERIC: "sql.number()",
INT: "sql.number()",
BIGINT: "sql.number()",
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

not blocking: we should map BigInt to JS BigInt numbers

@ditadi ditadi merged commit 58f73ad into main Dec 9, 2025
3 checks passed
@MarioCadenas MarioCadenas deleted the feat/type-safety-build-time branch December 19, 2025 17:44
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants