
Research
Shai-Hulud Descends to Hades: Miasma Worm Campaign Spreads with New PyPI Wave
Socket found 37 malicious PyPI wheels that abuse Python startup hooks to launch a Bun-powered credential stealer tied to Mini Shai-Hulud/Miasma.
@gqlite/vite-plugin
Advanced tools
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.
bun add @gqlite/tag
bun add -d @gqlite/vite-plugin @gqlite/gql2sql graphql
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();
}
};
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" }
},
}
} */
})
]
});
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);
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 }
}
`;
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 ProjectionUse @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.
const userId = 123;
const userPosts = gqlite<GQL.GetUserPosts>(db)`
query GetUserPosts {
user(id: ${userId}) {
id
name
posts {
id
title
content
}
}
}
`;
const usersWithPosts = gqlite<GQL.GetUsersWithPosts>(db)`
query GetUsersWithPosts {
users {
id
name
posts {
id
title
user {
name
}
}
}
}
`;
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 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.
gqlitePlugin({
schema: mySchema,
functionName: 'myCustomGql'
})
// Now use with custom name:
const data = myCustomGql<GQL.Query>(db)`query GetData { ... }`;
gqlitePlugin({
schema: mySchema,
include: ['.ts', '.tsx'], // Only process TypeScript files
exclude: ['node_modules', 'dist'] // Exclude these directories
})
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} })`;
}
})
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}])`;
}
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.
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 })
]
});
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"
}
}
}
};
The createSchemaFromDatabase function automatically:
userId → user relation field)id joins can be generated correctlyThe 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.
eq, ne, gt, gte, lt, lte, like, ilike)limit and offset parametersorderBy parameter for result sortingdirective @flatten on FIELD for supported flattened relation projectionsgqlitePlugin({
schema: mySchema,
// GraphQL schema generation (default: true)
generateGraphqlSchema: true,
// Output path (default: '.gqlite/schema.graphql')
graphqlSchemaOutputPath: './src/schema.graphql',
})
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!]!
}
The generated schema provides:
gqlitePlugin({
schema: mySchema,
generateGraphqlSchema: false // Disable GraphQL schema generation
})
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 })
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
}
}
}
`;
bun install
bun test
bun run build
gqlite template literalssqlExecutorThe plugin generates TypeScript-compatible code and preserves all type information. It works seamlessly with:
.ts and .tsx filesFAQs
Vite plugin for GQLite
We found that @gqlite/vite-plugin demonstrated a healthy version release cadence and project activity because the last version was released less than a year ago. It has 1 open source maintainer collaborating on the project.
Did you know?

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.

Research
Socket found 37 malicious PyPI wheels that abuse Python startup hooks to launch a Bun-powered credential stealer tied to Mini Shai-Hulud/Miasma.

Security News
RubyGems and Bundler 4.0.13 introduced an opt-in cooldown feature that delays newly published gems during dependency resolution.

Security News
pnpm 11.5 now recognizes npm staged publish approvals in release metadata, preventing those releases from being mistaken for lower-trust package publishes.