Lint Plugin
sqlfu ships an ESLint plugin at sqlfu/lint-plugin. It enforces the SQL First model at the editor level: your query file is the source of truth, not an inline string.
Quick setup
Section titled “Quick setup”import sqlfu from 'sqlfu/lint-plugin';
export default [ ...sqlfu.configs.recommended,];The recommended preset runs sqlfu/query-naming and sqlfu/format-sql on TS/JS files, enables the sqlfu/sql processor so .sql files are lintable as well, and checks that generated query wrappers are fresh.
If you use VS Code, make sure the ESLint extension validates .sql files as well as your JS/TS files. Without this, command-line linting works but SQL lint errors may not show in the editor:
{ "eslint.validate": ["markdown", "javascript", "typescript", "sql"]}sqlfu/query-naming
Section titled “sqlfu/query-naming”Flags inline SQL template literals that duplicate a checked-in .sql file.
// flagged: this SQL matches the getPosts query in sql/queries.sqlconst posts = client.all(sql`select id, slug from posts where published = 1`, {});
// correct: use the generated wrapper functionimport {getPosts} from './sql/.generated/queries.sql.ts';const posts = await getPosts(client, {limit: 10});Why: your filename is your query’s identity. An inline duplicate loses the query name, the generated TypeScript types, and the observability metadata (OpenTelemetry span name, Sentry tag, etc.). The rule catches the case where you paste a query inline rather than pointing at the file.
The rule fires on client.all, client.run, client.iterate, and client.sql`...` calls where the template has no interpolations and the normalized SQL matches a file under your project’s queries directory.
sqlfu/format-sql
Section titled “sqlfu/format-sql”Flags SQL that does not match sqlfu’s formatter output, on both inline template literals and standalone .sql files.
-- flaggedSELECT Id, Slug FROM Posts WHERE Published = 1
-- correct (lowercase, sqlfu style)select id, slug from posts where published = 1eslint --fix '**/*.sql' reformats .sql files in place. The same rule autofixes inline template bodies in TS/JS.
sqlfu/generated-query-freshness
Section titled “sqlfu/generated-query-freshness”Flags checked-in .sql query files whose generated manifest or wrapper is missing or stale.
sql/queries.sql # source queriessql/.generated/queries.ts # generated query barrel and manifestsql/.generated/queries.sql.ts # generated by sqlfu generateThe rule only checks .sql files under the configured queries directory. It compares the exact file contents with the sourceSql emitted in .generated/queries.ts. If the manifest is missing, the source query is missing from it, the wrapper is missing, or the SQL text no longer matches exactly, lint reports that sqlfu generate needs to be run.
When ESLint also lints .generated/queries.ts, the same rule reconciles the whole generated query set and reports orphaned generated wrappers left behind after a source query is deleted.
Why: generated wrappers carry the query’s TypeScript surface. If a query changes without regenerating, callers can keep compiling against old parameter and result types.
There is no autofix. Generation may need schema analysis and writes multiple generated files.
Manual configuration
Section titled “Manual configuration”If you need to wire the rules individually rather than using the preset:
import sqlfu from 'sqlfu/lint-plugin';
export default [ { files: ['**/*.{ts,tsx,mts,cts,js,jsx,mjs,cjs}'], plugins: {sqlfu}, rules: { 'sqlfu/query-naming': 'error', 'sqlfu/format-sql': 'error', }, }, { files: ['**/*.sql'], plugins: {sqlfu}, processor: 'sqlfu/sql', }, { files: ['**/*.sql/**/*.js'], plugins: {sqlfu}, rules: { 'sqlfu/format-sql': 'error', 'sqlfu/generated-query-freshness': 'error', }, },];Options
Section titled “Options”The rules accept optional config objects:
'sqlfu/query-naming': ['error', { queriesDir: './sql', // override; defaults to value from sqlfu.config.ts clientIdentifierPattern: /^(client|db)$/u, // customize the client identifier regex}]
'sqlfu/generated-query-freshness': ['error', { queriesDir: './sql', // same override; defaults to value from sqlfu.config.ts}]The sqlfu/sql processor
Section titled “The sqlfu/sql processor”The preset includes an ESLint processor (sqlfu/sql) that makes .sql files lintable by wrapping them in a tagged template literal so ESLint’s JS parser can process them. You only need to configure this separately if you are not using the preset.
Reference implementation note
Section titled “Reference implementation note”The recommended config is a reference implementation, not a locked-forever API. The stable contracts are the rule names (sqlfu/query-naming, sqlfu/format-sql, sqlfu/generated-query-freshness) and the options schema. If your team’s ESLint setup needs a different shape, copy the preset and adjust it — you are not going off-piste.