SafeQL ❤️ Slonik
SafeQL is compatible with Slonik with full support for Slonik's SQL helpers and Zod schema validation.
Using the Slonik Plugin (Experimental)
EXPERIMENTAL
The Slonik plugin is experimental and may change in future releases.
npm install @ts-safeql/plugin-slonik// eslint.config.js
import safeql from "@ts-safeql/eslint-plugin/config";
import slonik from "@ts-safeql/plugin-slonik";
import tseslint from "typescript-eslint";
export default tseslint.config(
// ...
safeql.configs.connections({
databaseUrl: "postgres://user:pass@localhost:5432/db",
plugins: [slonik()],
}),
);The Slonik plugin defaults sql.type(...) schema mismatches to suggestions instead of autofix. If you want --fix to rewrite the schema automatically, set enforceType: "fix" in the connection config.
Zod Schema Validation
SafeQL validates your Zod schemas against the actual query results:
import { z } from "zod";
import { sql } from "slonik";
// Wrong field type → suggestion by default
const query = sql.type(z.object({ id: z.string() }))`SELECT id FROM users`;
// ~~~~~~~~~~
// Error: Zod schema does not match query result.
// Expected: z.object({ id: z.number() })
// Correct ✅
const query = sql.type(z.object({ id: z.number() }))`SELECT id FROM users`;Fragment Embedding
Fragment variables are automatically inlined:
const where = sql.fragment`WHERE id = 1`;
const query = sql.unsafe`SELECT * FROM users ${where}`;
// Analyzed as: SELECT * FROM users WHERE id = 1Support Matrix
Legend: ✅ supported, ⚠️ partial support, ❌ unsupported.
| Library syntax | Support | Notes |
|---|---|---|
sql.unsafe | ✅ | Validated as a query, type annotations skipped |
sql.type(schema) | ✅ | Validated as a query, Zod schema checked against DB result types; suggestions by default, autofix with enforceType: "fix" |
sql.typeAlias("name") | ✅ | Validated as a query, type annotations skipped |
Embedded fragment variables like ${sql.fragment\...`}` | ✅ | Inlined into the outer query |
Standalone sql.fragment | ⚠️ | Intentionally skipped because it is not a complete query on its own |
sql.identifier(["schema", "table"]) | ✅ | Inlined as escaped identifiers |
sql.json(...), sql.jsonb(...), sql.binary(...), sql.date(...), sql.timestamp(...), sql.interval(...), sql.uuid(...) | ✅ | Rewritten to typed SQL placeholders |
sql.array([...], "type") | ✅ | Rewritten as type[] |
sql.unnest([...], ["type1", "type2"]) | ✅ | Rewritten as unnest(type1[], type2[]) |
sql.literalValue("foo") | ✅ | Inlined as a SQL literal |
sql.join(...) | ❌ | Query is skipped because the composition is too dynamic to analyze safely |
Manual Configuration
If you prefer not to use the plugin, you can configure SafeQL manually:
// eslint.config.js
import safeql from "@ts-safeql/eslint-plugin/config";
import tseslint from "typescript-eslint";
export default tseslint.config(
// ...
safeql.configs.connections({
// ... (read more about configuration in the API docs)
targets: [
{
tag: "sql.+(type\\(*\\)|typeAlias\\(*\\)|unsafe)",
skipTypeAnnotations: true,
},
],
}),
);Manual Configuration Limitations
The manual approach doesn't support Zod schema validation, helper translation, or fragment inlining. For full Slonik support, use the plugin.