Big News: Socket raises $60M Series C at a $1B valuation to secure software supply chains for AI-driven development.Announcement
Sign In

@gqlite/vite-plugin

Package Overview
Dependencies
Maintainers
1
Versions
7
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

@gqlite/vite-plugin

Vite plugin for GQLite

latest
npmnpm
Version
1.0.1
Version published
Maintainers
1
Created
Source

vite-plugin-gqlite

A Vite plugin that transforms gqlite template literals into optimized SQLite queries at build time. This plugin enables you to write GraphQL-like queries that are automatically converted to efficient SQL queries, preventing N+1 problems and SQL injection vulnerabilities.

Features

  • 🚀 Build-time GraphQL to SQL transformation
  • 🛡️ SQL injection prevention through parameterized queries
  • N+1 problem prevention with optimized joins
  • 🔍 TypeScript support with full type safety
  • 📍 Source map generation for debugging
  • 🎯 Named query requirement for better organization
  • 🔧 Configurable function names and file patterns
  • 📦 Persisted query mode with stable operation IDs and compact server maps

Installation

bun add @gqlite/tag
bun add -d @gqlite/vite-plugin @gqlite/gql2sql graphql

Database Setup

For automatic schema generation, you'll need a database connection that implements the DatabaseConnection interface:

interface DatabaseConnection {
  all(query: string): Promise<any[]>;
}

// Example with bun:sqlite
import { Database } from 'bun:sqlite';
const db = new Database('./database.sqlite');
const dbConnection = {
  async all(query: string) {
    return db.query(query).all();
  }
};

// Example with better-sqlite3
import Database from 'better-sqlite3';
const db = new Database('./database.sqlite');
const dbConnection = {
  async all(query: string) {
    return db.prepare(query).all();
  }
};

Quick Start

1. Configure the plugin in your vite.config.ts:

import { defineConfig } from 'vite';
import { createSchemaFromDatabase, gqlitePlugin } from '@gqlite/vite-plugin';
import Database from 'better-sqlite3';

const db = new Database('./database.sqlite');
const dbConnection = {
  async all(sql) {
    return db.prepare(sql).all();
  },
};

const schema = await createSchemaFromDatabase(dbConnection);

export default defineConfig({
  plugins: [
    gqlitePlugin({
      // Option 1: Auto-generate schema from database (recommended)
      schema,
      
      // Option 2: Manual schema definition
      /* schema: {
        users: {
          id: { type: "INTEGER", nullable: false },
          name: { type: "TEXT", nullable: false }, 
          email: { type: "TEXT", nullable: false },
        },
        posts: {
          id: { type: "INTEGER", nullable: false },
          title: { type: "TEXT", nullable: false },
          content: { type: "TEXT", nullable: true }, 
          userId: { 
            type: "INTEGER", 
            nullable: false,
            relation: { table: "users", foreignKey: "userId", type: "many-to-one" }
          },
        }
      } */
    })
  ]
});

2. Use gqlite in your code:

import { gqlite } from '@gqlite/tag';

// Before transformation (what you write):
const users = gqlite<GQL.GetUsers>(db)`
  query GetUsers {
    users {
      id
      name
      email
    }
  }
`;

// After transformation (what gets compiled):
const temp = db.query("SELECT t0.id AS id, t0.name AS name, t0.email AS email FROM users AS t0", {});
const users = gqlite.transformResult(temp);

Syntax Requirements

Named Queries Only

All GraphQL queries must have a name. Unnamed queries will not be transformed:

// ✅ Valid - will be transformed
const users = gqlite<GQL.GetUsers>(db)`
  query GetUsers {
    users { id name }
  }
`;

// ❌ Invalid - will not be transformed
const users = gqlite<GQL.GetUsers>(db)`
  query {
    users { id name }
  }
`;

Type Annotations

The plugin expects the TypeScript generic syntax for better code organization:

// ✅ Recommended syntax
const users = gqlite<GQL.GetUsers>(db)`query GetUsers { ... }`;

// ⚠️ Works but not recommended
const users = gqlite(db)`query GetUsers { ... }`;

