snowkit 0.0.0-stage → 0.1.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.
@@ -0,0 +1,100 @@
1
+ const EXCEPTIONS = { the: 1, every: 2, people: 2, business: 2, different: 3, area: 3, idea: 3, really: 2, being: 2, poem: 2, science: 2, quiet: 2, real: 1, create: 2, created: 3, recreate: 3, naive: 2, cooperate: 4, simile: 3, recipe: 3, apostrophe: 4, epitome: 4, coyote: 3, guacamole: 4, karate: 3, maybe: 2, someone: 2, sometimes: 2, somewhere: 2, everyone: 3, anyone: 3, lifetime: 2, likely: 2, ion: 2, lion: 2, giant: 2, diet: 2, client: 2, fluid: 2 };
2
+
3
+ export function syllables(word) {
4
+ let w = String(word).toLowerCase().replace(/[^a-z]/g, "");
5
+ if (!w) return 0;
6
+ if (EXCEPTIONS[w]) return EXCEPTIONS[w];
7
+ if (w.length <= 3) return 1;
8
+ w = w.replace(/(?:[^laeiouy]es|[^laeiouy]ed|[^laeiouy]e)$/, "").replace(/^y/, "");
9
+ const groups = w.match(/[aeiouy]+/g);
10
+ return Math.max(1, groups ? groups.length : 1);
11
+ }
12
+
13
+ export function splitSentences(text) {
14
+ return String(text)
15
+ .replace(/\b(Mr|Mrs|Ms|Dr|Prof|Sr|Jr|St|vs|etc|e\.g|i\.e|U\.S|Inc|Ltd|No)\./gi, (m) => m.replace(/\./g, "\u2024"))
16
+ .split(/(?<=[.!?\u2026])["'\u201D\u2019)]*\s+|\n{2,}/)
17
+ .map((s) => s.trim())
18
+ .filter((s) => /[a-z0-9]/i.test(s));
19
+ }
20
+
21
+ export function words(text) {
22
+ return String(text).match(/[A-Za-z0-9\u00C0-\u024F]+(?:['\u2019\-][A-Za-z0-9\u00C0-\u024F]+)*/g) || [];
23
+ }
24
+
25
+ const round1 = (v) => Math.round(v * 10) / 10;
26
+
27
+ export function readability(text, { wpm = 238 } = {}) {
28
+ const sentences = splitSentences(text);
29
+ const ws = words(text);
30
+ const nWords = ws.length;
31
+ const nSent = Math.max(1, sentences.length);
32
+ if (!nWords) return null;
33
+ let syl = 0;
34
+ let poly = 0;
35
+ let complex = 0;
36
+ let letters = 0;
37
+ let longWords = 0;
38
+ for (const w of ws) {
39
+ const s = syllables(w);
40
+ syl += s;
41
+ if (s >= 3) {
42
+ poly++;
43
+ if (!/^[A-Z]/.test(w) && !/(es|ed|ing)$/.test(w.toLowerCase())) complex++;
44
+ }
45
+ letters += w.replace(/[^A-Za-z0-9\u00C0-\u024F]/g, "").length;
46
+ if (w.length > 6) longWords++;
47
+ }
48
+ const wps = nWords / nSent;
49
+ const spw = syl / nWords;
50
+ const L = (letters / nWords) * 100;
51
+ const S = (nSent / nWords) * 100;
52
+ const fleschEase = 206.835 - 1.015 * wps - 84.6 * spw;
53
+ const fkGrade = 0.39 * wps + 11.8 * spw - 15.59;
54
+ const fog = 0.4 * (wps + 100 * (complex / nWords));
55
+ const smog = 1.043 * Math.sqrt(poly * (30 / nSent)) + 3.1291;
56
+ const coleman = 0.0588 * L - 0.296 * S - 15.8;
57
+ const ari = 4.71 * (letters / nWords) + 0.5 * wps - 21.43;
58
+ const grades = [fkGrade, fog, smog, coleman, ari];
59
+ const avgGrade = grades.reduce((a, b) => a + b, 0) / grades.length;
60
+ const lens = sentences.map((s) => words(s).length);
61
+ return {
62
+ words: nWords,
63
+ sentences: sentences.length,
64
+ syllables: syl,
65
+ characters: letters,
66
+ paragraphs: String(text).split(/\n\s*\n/).filter((p) => p.trim()).length,
67
+ wordsPerSentence: round1(wps),
68
+ syllablesPerWord: Math.round(spw * 100) / 100,
69
+ complexWords: poly,
70
+ longWords,
71
+ fleschEase: round1(Math.max(0, Math.min(121, fleschEase))),
72
+ fleschKincaid: round1(Math.max(0, fkGrade)),
73
+ gunningFog: round1(Math.max(0, fog)),
74
+ smog: round1(Math.max(0, smog)),
75
+ colemanLiau: round1(Math.max(0, coleman)),
76
+ ari: round1(Math.max(0, ari)),
77
+ averageGrade: round1(Math.max(0, avgGrade)),
78
+ readingMinutes: nWords / wpm,
79
+ speakingMinutes: nWords / 150,
80
+ longSentences: sentences.filter((s, i) => lens[i] > 25),
81
+ longestSentence: Math.max(0, ...lens),
82
+ };
83
+ }
84
+
85
+ export function easeLabel(score) {
86
+ if (score >= 90) return "Very easy";
87
+ if (score >= 80) return "Easy";
88
+ if (score >= 70) return "Fairly easy";
89
+ if (score >= 60) return "Plain English";
90
+ if (score >= 50) return "Fairly hard";
91
+ if (score >= 30) return "Hard";
92
+ return "Very hard";
93
+ }
94
+
95
+ export function gradeLabel(g) {
96
+ if (g < 1) return "Kindergarten";
97
+ if (g <= 12) return "Grade " + Math.round(g);
98
+ if (g <= 16) return "College";
99
+ return "Graduate";
100
+ }
@@ -0,0 +1,425 @@
1
+ const KEYWORDS = new Set(
2
+ (
3
+ "SELECT FROM WHERE AND OR NOT IN IS NULL LIKE ILIKE BETWEEN EXISTS AS ON USING JOIN INNER LEFT RIGHT FULL OUTER CROSS NATURAL LATERAL APPLY " +
4
+ "GROUP BY ORDER HAVING LIMIT OFFSET FETCH FIRST NEXT ROWS ROW ONLY UNION ALL INTERSECT EXCEPT MINUS DISTINCT INSERT INTO VALUES UPDATE SET " +
5
+ "DELETE CREATE TABLE VIEW INDEX UNIQUE PRIMARY FOREIGN KEY REFERENCES CONSTRAINT DEFAULT CHECK ALTER ADD DROP COLUMN RENAME TO TRUNCATE IF " +
6
+ "CASCADE RESTRICT WITH RECURSIVE CASE WHEN THEN ELSE END ASC DESC NULLS TRUE FALSE OVER PARTITION WINDOW RETURNING CONFLICT DO NOTHING " +
7
+ "DUPLICATE TEMPORARY TEMP REPLACE MERGE MATCHED BEGIN COMMIT ROLLBACK TRANSACTION GRANT REVOKE AUTO_INCREMENT AUTOINCREMENT IDENTITY ANY SOME " +
8
+ "QUALIFY FILTER WITHIN COLLATE ESCAPE SIMILAR INTERVAL TOP SCHEMA DATABASE TRIGGER PROCEDURE FUNCTION RETURNS LANGUAGE EXPLAIN ANALYZE " +
9
+ "PRECEDING FOLLOWING UNBOUNDED RANGE FOR SHOW DESCRIBE USE EXEC EXECUTE DECLARE " +
10
+ "COUNT SUM AVG MIN MAX COALESCE NULLIF CAST CONVERT UPPER LOWER LENGTH SUBSTRING SUBSTR TRIM ROUND NOW CURRENT_DATE CURRENT_TIMESTAMP " +
11
+ "CURRENT_TIME EXTRACT ROW_NUMBER RANK DENSE_RANK LAG LEAD CONCAT IFNULL ISNULL NVL DATE_TRUNC STRING_AGG ARRAY_AGG GROUP_CONCAT ABS FLOOR CEIL " +
12
+ "CEILING FIRST_VALUE LAST_VALUE NTILE GREATEST LEAST " +
13
+ "INT INTEGER BIGINT SMALLINT TINYINT VARCHAR NVARCHAR CHAR TEXT BOOLEAN BOOL DECIMAL NUMERIC FLOAT DOUBLE REAL SERIAL BIGSERIAL TIMESTAMP " +
14
+ "TIMESTAMPTZ JSONB BLOB PRECISION VARYING UNSIGNED"
15
+ ).split(" ")
16
+ );
17
+
18
+ const FUNCLIKE = new Set(
19
+ (
20
+ "COUNT SUM AVG MIN MAX COALESCE NULLIF CAST CONVERT UPPER LOWER LENGTH SUBSTRING SUBSTR TRIM ROUND NOW EXTRACT ROW_NUMBER RANK DENSE_RANK LAG " +
21
+ "LEAD CONCAT IFNULL ISNULL NVL DATE_TRUNC STRING_AGG ARRAY_AGG GROUP_CONCAT ABS FLOOR CEIL CEILING FIRST_VALUE LAST_VALUE NTILE GREATEST LEAST " +
22
+ "INT INTEGER BIGINT SMALLINT TINYINT VARCHAR NVARCHAR CHAR DECIMAL NUMERIC FLOAT DOUBLE TIMESTAMP REPLACE LEFT RIGHT IF VALUES CURRENT_TIMESTAMP"
23
+ ).split(" ")
24
+ );
25
+
26
+ const PHRASES = [
27
+ ["ON DUPLICATE KEY UPDATE", "inline"],
28
+ ["CREATE TABLE IF NOT EXISTS", "create"],
29
+ ["CREATE TEMPORARY TABLE", "create"],
30
+ ["CREATE TEMP TABLE", "create"],
31
+ ["CREATE OR REPLACE VIEW", "inline"],
32
+ ["IS NOT DISTINCT FROM", "plain"],
33
+ ["WITHOUT TIME ZONE", "plain"],
34
+ ["WITH TIME ZONE", "plain"],
35
+ ["CREATE UNIQUE INDEX", "inline"],
36
+ ["LEFT OUTER JOIN", "join"],
37
+ ["RIGHT OUTER JOIN", "join"],
38
+ ["FULL OUTER JOIN", "join"],
39
+ ["IS DISTINCT FROM", "plain"],
40
+ ["DROP TABLE IF EXISTS", "inline"],
41
+ ["DO UPDATE SET", "clause"],
42
+ ["SELECT DISTINCT", "clause"],
43
+ ["WITH RECURSIVE", "with"],
44
+ ["INSERT INTO", "inline"],
45
+ ["INSERT IGNORE INTO", "inline"],
46
+ ["REPLACE INTO", "inline"],
47
+ ["MERGE INTO", "inline"],
48
+ ["DELETE FROM", "inline"],
49
+ ["GROUP BY", "clause"],
50
+ ["ORDER BY", "clause"],
51
+ ["UNION ALL", "setop"],
52
+ ["UNION DISTINCT", "setop"],
53
+ ["EXCEPT ALL", "setop"],
54
+ ["INTERSECT ALL", "setop"],
55
+ ["INNER JOIN", "join"],
56
+ ["LEFT JOIN", "join"],
57
+ ["RIGHT JOIN", "join"],
58
+ ["FULL JOIN", "join"],
59
+ ["CROSS JOIN", "join"],
60
+ ["NATURAL JOIN", "join"],
61
+ ["CROSS APPLY", "join"],
62
+ ["OUTER APPLY", "join"],
63
+ ["CREATE TABLE", "create"],
64
+ ["CREATE VIEW", "inline"],
65
+ ["CREATE INDEX", "inline"],
66
+ ["ALTER TABLE", "inline"],
67
+ ["DROP TABLE", "inline"],
68
+ ["TRUNCATE TABLE", "inline"],
69
+ ["ON CONFLICT", "inline"],
70
+ ["DO NOTHING", "plain"],
71
+ ["SELECT", "clause"],
72
+ ["FROM", "clause"],
73
+ ["WHERE", "clause"],
74
+ ["HAVING", "clause"],
75
+ ["VALUES", "values"],
76
+ ["SET", "set"],
77
+ ["RETURNING", "clause"],
78
+ ["WINDOW", "clause"],
79
+ ["QUALIFY", "clause"],
80
+ ["LIMIT", "inline"],
81
+ ["OFFSET", "inline"],
82
+ ["FETCH", "inline"],
83
+ ["UNION", "setop"],
84
+ ["INTERSECT", "setop"],
85
+ ["EXCEPT", "setop"],
86
+ ["MINUS", "setop"],
87
+ ["UPDATE", "stmt"],
88
+ ["DELETE", "stmt"],
89
+ ["INSERT", "stmt"],
90
+ ["WITH", "with"],
91
+ ["JOIN", "join"],
92
+ ["CASE", "case"],
93
+ ["WHEN", "when"],
94
+ ["ELSE", "when"],
95
+ ["END", "end"],
96
+ ["AND", "and"],
97
+ ["OR", "and"],
98
+ ["BETWEEN", "between"],
99
+ ]
100
+ .map(([p, role]) => ({ words: p.split(" "), text: p, role }))
101
+ .sort((a, b) => b.words.length - a.words.length);
102
+
103
+ const INDENTED = new Set(["SELECT", "SELECT DISTINCT", "FROM", "WHERE", "GROUP BY", "HAVING", "ORDER BY", "VALUES", "SET", "DO UPDATE SET", "RETURNING", "WINDOW", "QUALIFY"]);
104
+ const TABLE_PARENS = new Set(["INSERT INTO", "INSERT IGNORE INTO", "INSERT", "REPLACE INTO", "MERGE INTO", "CREATE TABLE", "CREATE VIEW", "CREATE OR REPLACE VIEW", "WITH", "WITH RECURSIVE", "CREATE INDEX", "CREATE UNIQUE INDEX"]);
105
+ const INSERTS = new Set(["INSERT INTO", "INSERT IGNORE INTO", "INSERT", "REPLACE INTO"]);
106
+
107
+ const RULES = [
108
+ ["ws", /\s+/y],
109
+ ["comment", /--[^\n]*/y],
110
+ ["comment", /\/\*[\s\S]*?(?:\*\/|$)/y],
111
+ ["string", /[Ee]'(?:[^'\\]|\\[\s\S]|'')*'?/y],
112
+ ["string", /(?:[NnBbXx]|[Uu]&)?'(?:[^']|'')*'?/y],
113
+ ["string", /\$([A-Za-z_]\w*)?\$[\s\S]*?\$\1\$/y],
114
+ ["qident", /"(?:[^"]|"")*"?/y],
115
+ ["qident", /`(?:[^`]|``)*`?/y],
116
+ ["qident", /\[[A-Za-z_ #$@][^\]\n]*\]/y],
117
+ ["number", /(?:\d+(?:\.\d*)?|\.\d+)(?:[eE][+-]?\d+)?/y],
118
+ ["op", /::/y],
119
+ ["param", /\$\d+|\?\d*|:[A-Za-z_]\w*|@@?[A-Za-z_][\w$]*/y],
120
+ ["word", /[A-Za-z_\u00C0-\uFFFF][\w$\u00C0-\uFFFF]*/y],
121
+ ["open", /\(/y],
122
+ ["close", /\)/y],
123
+ ["comma", /,/y],
124
+ ["semi", /;/y],
125
+ ["dot", /\./y],
126
+ ["op", /<=>|<>|!=|<=|>=|\|\||->>|->|=>|:=|#>>|#>|@>|<@|[-+*/%=<>!~&|^#@:]/y],
127
+ ["op", /[\s\S]/y],
128
+ ];
129
+
130
+ export function tokenizeSql(sql) {
131
+ const src = String(sql);
132
+ const out = [];
133
+ let i = 0;
134
+ while (i < src.length) {
135
+ for (const [t, re] of RULES) {
136
+ re.lastIndex = i;
137
+ const m = re.exec(src);
138
+ if (m && m[0].length) {
139
+ out.push({ t, v: m[0] });
140
+ i += m[0].length;
141
+ break;
142
+ }
143
+ }
144
+ }
145
+ return out;
146
+ }
147
+
148
+ function caser(mode) {
149
+ return (w) => (mode === "lower" ? w.toLowerCase() : mode === "preserve" ? w : w.toUpperCase());
150
+ }
151
+
152
+ export function formatSql(sql, opts = {}) {
153
+ const kc = caser(opts.keywordCase || "upper");
154
+ const IND = opts.indent === undefined ? " " : opts.indent;
155
+ const perLine = opts.columnsPerLine !== false;
156
+ const S = [];
157
+ let sawNl = false;
158
+ for (const t of tokenizeSql(sql)) {
159
+ if (t.t === "ws") {
160
+ if (t.v.includes("\n")) sawNl = true;
161
+ continue;
162
+ }
163
+ t.nl = sawNl;
164
+ sawNl = false;
165
+ S.push(t);
166
+ }
167
+ const lines = [];
168
+ let cur = "";
169
+ let lineLevel = 0;
170
+ let pending = null;
171
+ let blank = false;
172
+ let prev = null;
173
+ const root = () => ({ kind: "root", base: 0, clause: null, count: 0 });
174
+ let frames = [root()];
175
+ const block = () => {
176
+ for (let i = frames.length - 1; i >= 0; i--) if (frames[i].kind !== "case") return frames[i];
177
+ return frames[0];
178
+ };
179
+ const top = () => frames[frames.length - 1];
180
+ const startLine = (level) => {
181
+ if (cur.trim()) {
182
+ lines.push(cur.replace(/\s+$/, ""));
183
+ if (blank) lines.push("");
184
+ }
185
+ blank = false;
186
+ cur = IND.repeat(level);
187
+ lineLevel = level;
188
+ };
189
+ const put = (text, space) => {
190
+ if (pending !== null) {
191
+ startLine(pending);
192
+ pending = null;
193
+ } else if (cur.trim() && space) cur += " ";
194
+ cur += text;
195
+ };
196
+ const tableParen = () => TABLE_PARENS.has(block().clause) && block().kind !== "inline";
197
+ const spaceBefore = (tok) => {
198
+ if (!prev) return false;
199
+ if (tok.t === "comma" || tok.t === "semi" || tok.t === "close" || tok.t === "dot") return false;
200
+ if (prev.t === "open" || prev.t === "dot" || prev.unary) return false;
201
+ if ((tok.t === "op" && tok.v === "::") || (prev.t === "op" && prev.v === "::")) return false;
202
+ if (tok.t === "open") {
203
+ if (prev.t === "word" && !prev.kw) return tableParen();
204
+ if (prev.t === "word" && FUNCLIKE.has(prev.up)) return false;
205
+ if (prev.t === "qident") return tableParen();
206
+ }
207
+ return true;
208
+ };
209
+ const matchPhrase = (k) => {
210
+ const words = [];
211
+ for (let j = k; j < S.length && words.length < 5 && S[j].t === "word"; j++) words.push(S[j].v.toUpperCase());
212
+ for (const p of PHRASES) {
213
+ if (p.words.length <= words.length && p.words.every((w, n) => w === words[n])) return p;
214
+ }
215
+ return null;
216
+ };
217
+
218
+ for (let k = 0; k < S.length; k++) {
219
+ const tok = S[k];
220
+ const B = block();
221
+ const T = top();
222
+ const flat = B.kind === "inline" || B.kind === "defs";
223
+ if (tok.t === "comment") {
224
+ const text = tok.v.startsWith("/*") ? tok.v : tok.v.replace(/\s+$/, "");
225
+ const lineComment = !tok.v.startsWith("/*");
226
+ if (!tok.nl && cur.trim()) cur += " " + text;
227
+ else {
228
+ if (pending === null && cur.trim()) pending = lineLevel;
229
+ put(text, true);
230
+ }
231
+ if (lineComment && pending === null) pending = lineLevel;
232
+ continue;
233
+ }
234
+ if (tok.t === "word") {
235
+ const up = tok.v.toUpperCase();
236
+ const ident = (prev && prev.t === "dot") || (S[k + 1] && S[k + 1].t === "dot");
237
+ let p = ident ? null : matchPhrase(k);
238
+ let role = p ? p.role : "plain";
239
+ if (flat && role !== "case" && role !== "when" && role !== "end" && role !== "plain") role = "word";
240
+ if (role === "set" && !(B.clause === "UPDATE" || B.clause === "ON DUPLICATE KEY UPDATE" || B.clause === "MERGE INTO")) role = "word";
241
+ if (role === "values" && !(INSERTS.has(B.clause) || B.count === 0)) role = "word";
242
+ if (role === "with" && B.count !== 0) role = "word";
243
+ if (role === "stmt" && prev && prev.t === "word" && ["FOR", "ON", "KEY", "DO", "THEN"].includes(prev.up)) role = "word";
244
+ if ((role === "and" || role === "between") && T.kind === "case") role = role === "between" ? "between" : "word";
245
+ const text = p && role !== "word" ? S.slice(k, k + p.words.length).map((x) => kc(x.v)).join(" ") :KEYWORDS.has(up) && !ident ? kc(tok.v) : tok.v;
246
+ if (p && role !== "word") k += p.words.length - 1;
247
+ switch (role) {
248
+ case "clause":
249
+ pending = B.base;
250
+ put(text, true);
251
+ B.clause = p.text === "DO UPDATE SET" ? "SET" : p.text;
252
+ pending = B.base + 1;
253
+ break;
254
+ case "set":
255
+ case "values":
256
+ pending = B.base;
257
+ put(text, true);
258
+ B.clause = p.text;
259
+ pending = B.base + 1;
260
+ break;
261
+ case "inline":
262
+ case "create":
263
+ case "stmt":
264
+ case "with":
265
+ pending = B.base;
266
+ put(text, true);
267
+ B.clause = role === "create" ? "CREATE TABLE" : role === "with" ? "WITH" : p.text;
268
+ B.defsUsed = false;
269
+ break;
270
+ case "setop":
271
+ pending = B.base;
272
+ put(text, true);
273
+ B.clause = p.text;
274
+ pending = B.base;
275
+ break;
276
+ case "join":
277
+ pending = B.base + 1;
278
+ put(text, true);
279
+ B.clause = "FROM";
280
+ break;
281
+ case "and":
282
+ if (B.between) {
283
+ B.between = false;
284
+ put(text, true);
285
+ } else if (B.clause === "WHERE" || B.clause === "HAVING" || B.clause === "QUALIFY") {
286
+ pending = B.base + 1;
287
+ put(text, true);
288
+ } else if (B.clause === "FROM") {
289
+ pending = B.base + 2;
290
+ put(text, true);
291
+ } else put(text, true);
292
+ break;
293
+ case "between":
294
+ B.between = true;
295
+ put(text, true);
296
+ break;
297
+ case "case":
298
+ put(text, spaceBefore(tok));
299
+ frames.push(flat ? { kind: "case", flat: true } : { kind: "case", base: lineLevel + 1, endLevel: lineLevel });
300
+ break;
301
+ case "when":
302
+ if (T.kind === "case" && !T.flat) pending = T.base;
303
+ put(text, true);
304
+ break;
305
+ case "end":
306
+ if (T.kind === "case") {
307
+ if (!T.flat) pending = T.endLevel;
308
+ frames.pop();
309
+ }
310
+ put(text, true);
311
+ break;
312
+ default:
313
+ put(text, spaceBefore(tok));
314
+ }
315
+ B.count++;
316
+ prev = { t: "word", v: tok.v, up, kw: KEYWORDS.has(up) || !!(p && role !== "word") };
317
+ continue;
318
+ }
319
+ if (tok.t === "open") {
320
+ let n = k + 1;
321
+ while (n < S.length && S[n].t === "comment") n++;
322
+ const nx = S[n];
323
+ const sub = nx && nx.t === "word" && /^(SELECT|WITH)$/i.test(nx.v);
324
+ const defs = !sub && !flat && B.clause === "CREATE TABLE" && !B.defsUsed;
325
+ if (defs) B.defsUsed = true;
326
+ put("(", spaceBefore(tok));
327
+ B.count++;
328
+ if (sub || defs) {
329
+ frames.push({ kind: sub ? "sub" : "defs", base: lineLevel + 1, closeLevel: lineLevel, clause: null, count: 0 });
330
+ pending = lineLevel + 1;
331
+ } else frames.push({ kind: "inline", count: 0 });
332
+ prev = { t: "open", v: "(" };
333
+ continue;
334
+ }
335
+ if (tok.t === "close") {
336
+ let f = null;
337
+ while (frames.length > 1) {
338
+ f = frames.pop();
339
+ if (f.kind !== "case") break;
340
+ f = null;
341
+ }
342
+ if (f && (f.kind === "sub" || f.kind === "defs")) pending = f.closeLevel;
343
+ else if (pending !== null && !cur.trim()) pending = null;
344
+ put(")", false);
345
+ prev = { t: "close", v: ")" };
346
+ continue;
347
+ }
348
+ if (tok.t === "comma") {
349
+ put(",", false);
350
+ if (T.kind !== "case") {
351
+ if (B.kind === "defs") pending = B.base;
352
+ else if (B.kind !== "inline") {
353
+ if (B.clause === "WITH") pending = B.base;
354
+ else if (B.clause === "VALUES") pending = B.base + 1;
355
+ else if (perLine && INDENTED.has(B.clause)) pending = B.base + 1;
356
+ }
357
+ }
358
+ prev = { t: "comma", v: "," };
359
+ continue;
360
+ }
361
+ if (tok.t === "semi") {
362
+ pending = null;
363
+ put(";", false);
364
+ frames = [root()];
365
+ pending = 0;
366
+ blank = true;
367
+ prev = null;
368
+ continue;
369
+ }
370
+ if (tok.t === "dot") {
371
+ put(".", false);
372
+ prev = { t: "dot", v: "." };
373
+ continue;
374
+ }
375
+ const unary = tok.t === "op" && (tok.v === "-" || tok.v === "+") && (!prev || prev.t === "op" || prev.t === "open" || prev.t === "comma" || (prev.t === "word" && prev.kw && !FUNCLIKE.has(prev.up)));
376
+ put(tok.v, spaceBefore(tok));
377
+ B.count++;
378
+ prev = { t: tok.t, v: tok.v, unary };
379
+ }
380
+ if (cur.trim()) lines.push(cur.replace(/\s+$/, ""));
381
+ while (lines.length && !lines[lines.length - 1].trim()) lines.pop();
382
+ return lines.join("\n");
383
+ }
384
+
385
+ export function minifySql(sql, opts = {}) {
386
+ const kc = caser(opts.keywordCase || "upper");
387
+ const toks = tokenizeSql(sql).filter((t) => t.t !== "ws" && t.t !== "comment");
388
+ const wordish = (t) => t.t === "word" || t.t === "number" || t.t === "param" || t.t === "qident";
389
+ let out = "";
390
+ let prev = null;
391
+ toks.forEach((t, k) => {
392
+ const ident = (prev && prev.t === "dot") || (toks[k + 1] && toks[k + 1].t === "dot");
393
+ const text = t.t === "word" && KEYWORDS.has(t.v.toUpperCase()) && !ident ? kc(t.v) : t.v;
394
+ if (prev) {
395
+ let gap = false;
396
+ if (wordish(prev) && wordish(t)) gap = true;
397
+ else if ((prev.t === "word" && t.t === "string") || (prev.t === "string" && t.t === "word")) gap = true;
398
+ else if (prev.t === "op" && t.t === "op") {
399
+ const a = prev.v[prev.v.length - 1];
400
+ const b = t.v[0];
401
+ gap = (a === "-" && b === "-") || (a === "/" && b === "*") || (a === "*" && b === "/") || (a === "<" || a === ">" || a === "=" || a === "!" || a === "|" || a === ":");
402
+ } else if ((prev.t === "number" && t.t === "dot") || (prev.t === "close" && (t.t === "word" || t.t === "number"))) gap = true;
403
+ if (gap) out += " ";
404
+ }
405
+ out += text;
406
+ prev = t;
407
+ });
408
+ return out;
409
+ }
410
+
411
+ export function sqlStats(sql) {
412
+ let statements = 0;
413
+ let open = false;
414
+ let tokens = 0;
415
+ for (const t of tokenizeSql(sql)) {
416
+ if (t.t === "ws") continue;
417
+ tokens++;
418
+ if (t.t === "semi") {
419
+ if (open) statements++;
420
+ open = false;
421
+ } else if (t.t !== "comment") open = true;
422
+ }
423
+ if (open) statements++;
424
+ return { statements, tokens };
425
+ }