holywell 1.2.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.holywellrc.json.example +7 -0
- package/CODE_OF_CONDUCT.md +49 -0
- package/LICENSE +21 -0
- package/README.md +538 -0
- package/dist/cli.cjs +7167 -0
- package/dist/index.cjs +6321 -0
- package/dist/index.d.cts +1169 -0
- package/dist/index.d.ts +1169 -0
- package/dist/index.js +6284 -0
- package/package.json +70 -0
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
# Code of Conduct
|
|
2
|
+
|
|
3
|
+
## Our Pledge
|
|
4
|
+
We as members, contributors, and maintainers pledge to make participation in
|
|
5
|
+
this project a harassment-free experience for everyone, regardless of age, body
|
|
6
|
+
size, visible or invisible disability, ethnicity, sex characteristics, gender
|
|
7
|
+
identity and expression, level of experience, education, socio-economic status,
|
|
8
|
+
nationality, personal appearance, race, religion, or sexual identity and
|
|
9
|
+
orientation.
|
|
10
|
+
|
|
11
|
+
## Our Standards
|
|
12
|
+
Examples of behavior that contributes to a positive environment include:
|
|
13
|
+
|
|
14
|
+
- Demonstrating empathy and kindness toward other people.
|
|
15
|
+
- Being respectful of differing opinions, viewpoints, and experiences.
|
|
16
|
+
- Giving and gracefully accepting constructive feedback.
|
|
17
|
+
- Taking responsibility, apologizing to those affected by our mistakes, and
|
|
18
|
+
learning from the experience.
|
|
19
|
+
- Focusing on what is best not just for us as individuals, but for the overall
|
|
20
|
+
community.
|
|
21
|
+
|
|
22
|
+
Examples of unacceptable behavior include:
|
|
23
|
+
|
|
24
|
+
- The use of sexualized language or imagery, and sexual attention or advances.
|
|
25
|
+
- Trolling, insulting or derogatory comments, and personal or political attacks.
|
|
26
|
+
- Public or private harassment.
|
|
27
|
+
- Publishing others' private information, such as a physical or email address,
|
|
28
|
+
without their explicit permission.
|
|
29
|
+
- Other conduct which could reasonably be considered inappropriate in a
|
|
30
|
+
professional setting.
|
|
31
|
+
|
|
32
|
+
## Enforcement Responsibilities
|
|
33
|
+
Project maintainers are responsible for clarifying and enforcing our standards
|
|
34
|
+
of acceptable behavior and will take appropriate and fair corrective action in
|
|
35
|
+
response to behavior that they deem inappropriate, threatening, offensive, or
|
|
36
|
+
harmful.
|
|
37
|
+
|
|
38
|
+
## Scope
|
|
39
|
+
This Code of Conduct applies within all project spaces, and it also applies when
|
|
40
|
+
an individual is officially representing the project in public spaces.
|
|
41
|
+
|
|
42
|
+
## Enforcement
|
|
43
|
+
Instances of abusive, harassing, or otherwise unacceptable behavior may be
|
|
44
|
+
reported to the project maintainers at `vincecoppola@gmail.com`.
|
|
45
|
+
All complaints will be reviewed and investigated promptly and fairly.
|
|
46
|
+
|
|
47
|
+
## Attribution
|
|
48
|
+
This Code of Conduct is adapted from the [Contributor Covenant](https://www.contributor-covenant.org),
|
|
49
|
+
version 2.1.
|
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Vince Coppola
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,538 @@
|
|
|
1
|
+
# holywell
|
|
2
|
+
|
|
3
|
+
[](https://www.npmjs.com/package/holywell)
|
|
4
|
+
[](https://www.npmjs.com/package/holywell)
|
|
5
|
+
[](https://github.com/vinsidious/holywell/blob/main/LICENSE)
|
|
6
|
+
[](https://github.com/vinsidious/holywell/actions)
|
|
7
|
+
[](https://github.com/vinsidious/holywell/tree/main/tests)
|
|
8
|
+
|
|
9
|
+
An opinionated SQL formatter that implements [Simon Holywell's SQL Style Guide](https://www.sqlstyle.guide/). It faithfully applies the guide's formatting rules -- including river alignment, keyword uppercasing, and consistent indentation -- to produce deterministic, readable SQL with minimal configuration.
|
|
10
|
+
|
|
11
|
+
> **Disclaimer:** This project is not officially associated with or endorsed by Simon Holywell or sqlstyle.guide. It is an independent, faithful implementation of the SQL formatting rules described in that style guide.
|
|
12
|
+
|
|
13
|
+
## Quick Start
|
|
14
|
+
|
|
15
|
+
### Install
|
|
16
|
+
|
|
17
|
+
```bash
|
|
18
|
+
npm install holywell
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
### CLI Usage
|
|
22
|
+
|
|
23
|
+
```bash
|
|
24
|
+
# Format a file
|
|
25
|
+
npx holywell query.sql
|
|
26
|
+
|
|
27
|
+
# Format all SQL files
|
|
28
|
+
npx holywell "**/*.sql"
|
|
29
|
+
|
|
30
|
+
# Check formatting (CI mode)
|
|
31
|
+
npx holywell --check "**/*.sql"
|
|
32
|
+
|
|
33
|
+
# Format in place
|
|
34
|
+
npx holywell --write "**/*.sql"
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
### Programmatic Usage
|
|
38
|
+
|
|
39
|
+
```typescript
|
|
40
|
+
import { formatSQL } from 'holywell';
|
|
41
|
+
|
|
42
|
+
const formatted = formatSQL('select id, name from users where active = true;');
|
|
43
|
+
// Output:
|
|
44
|
+
// SELECT id, name
|
|
45
|
+
// FROM users
|
|
46
|
+
// WHERE active = TRUE;
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
## Table of Contents
|
|
50
|
+
|
|
51
|
+
- [What it does](#what-it-does)
|
|
52
|
+
- [When NOT to use holywell](#when-not-to-use-holywell)
|
|
53
|
+
- [SQL Dialect Support](#sql-dialect-support)
|
|
54
|
+
- [CLI Reference](#cli-reference)
|
|
55
|
+
- [API Guide](#api-guide)
|
|
56
|
+
- [How the formatter works](#how-the-formatter-works)
|
|
57
|
+
- [Edge Cases & Behavior](#edge-cases--behavior)
|
|
58
|
+
- [FAQ](#faq)
|
|
59
|
+
- [Documentation](#documentation)
|
|
60
|
+
- [Development](#development)
|
|
61
|
+
- [Performance](#performance)
|
|
62
|
+
- [Limitations](#limitations)
|
|
63
|
+
- [License](#license)
|
|
64
|
+
|
|
65
|
+
## What it does
|
|
66
|
+
|
|
67
|
+
Takes messy SQL and formats it according to the [Simon Holywell SQL Style Guide](https://www.sqlstyle.guide/). A key technique from the guide is **river alignment** -- right-aligning keywords so content flows along a consistent vertical column:
|
|
68
|
+
|
|
69
|
+
```sql
|
|
70
|
+
-- Input
|
|
71
|
+
select e.name, e.salary, d.department_name from employees as e inner join departments as d on e.department_id = d.department_id where e.salary > 50000 and d.department_name in ('Sales', 'Engineering') order by e.salary desc;
|
|
72
|
+
|
|
73
|
+
-- Output
|
|
74
|
+
SELECT e.name, e.salary, d.department_name
|
|
75
|
+
FROM employees AS e
|
|
76
|
+
INNER JOIN departments AS d
|
|
77
|
+
ON e.department_id = d.department_id
|
|
78
|
+
WHERE e.salary > 50000
|
|
79
|
+
AND d.department_name IN ('Sales', 'Engineering')
|
|
80
|
+
ORDER BY e.salary DESC;
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
### More examples
|
|
84
|
+
|
|
85
|
+
**Multi-table JOINs:**
|
|
86
|
+
|
|
87
|
+
```sql
|
|
88
|
+
-- Input
|
|
89
|
+
select o.id, c.name, p.title, o.total from orders o join customers c on o.customer_id = c.id left join products p on o.product_id = p.id left join shipping s on o.id = s.order_id where o.created_at > '2024-01-01' and s.status = 'delivered' order by o.created_at desc;
|
|
90
|
+
|
|
91
|
+
-- Output
|
|
92
|
+
SELECT o.id, c.name, p.title, o.total
|
|
93
|
+
FROM orders AS o
|
|
94
|
+
JOIN customers AS c
|
|
95
|
+
ON o.customer_id = c.id
|
|
96
|
+
|
|
97
|
+
LEFT JOIN products AS p
|
|
98
|
+
ON o.product_id = p.id
|
|
99
|
+
|
|
100
|
+
LEFT JOIN shipping AS s
|
|
101
|
+
ON o.id = s.order_id
|
|
102
|
+
WHERE o.created_at > '2024-01-01'
|
|
103
|
+
AND s.status = 'delivered'
|
|
104
|
+
ORDER BY o.created_at DESC;
|
|
105
|
+
```
|
|
106
|
+
|
|
107
|
+
**CTEs (Common Table Expressions):**
|
|
108
|
+
|
|
109
|
+
```sql
|
|
110
|
+
-- Input
|
|
111
|
+
with monthly_totals as (select date_trunc('month', created_at) as month, sum(amount) as total from payments group by 1), running as (select month, total, sum(total) over (order by month) as cumulative from monthly_totals) select * from running where cumulative > 10000;
|
|
112
|
+
|
|
113
|
+
-- Output
|
|
114
|
+
WITH monthly_totals AS (
|
|
115
|
+
SELECT DATE_TRUNC('month', created_at) AS month,
|
|
116
|
+
SUM(amount) AS total
|
|
117
|
+
FROM payments
|
|
118
|
+
GROUP BY 1
|
|
119
|
+
),
|
|
120
|
+
running AS (
|
|
121
|
+
SELECT month, total, SUM(total) OVER (ORDER BY month) AS cumulative
|
|
122
|
+
FROM monthly_totals
|
|
123
|
+
)
|
|
124
|
+
SELECT *
|
|
125
|
+
FROM running
|
|
126
|
+
WHERE cumulative > 10000;
|
|
127
|
+
```
|
|
128
|
+
|
|
129
|
+
**Window functions:**
|
|
130
|
+
|
|
131
|
+
```sql
|
|
132
|
+
-- Input
|
|
133
|
+
select department, employee, salary, rank() over (partition by department order by salary desc) as dept_rank, salary - avg(salary) over (partition by department) as diff_from_avg from employees;
|
|
134
|
+
|
|
135
|
+
-- Output
|
|
136
|
+
SELECT department,
|
|
137
|
+
employee,
|
|
138
|
+
salary,
|
|
139
|
+
RANK() OVER (PARTITION BY department
|
|
140
|
+
ORDER BY salary DESC) AS dept_rank,
|
|
141
|
+
salary - AVG(salary) OVER (PARTITION BY department) AS diff_from_avg
|
|
142
|
+
FROM employees;
|
|
143
|
+
```
|
|
144
|
+
|
|
145
|
+
**CASE expressions:**
|
|
146
|
+
|
|
147
|
+
```sql
|
|
148
|
+
-- Input
|
|
149
|
+
select name, case status when 'A' then 'Active' when 'I' then 'Inactive' when 'P' then 'Pending' else 'Unknown' end as status_label, case when balance > 10000 then 'high' when balance > 1000 then 'medium' else 'low' end as tier from accounts;
|
|
150
|
+
|
|
151
|
+
-- Output
|
|
152
|
+
SELECT name,
|
|
153
|
+
CASE status
|
|
154
|
+
WHEN 'A' THEN 'Active'
|
|
155
|
+
WHEN 'I' THEN 'Inactive'
|
|
156
|
+
WHEN 'P' THEN 'Pending'
|
|
157
|
+
ELSE 'Unknown'
|
|
158
|
+
END AS status_label,
|
|
159
|
+
CASE
|
|
160
|
+
WHEN balance > 10000 THEN 'high'
|
|
161
|
+
WHEN balance > 1000 THEN 'medium'
|
|
162
|
+
ELSE 'low'
|
|
163
|
+
END AS tier
|
|
164
|
+
FROM accounts;
|
|
165
|
+
```
|
|
166
|
+
|
|
167
|
+
## When NOT to use holywell
|
|
168
|
+
|
|
169
|
+
- **You need highly configurable style output** -- holywell intentionally does not expose style knobs for indentation strategy, keyword casing, or alignment mode. If you need full style customization, use [sql-formatter](https://github.com/sql-formatter-org/sql-formatter) or [prettier-plugin-sql](https://github.com/JounQin/prettier-plugin-sql).
|
|
170
|
+
- **You exclusively target MySQL or SQL Server** -- holywell is PostgreSQL-first. Standard ANSI SQL works fine, but vendor-specific syntax (stored procedures, MySQL-only functions) may not be fully parsed.
|
|
171
|
+
- **You need a language server** -- holywell is a formatter, not a linter or LSP. It does not provide diagnostics, completions, or semantic analysis.
|
|
172
|
+
|
|
173
|
+
## SQL Dialect Support
|
|
174
|
+
|
|
175
|
+
| Dialect | Status | Notes |
|
|
176
|
+
|---|---|---|
|
|
177
|
+
| PostgreSQL | Primary / continuously tested | Full formatter/parser coverage target |
|
|
178
|
+
| ANSI SQL core | Broad support | Most query/DDL patterns covered |
|
|
179
|
+
| MySQL | Partial | Many ANSI queries work; MySQL-specific extensions may recover as raw |
|
|
180
|
+
| SQL Server (T-SQL) | Partial | Many ANSI queries work; procedural T-SQL is limited |
|
|
181
|
+
| SQLite | Partial | Common ANSI queries work; SQLite-specific extensions are limited |
|
|
182
|
+
|
|
183
|
+
holywell test coverage is PostgreSQL-first. If you rely on non-PostgreSQL vendor extensions, run `--check` in CI and prefer `--strict` where parse failures should block merges.
|
|
184
|
+
|
|
185
|
+
You can extend keyword/clause recognition without forking:
|
|
186
|
+
|
|
187
|
+
```typescript
|
|
188
|
+
import { formatSQL } from 'holywell';
|
|
189
|
+
|
|
190
|
+
const formatted = formatSQL(sql, {
|
|
191
|
+
dialect: {
|
|
192
|
+
additionalKeywords: ['QUALIFY', 'TOP'],
|
|
193
|
+
clauseKeywords: ['QUALIFY'],
|
|
194
|
+
},
|
|
195
|
+
});
|
|
196
|
+
```
|
|
197
|
+
|
|
198
|
+
### PostgreSQL (Full Support)
|
|
199
|
+
|
|
200
|
+
- Type casts (`::integer`), JSON operators (`->`, `->>`), dollar-quoting (`$$...$$`)
|
|
201
|
+
- Array constructors, window functions, CTEs, LATERAL joins
|
|
202
|
+
- ON CONFLICT (UPSERT), RETURNING clauses
|
|
203
|
+
- **Note:** PL/pgSQL function bodies are preserved verbatim (not reformatted)
|
|
204
|
+
|
|
205
|
+
### ANSI SQL (Full Support)
|
|
206
|
+
|
|
207
|
+
- SELECT, INSERT, UPDATE, DELETE, MERGE
|
|
208
|
+
- JOINs (INNER, LEFT, RIGHT, FULL, CROSS, NATURAL)
|
|
209
|
+
- CTEs (WITH, WITH RECURSIVE)
|
|
210
|
+
- Window functions (PARTITION BY, ORDER BY, frame clauses)
|
|
211
|
+
- DDL (CREATE TABLE, ALTER TABLE, DROP, CREATE INDEX, CREATE VIEW)
|
|
212
|
+
|
|
213
|
+
### MySQL (Partial)
|
|
214
|
+
|
|
215
|
+
- Standard ANSI SQL queries format correctly
|
|
216
|
+
- Backtick identifiers, LIMIT offset syntax, and storage engine clauses are not yet supported
|
|
217
|
+
|
|
218
|
+
### SQL Server (Partial)
|
|
219
|
+
|
|
220
|
+
- Standard ANSI SQL queries format correctly
|
|
221
|
+
- T-SQL procedural syntax (BEGIN/END blocks, DECLARE, @@variables) is not yet supported
|
|
222
|
+
|
|
223
|
+
### Recovery Mode
|
|
224
|
+
|
|
225
|
+
Unsupported syntax is passed through unchanged rather than causing errors. Use `--strict` to fail on unparseable SQL.
|
|
226
|
+
|
|
227
|
+
## Style Guide
|
|
228
|
+
|
|
229
|
+
This formatter implements the [Simon Holywell SQL Style Guide](https://www.sqlstyle.guide/). Key principles from the guide that holywell enforces:
|
|
230
|
+
|
|
231
|
+
- **River alignment** -- Clause/logical keywords are right-aligned to a per-statement river width derived from the longest top-level aligned keyword
|
|
232
|
+
- **Keyword uppercasing** -- Reserved words like `SELECT`, `FROM`, `WHERE` are uppercased
|
|
233
|
+
- **Identifier normalization** -- Most unquoted identifiers are lowercased; quoted identifiers are preserved
|
|
234
|
+
- **Right-aligned clause/logical keywords** -- `SELECT`, `FROM`, `WHERE`, `AND`, `OR`, `JOIN`, `ON`, `ORDER BY`, `GROUP BY`, etc. align within each formatted block
|
|
235
|
+
- **Consistent indentation** -- Continuation lines and subexpressions are indented predictably
|
|
236
|
+
|
|
237
|
+
For the full style guide, see [sqlstyle.guide](https://www.sqlstyle.guide/) or the [source on GitHub](https://github.com/treffynnon/sqlstyle.guide).
|
|
238
|
+
|
|
239
|
+
## Why holywell?
|
|
240
|
+
|
|
241
|
+
| | **holywell** | sql-formatter | prettier-plugin-sql |
|
|
242
|
+
|---|---|---|---|
|
|
243
|
+
| **Formatting style** | River alignment ([sqlstyle.guide](https://www.sqlstyle.guide/)) | Indentation-based | Indentation-based |
|
|
244
|
+
| **Configuration** | Opinionated defaults + small operational config (`.holywellrc.json`) | Configurable | Configurable via Prettier |
|
|
245
|
+
| **PostgreSQL support** | First-class (casts, JSON ops, dollar-quoting, arrays) | Partial | Partial |
|
|
246
|
+
| **Runtime dependencies** | Zero | Several | Prettier + parser |
|
|
247
|
+
| **Idempotent** | Yes | Yes | Yes |
|
|
248
|
+
| **Keyword casing** | Uppercase (enforced) | Configurable | Configurable |
|
|
249
|
+
| **Identifier casing** | Lowercase (enforced) | Not modified | Not modified |
|
|
250
|
+
| **Output** | Deterministic, single style | Depends on config | Depends on config |
|
|
251
|
+
|
|
252
|
+
holywell is the right choice when you want consistent, readable SQL with minimal setup and deterministic style.
|
|
253
|
+
|
|
254
|
+
### Configuration philosophy
|
|
255
|
+
|
|
256
|
+
holywell keeps style deterministic by design: no indentation/casing style matrix, no formatter presets.
|
|
257
|
+
It does support a focused optional config file (`.holywellrc.json`) for operational settings:
|
|
258
|
+
|
|
259
|
+
- `maxLineLength`
|
|
260
|
+
- `maxDepth`
|
|
261
|
+
- `maxInputSize`
|
|
262
|
+
- `strict`
|
|
263
|
+
- `recover`
|
|
264
|
+
|
|
265
|
+
CLI flags still override config values.
|
|
266
|
+
|
|
267
|
+
A starter config is available at `.holywellrc.json.example`.
|
|
268
|
+
|
|
269
|
+
## CLI Reference
|
|
270
|
+
|
|
271
|
+
```bash
|
|
272
|
+
# Format a file (prints to stdout by default)
|
|
273
|
+
npx holywell query.sql
|
|
274
|
+
|
|
275
|
+
# Format a file in place
|
|
276
|
+
npx holywell --write query.sql
|
|
277
|
+
|
|
278
|
+
# Format from stdin
|
|
279
|
+
cat query.sql | npx holywell
|
|
280
|
+
|
|
281
|
+
# Check if a file is already formatted (exits non-zero if not)
|
|
282
|
+
npx holywell --check query.sql
|
|
283
|
+
|
|
284
|
+
# List files that would change (useful in CI)
|
|
285
|
+
npx holywell --list-different "src/**/*.sql"
|
|
286
|
+
npx holywell -l "migrations/*.sql"
|
|
287
|
+
|
|
288
|
+
# Strict mode: fail on unparseable SQL instead of passing through
|
|
289
|
+
npx holywell --strict --check "**/*.sql"
|
|
290
|
+
|
|
291
|
+
# Tune output width
|
|
292
|
+
npx holywell --max-line-length 100 query.sql
|
|
293
|
+
|
|
294
|
+
# Use project config
|
|
295
|
+
npx holywell --config .holywellrc.json --check "**/*.sql"
|
|
296
|
+
|
|
297
|
+
# Ignore files (can repeat --ignore)
|
|
298
|
+
npx holywell --check --ignore "migrations/**" "**/*.sql"
|
|
299
|
+
|
|
300
|
+
# Or store ignore patterns in .holywellignore (one pattern per line)
|
|
301
|
+
npx holywell --check "**/*.sql"
|
|
302
|
+
|
|
303
|
+
# Control color in CI/logs
|
|
304
|
+
npx holywell --color=always --check query.sql
|
|
305
|
+
|
|
306
|
+
# Generate shell completion
|
|
307
|
+
npx holywell --completion bash
|
|
308
|
+
npx holywell --completion zsh
|
|
309
|
+
npx holywell --completion fish
|
|
310
|
+
|
|
311
|
+
# Pipe patterns
|
|
312
|
+
pbpaste | npx holywell | pbcopy # Format clipboard (macOS)
|
|
313
|
+
pg_dump mydb --schema-only | npx holywell > schema.sql
|
|
314
|
+
echo "select 1" | npx holywell
|
|
315
|
+
```
|
|
316
|
+
|
|
317
|
+
By default, `npx holywell query.sql` prints formatted output to **stdout**. Use `--write` to modify the file in place.
|
|
318
|
+
|
|
319
|
+
When present, `.holywellignore` is read from the current working directory and combined with any `--ignore` flags.
|
|
320
|
+
|
|
321
|
+
**CLI exit codes:**
|
|
322
|
+
|
|
323
|
+
| Code | Meaning |
|
|
324
|
+
|------|---------|
|
|
325
|
+
| `0` | Success (or all files already formatted with `--check`) |
|
|
326
|
+
| `1` | Check failure |
|
|
327
|
+
| `2` | Parse or tokenize error |
|
|
328
|
+
| `3` | Usage or I/O error |
|
|
329
|
+
|
|
330
|
+
## API Guide
|
|
331
|
+
|
|
332
|
+
### Basic Usage
|
|
333
|
+
|
|
334
|
+
```typescript
|
|
335
|
+
import { formatSQL } from 'holywell';
|
|
336
|
+
|
|
337
|
+
const formatted = formatSQL('SELECT * FROM users;');
|
|
338
|
+
```
|
|
339
|
+
|
|
340
|
+
### Synchronous API by design
|
|
341
|
+
|
|
342
|
+
`formatSQL`, `parse`, and `tokenize` are intentionally synchronous.
|
|
343
|
+
This keeps editor/CLI integration predictable and avoids hidden async overhead.
|
|
344
|
+
|
|
345
|
+
### Error Recovery
|
|
346
|
+
|
|
347
|
+
By default, unparseable SQL is passed through unchanged:
|
|
348
|
+
|
|
349
|
+
```typescript
|
|
350
|
+
const warnings: string[] = [];
|
|
351
|
+
const formatted = formatSQL(sql, {
|
|
352
|
+
onRecover: (error, raw) => {
|
|
353
|
+
warnings.push(`Line ${error.token.line}: ${error.message}`);
|
|
354
|
+
}
|
|
355
|
+
});
|
|
356
|
+
```
|
|
357
|
+
|
|
358
|
+
### Strict Mode (throw on parse errors)
|
|
359
|
+
|
|
360
|
+
```typescript
|
|
361
|
+
import { formatSQL, ParseError } from 'holywell';
|
|
362
|
+
|
|
363
|
+
try {
|
|
364
|
+
formatSQL(sql, { recover: false });
|
|
365
|
+
} catch (err) {
|
|
366
|
+
if (err instanceof ParseError) {
|
|
367
|
+
console.error(`Parse error: ${err.message}`);
|
|
368
|
+
}
|
|
369
|
+
}
|
|
370
|
+
```
|
|
371
|
+
|
|
372
|
+
### Depth Limits
|
|
373
|
+
|
|
374
|
+
```typescript
|
|
375
|
+
formatSQL(sql, { maxDepth: 300 }); // Increase for deeply nested CTEs
|
|
376
|
+
```
|
|
377
|
+
|
|
378
|
+
### Input Size Limits
|
|
379
|
+
|
|
380
|
+
```typescript
|
|
381
|
+
formatSQL(sql, { maxInputSize: 5_000_000 }); // 5MB limit (default: 10MB)
|
|
382
|
+
```
|
|
383
|
+
|
|
384
|
+
### Low-Level Access
|
|
385
|
+
|
|
386
|
+
```typescript
|
|
387
|
+
import { tokenize, parse, formatStatements, visitAst } from 'holywell';
|
|
388
|
+
|
|
389
|
+
// Tokenize SQL into a token stream
|
|
390
|
+
const tokens = tokenize(sql);
|
|
391
|
+
|
|
392
|
+
// Parse SQL into an AST
|
|
393
|
+
const ast = parse(sql);
|
|
394
|
+
|
|
395
|
+
// Format AST nodes back to SQL
|
|
396
|
+
const output = formatStatements(ast);
|
|
397
|
+
|
|
398
|
+
// Visit AST nodes (for custom linting/analysis)
|
|
399
|
+
visitAst(ast, {
|
|
400
|
+
byType: {
|
|
401
|
+
select(node) {
|
|
402
|
+
console.log('SELECT node:', node);
|
|
403
|
+
},
|
|
404
|
+
},
|
|
405
|
+
});
|
|
406
|
+
```
|
|
407
|
+
|
|
408
|
+
### Error Types
|
|
409
|
+
|
|
410
|
+
```typescript
|
|
411
|
+
import { formatSQL, TokenizeError, ParseError, MaxDepthError } from 'holywell';
|
|
412
|
+
|
|
413
|
+
try {
|
|
414
|
+
const result = formatSQL(input);
|
|
415
|
+
} catch (err) {
|
|
416
|
+
if (err instanceof TokenizeError) {
|
|
417
|
+
// Invalid token encountered during lexing (e.g., unterminated string)
|
|
418
|
+
console.error(`Tokenize error at position ${err.position}: ${err.message}`);
|
|
419
|
+
} else if (err instanceof MaxDepthError) {
|
|
420
|
+
// Parser nesting exceeded configured maxDepth
|
|
421
|
+
console.error(`Parse depth exceeded: ${err.message}`);
|
|
422
|
+
} else if (err instanceof ParseError) {
|
|
423
|
+
// Structural error in the SQL (e.g., unmatched parentheses)
|
|
424
|
+
console.error(`Parse error: ${err.message}`);
|
|
425
|
+
} else if (err instanceof Error && err.message.includes('Input exceeds maximum size')) {
|
|
426
|
+
// Input exceeded maxInputSize
|
|
427
|
+
console.error(`Input too large: ${err.message}`);
|
|
428
|
+
} else {
|
|
429
|
+
throw err;
|
|
430
|
+
}
|
|
431
|
+
}
|
|
432
|
+
```
|
|
433
|
+
|
|
434
|
+
## How the formatter works
|
|
435
|
+
|
|
436
|
+
```
|
|
437
|
+
SQL Text → Tokenizer → Parser → AST → Formatter → Formatted SQL
|
|
438
|
+
```
|
|
439
|
+
|
|
440
|
+
1. **Tokenizer** (`src/tokenizer.ts`) -- Splits SQL text into tokens (keywords, identifiers, literals, operators, comments)
|
|
441
|
+
2. **Parser** (`src/parser.ts`) -- Builds an AST from the token stream
|
|
442
|
+
3. **Formatter** (`src/formatter.ts`) -- Walks the AST and produces formatted output
|
|
443
|
+
|
|
444
|
+
The key formatting concept is the **river**. For each statement, holywell derives a river width from the longest top-level aligned keyword in that statement (for example, `RETURNING` can widen DML alignment). Clause/logical keywords are then right-aligned to that width so content starts in a consistent column. Nested blocks may use their own derived widths. This approach comes directly from the [Simon Holywell SQL Style Guide](https://www.sqlstyle.guide/).
|
|
445
|
+
|
|
446
|
+
## Edge Cases & Behavior
|
|
447
|
+
|
|
448
|
+
### Long Lines
|
|
449
|
+
|
|
450
|
+
holywell targets 80-column output by default and supports `maxLineLength` (CLI flag or config file). It does not break individual tokens (identifiers, string literals), so single-token lines can still exceed the configured width.
|
|
451
|
+
|
|
452
|
+
### Comment Preservation
|
|
453
|
+
|
|
454
|
+
Line comments and block comments are preserved. Comments attached to specific expressions maintain their association.
|
|
455
|
+
|
|
456
|
+
### Keyword Casing
|
|
457
|
+
|
|
458
|
+
All SQL keywords are uppercased. Identifiers are preserved as-is (quoted identifiers keep their case and quotes). Unquoted identifiers are lowercased.
|
|
459
|
+
|
|
460
|
+
### Idempotency
|
|
461
|
+
|
|
462
|
+
Formatting is idempotent: `formatSQL(formatSQL(x)) === formatSQL(x)` for all valid inputs.
|
|
463
|
+
|
|
464
|
+
## FAQ
|
|
465
|
+
|
|
466
|
+
**Q: Can I change the indentation style or keyword casing?**
|
|
467
|
+
|
|
468
|
+
No. Style output is intentionally fixed. holywell provides operational configuration (line length, strictness/safety), not style customization.
|
|
469
|
+
|
|
470
|
+
**Q: What happens with SQL syntax holywell doesn't understand?**
|
|
471
|
+
|
|
472
|
+
In default (recovery) mode, unrecognized statements are passed through unchanged. Use `--strict` to fail instead.
|
|
473
|
+
|
|
474
|
+
**Q: How fast is holywell?**
|
|
475
|
+
|
|
476
|
+
~30,000+ statements/second on modern hardware. A typical migration file formats in <10ms.
|
|
477
|
+
|
|
478
|
+
**Q: Does holywell modify SQL semantics?**
|
|
479
|
+
|
|
480
|
+
No. holywell changes whitespace, uppercases SQL keywords, lowercases unquoted identifiers, and normalizes alias syntax (e.g., inserting AS). Quoted identifiers and string literals are preserved exactly. The semantic meaning is preserved.
|
|
481
|
+
|
|
482
|
+
**Q: Does holywell respect `.editorconfig`?**
|
|
483
|
+
|
|
484
|
+
No. holywell does not read `.editorconfig`. It does read `.holywellrc.json` (or `--config`) for operational settings, but style output remains deterministic.
|
|
485
|
+
|
|
486
|
+
**Q: Can I customize the river width?**
|
|
487
|
+
|
|
488
|
+
Not directly. River width is derived automatically from statement structure. You can influence wrapping via `maxLineLength`, but keyword alignment behavior itself is fixed.
|
|
489
|
+
|
|
490
|
+
**Q: Does formatting change SQL semantics?**
|
|
491
|
+
|
|
492
|
+
holywell only changes whitespace and casing. Specifically: SQL keywords are uppercased (`select` becomes `SELECT`), unquoted identifiers are lowercased (`MyTable` becomes `mytable`), and quoted identifiers are preserved exactly (`"MyTable"` stays `"MyTable"`). If your database is case-sensitive for unquoted identifiers (rare, but possible), see the [Migration Guide](docs/migration-guide.md) for details.
|
|
493
|
+
|
|
494
|
+
**Q: Does holywell work with MySQL / SQL Server / SQLite?**
|
|
495
|
+
|
|
496
|
+
holywell is PostgreSQL-first, but any query written in standard ANSI SQL will format correctly regardless of your target database. Vendor-specific extensions (stored procedures, MySQL-only syntax) may not be fully parsed. See [SQL Dialect Support](#sql-dialect-support) for details.
|
|
497
|
+
|
|
498
|
+
## Documentation
|
|
499
|
+
|
|
500
|
+
- [Integrations](docs/integrations.md) -- Pre-commit hooks, CI pipelines, and editor setup recipes
|
|
501
|
+
- [Architecture](docs/architecture.md) -- Internal pipeline and design decisions
|
|
502
|
+
- [Style Guide Mapping](docs/style-guide.md) -- How holywell maps to each rule in the Simon Holywell SQL Style Guide
|
|
503
|
+
- [Migration Guide](docs/migration-guide.md) -- Rolling out holywell in existing codebases with minimal churn
|
|
504
|
+
- [Contributing](CONTRIBUTING.md) -- Development setup, running tests, and submitting changes
|
|
505
|
+
- [Changelog](CHANGELOG.md) -- Release history
|
|
506
|
+
|
|
507
|
+
## Development
|
|
508
|
+
|
|
509
|
+
Requires [Bun](https://bun.sh/).
|
|
510
|
+
|
|
511
|
+
```bash
|
|
512
|
+
# Install dependencies
|
|
513
|
+
bun install
|
|
514
|
+
|
|
515
|
+
# Run tests
|
|
516
|
+
bun test
|
|
517
|
+
|
|
518
|
+
# Type check
|
|
519
|
+
bun run check
|
|
520
|
+
|
|
521
|
+
# Build dist (for npm publishing)
|
|
522
|
+
bun run build
|
|
523
|
+
```
|
|
524
|
+
|
|
525
|
+
## Performance
|
|
526
|
+
|
|
527
|
+
holywell has zero runtime dependencies and formats SQL through a single tokenize-parse-format pass. Typical throughput is 30,000+ statements per second on modern hardware. Input is bounded by default size limits to prevent excessive memory use on untrusted input.
|
|
528
|
+
|
|
529
|
+
## Limitations
|
|
530
|
+
|
|
531
|
+
- Dialect coverage is broad but intentionally pragmatic, with strongest support for PostgreSQL-style syntax.
|
|
532
|
+
- Procedural SQL bodies (`CREATE FUNCTION ... LANGUAGE plpgsql` control-flow blocks, vendor-specific scripting extensions) are not fully parsed as procedural ASTs.
|
|
533
|
+
- Unknown/unsupported constructs may fall back to raw statement preservation.
|
|
534
|
+
- Formatting style is opinionated and focused on faithfully implementing the [Simon Holywell SQL Style Guide](https://www.sqlstyle.guide/) rather than per-project style configurability.
|
|
535
|
+
|
|
536
|
+
## License
|
|
537
|
+
|
|
538
|
+
MIT
|