@flatten Relation Projection

Use @flatten on root many-to-one or one-to-one relation fields to inline selected child scalar fields into parent rows. Child aliases become output field names.

const posts = gqlite<GQL.GetPostsWithAuthors>(db)`
  query GetPostsWithAuthors {
    posts {
      id
      title
      user @flatten {
        authorName: name
        authorEmail: email
      }
    }
  }
`;

@flatten is rejected for array relations (one-to-many / many-to-many) and nested relation children.

Advanced Examples

Queries with Parameters

const userId = 123;
const userPosts = gqlite<GQL.GetUserPosts>(db)`
  query GetUserPosts {
    user(id: ${userId}) {
      id
      name
      posts {
        id
        title
        content
      }
    }
  }
`;

Complex Nested Relations

const usersWithPosts = gqlite<GQL.GetUsersWithPosts>(db)`
  query GetUsersWithPosts {
    users {
      id
      name
      posts {
        id
        title
        user {
          name
        }
      }
    }
  }
`;

Configuration Options

interface GqlitePluginOptions {
  /**
   * Database schema definition
   */
  schema: Schema;

  /**
   * Function name to transform (default: 'gqlite')
   */
  functionName?: string;

  /**
   * File extensions to process (default: ['.ts', '.tsx', '.js', '.jsx'])
   */
  include?: string[];

  /**
   * File patterns to exclude
   */
  exclude?: string[];

  /**
   * Generate TypeScript types (default: true)
   */
  generateTypes?: boolean;

  /**
   * Directory to generate TypeScript types (default: '.gqlite/types/')
   */
  typesDir?: string;

  /**
   * Generate GraphQL schema file (default: true)
   */
  generateGraphqlSchema?: boolean;

  /**
   * Path to generate GraphQL schema file (default: '.gqlite/schema.graphql')
   */
  graphqlSchemaOutputPath?: string;

  /**
   * Custom SQL execution function generator
   * Receives the SQL query parameters and should return the code string for execution
   */
  sqlExecutor?: (params: {
    paramMapping: Record<string, string>;
    escapedSql: string;
    dbVariable: string;
  }) => string;

  /**
   * Persisted query artifact generation and client transform.
   */
  persistedQueries?: boolean | PersistedQueryOptions;
}

interface PersistedQueryOptions {
  registryPath?: string;
  manifestPath?: string;
  compactMapPath?: string;
  clientFunctionName?: string;
  schemaHash?: string;
  compilerVersion?: string;
}

Persisted Query Mode

Persisted mode keeps GraphQL and SQL out of the browser bundle. The plugin collects gqlite<T>(db) template literals, assigns stable integer operation IDs, writes server artifacts, and replaces client code with ID calls.

gqlitePlugin({
  schema,
  persistedQueries: {
    registryPath: ".gqlite/operation-registry.json",
    manifestPath: ".gqlite/persisted-manifest.json",
    compactMapPath: ".gqlite/persisted-map.ts",
    clientFunctionName: "gqliteQuery",
  },
});

Source code:

const usersPromise = gqlite<GQL.ListUsers>(db)`
  query ListUsers {
    users { id name email }
  }
`;

Generated client code:

const usersPromise = gqliteQuery(1);

Generated artifacts:

  • operation-registry.json stores stable integer IDs for canonical GraphQL operations.
  • persisted-manifest.json is a readable debug manifest with SQL and parameter names.
  • persisted-map.ts exports compact runtime data for executePersistedQuery().

For a complete app, see examples/gqlite-persisted.

Custom Function Name

gqlitePlugin({
  schema: mySchema,
  functionName: 'myCustomGql'
})

// Now use with custom name:
const data = myCustomGql<GQL.Query>(db)`query GetData { ... }`;

File Filtering

gqlitePlugin({
  schema: mySchema,
  include: ['.ts', '.tsx'],           // Only process TypeScript files
  exclude: ['node_modules', 'dist']   // Exclude these directories
})

Custom SQL Executor

By default, the plugin generates code that calls db.query(sql, params). You can customize this behavior using the sqlExecutor option:

gqlitePlugin({
  schema: mySchema,
  sqlExecutor: ({ paramMapping, escapedSql, dbVariable }) => {
    // Custom database call format
    const paramObject = Object.entries(paramMapping)
      .map(([key, value]) => `${key}: ${value}`)
      .join(", ");
    
    return `${dbVariable}.execute(${escapedSql}, { ${paramObject} })`;
  }
})

Examples of Custom SQL Executors

For databases that use .prepare() and .all():

sqlExecutor: ({ paramMapping, escapedSql, dbVariable }) => {
  const paramObject = Object.entries(paramMapping)
    .map(([key, value]) => `${key}: ${value}`)
    .join(", ");
  
  return `${dbVariable}.prepare(${escapedSql}).all({ ${paramObject} })`;
}

For async database calls:

sqlExecutor: ({ paramMapping, escapedSql, dbVariable }) => {
  const paramObject = Object.entries(paramMapping)
    .map(([key, value]) => `${key}: ${value}`)
    .join(", ");
  
  return `await ${dbVariable}.query(${escapedSql}, { ${paramObject} })`;
}

For custom parameter formatting:

sqlExecutor: ({ paramMapping, escapedSql, dbVariable }) => {
  // Convert to array format instead of object
  const paramArray = Object.entries(paramMapping)
    .map(([_, value]) => value)
    .join(", ");
  
  return `${dbVariable}.query(${escapedSql}, [${paramArray}])`;
}

Understanding the Parameters

The sqlExecutor function receives these parameters:

  • paramMapping: Maps parameter names to their JavaScript expressions

    // Example: { "param0": "userId", "param1": "status" }
    
  • escapedSql: The generated SQL query as a JSON-escaped string

    // Example: "\"SELECT * FROM users WHERE id = $param0 AND status = $param1\""
    
  • dbVariable: The variable name of the database instance

    // Example: "db" from gqlite(db)`query...`
    

The function should return a string of JavaScript code that will be inserted into the transformed output.

Schema Definition

You can generate schemas automatically from your existing SQLite database using the built-in schema generator:

import { createSchemaFromDatabase, gqlitePlugin } from '@gqlite/vite-plugin';

// Generate schema from existing database
const dbConnection = {
  all: (query: string) => db.prepare(query).all() // Your SQLite connection
};

const schema = await createSchemaFromDatabase(dbConnection);

export default defineConfig({
  plugins: [
    gqlitePlugin({ schema })
  ]
});

Manual Schema Definition (Alternative)

If you prefer to define schemas manually or need custom configurations:

const schema = {
  users: {
    id: { type: "INTEGER", nullable: false },
    name: { type: "TEXT", nullable: false },
    email: { type: "TEXT", nullable: false },
    createdAt: { type: "DATETIME", nullable: true }
  },
  posts: {
    id: { type: "INTEGER", nullable: false },
    title: { type: "TEXT", nullable: false },
    content: { type: "TEXT", nullable: true },
    userId: { 
      type: "INTEGER", 
      nullable: false,
      relation: {
        table: "users",
        foreignKey: "userId", 
        type: "many-to-one"
      }
    },
    // Convenience relation field (auto-generated by schema generator)
    user: {
      type: "OBJECT",
      relation: {
        table: "users",
        foreignKey: "userId",
        type: "many-to-one"
      }
    }
  },
  // One-to-many relationship (auto-generated by schema generator)
  users: {
    // ...other fields...
    posts: {
      type: "ARRAY",
      relation: {
        table: "posts",
        foreignKey: "userId", 
        type: "one-to-many"
      }
    }
  }
};

Schema Generator Features

The createSchemaFromDatabase function automatically:

  • Detects column types and maps SQLite types to schema types
  • Identifies foreign keys and creates many-to-one relationships
  • Generates convenience fields (e.g., userIduser relation field)
  • Creates one-to-many relationships in referenced tables
  • Detects one-to-one relationships from unique foreign keys
  • Detects many-to-many relationships through junction tables, including payload-column junction tables
  • Preserves relation source/target columns so non-id joins can be generated correctly
  • Handles nullable columns correctly (primary keys are never nullable)

GraphQL Schema Generation

The plugin automatically generates GraphQL schema files that provide IDE validation and autocompletion support for your GraphQL queries. This feature is enabled by default and creates a comprehensive schema based on your SQLite database structure.

Generated Schema Features

  • Complete Type Definitions: All database tables become GraphQL types
  • WHERE Input Types: Support for filtering with operators (eq, ne, gt, gte, lt, lte, like, ilike)
  • Query Root: Automatically generated Query type with all table queries
  • Relationship Support: Proper GraphQL field definitions for table relations
  • Pagination Support: Built-in limit and offset parameters
  • Ordering Support: orderBy parameter for result sorting
  • Directive Support: directive @flatten on FIELD for supported flattened relation projections

Configuration

gqlitePlugin({
  schema: mySchema,
  
  // GraphQL schema generation (default: true)
  generateGraphqlSchema: true,
  
  // Output path (default: '.gqlite/schema.graphql')
  graphqlSchemaOutputPath: './src/schema.graphql',
})

Generated Schema Example

For a simple blog schema, the plugin generates:

# Users type based on users table
type Users {
  id: Int!
  username: String!
  email: String!
  posts: [Posts!]!
}

# Posts type based on posts table  
type Posts {
  id: Int!
  title: String!
  content: String!
  authorId: Int!
  author: Users!
}

# WHERE input for filtering
input UsersWhereInput {
  id: IntWhereInput
  username: StringWhereInput
  email: StringWhereInput
}

input StringWhereInput {
  eq: String
  ne: String
  like: String
  ilike: String
}

# Root Query type
type Query {
  users(
    where: UsersWhereInput
    orderBy: [String!]
    limit: Int
    offset: Int
  ): [Users!]!
  
  posts(
    where: PostsWhereInput
    orderBy: [String!] 
    limit: Int
    offset: Int
  ): [Posts!]!
}

IDE Integration

The generated schema provides:

  • Syntax Highlighting: Proper GraphQL syntax highlighting in your queries
  • Autocompletion: Field name suggestions as you type
  • Type Validation: Real-time validation of query structure
  • Documentation: Hover information for fields and types

Disabling Schema Generation

gqlitePlugin({
  schema: mySchema,
  generateGraphqlSchema: false  // Disable GraphQL schema generation
})

Security Features

SQL Injection Prevention

All user inputs are automatically parameterized:

const userInput = "'; DROP TABLE users; --";
const result = gqlite<GQL.GetUser>(db)`
  query GetUser {
    user(id: ${userInput}) {
      id name
    }
  }
`;

// Generates safe parameterized query:
// db.query("SELECT ... WHERE id = ?", { param1: userInput })

N+1 Problem Prevention

Nested queries are automatically optimized with JOINs:

// This query automatically generates efficient JOINs
const usersWithPosts = gqlite<GQL.GetUsersWithPosts>(db)`
  query GetUsersWithPosts {
    users {
      id
      name
      posts {    // Automatically joined, not fetched in a loop
        id
        title
      }
    }
  }
`;

Development

Install Dependencies

bun install

Run Tests

bun test

Build

bun run build

How It Works

  • Parse: The plugin parses your source code to find gqlite template literals
  • Extract: GraphQL queries and parameters are extracted from template literals
  • Convert: GraphQL queries are converted to optimized SQL using the schema
  • Execute: SQL execution code is generated using the default or custom sqlExecutor
  • Transform: The original code is replaced with parameterized SQL calls
  • Generate: Source maps are generated for debugging support

TypeScript Support

The plugin generates TypeScript-compatible code and preserves all type information. It works seamlessly with:

  • .ts and .tsx files
  • JSX syntax
  • TypeScript generics
  • Interface definitions
  • Type annotations

FAQs

Package last updated on 08 May 2026

Did you know?

Socket

Socket for GitHub automatically highlights issues in each pull request and monitors the health of all your open source dependencies. Discover the contents of your packages and block harmful activity before you install or update your dependencies.

Install

Related posts