@syncular/typegen 0.2.1 → 0.3.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.
Files changed (51) hide show
  1. package/README.md +25 -10
  2. package/dist/cli.js +155 -4
  3. package/dist/emit-dart.js +3 -2
  4. package/dist/emit-kotlin.js +3 -2
  5. package/dist/emit-queries-dart.js +87 -15
  6. package/dist/emit-queries-kotlin.js +89 -15
  7. package/dist/emit-queries-swift.js +92 -14
  8. package/dist/emit-queries.js +131 -22
  9. package/dist/emit-swift.js +3 -2
  10. package/dist/emit.d.ts +2 -1
  11. package/dist/emit.js +13 -27
  12. package/dist/fmt.d.ts +3 -0
  13. package/dist/fmt.js +356 -0
  14. package/dist/generate.d.ts +16 -6
  15. package/dist/generate.js +34 -9
  16. package/dist/index.d.ts +6 -0
  17. package/dist/index.js +6 -0
  18. package/dist/lower.d.ts +54 -0
  19. package/dist/lower.js +283 -0
  20. package/dist/lsp.d.ts +20 -0
  21. package/dist/lsp.js +376 -0
  22. package/dist/manifest.d.ts +11 -0
  23. package/dist/manifest.js +20 -0
  24. package/dist/naming.d.ts +22 -0
  25. package/dist/naming.js +240 -0
  26. package/dist/query-ir.d.ts +14 -0
  27. package/dist/query-ir.js +69 -0
  28. package/dist/query.d.ts +102 -6
  29. package/dist/query.js +126 -34
  30. package/dist/syql.d.ts +83 -0
  31. package/dist/syql.js +945 -0
  32. package/package.json +2 -2
  33. package/src/cli.ts +155 -4
  34. package/src/emit-dart.ts +3 -2
  35. package/src/emit-kotlin.ts +3 -2
  36. package/src/emit-queries-dart.ts +109 -16
  37. package/src/emit-queries-kotlin.ts +111 -18
  38. package/src/emit-queries-swift.ts +106 -17
  39. package/src/emit-queries.ts +179 -28
  40. package/src/emit-swift.ts +3 -2
  41. package/src/emit.ts +18 -28
  42. package/src/fmt.ts +351 -0
  43. package/src/generate.ts +54 -11
  44. package/src/index.ts +6 -0
  45. package/src/lower.ts +331 -0
  46. package/src/lsp.ts +445 -0
  47. package/src/manifest.ts +38 -0
  48. package/src/naming.ts +271 -0
  49. package/src/query-ir.ts +82 -0
  50. package/src/query.ts +241 -34
  51. package/src/syql.ts +1171 -0
package/src/fmt.ts ADDED
@@ -0,0 +1,351 @@
1
+ /**
2
+ * `syncular fmt` — the canonical `.syql` formatter (DESIGN-queries.md §10,
3
+ * §12). One style, no options:
4
+ *
5
+ * - SQL keywords lowercase; identifier casing preserved (SQL-truth).
6
+ * - one space after commas; collapsed whitespace elsewhere.
7
+ * - one clause per line in the body; WHERE conjuncts one per line,
8
+ * `and`-prefixed and indented under the `where`.
9
+ * - declarations separated by one blank line; knobs on their own lines.
10
+ * - comments are preserved: a declaration's leading comment block stays
11
+ * above it; comments inside bodies stay on their own lines.
12
+ *
13
+ * The formatter is built on the same parser as the generator (`.syql` that
14
+ * does not parse does not format), and formatting is semantics-preserving
15
+ * by construction — `fmt` output re-parses to the same declarations.
16
+ */
17
+ import { TypegenError } from './errors';
18
+ import { parseSyqlFile, type SyqlQueryDecl } from './syql';
19
+
20
+ /** SQL keywords the canon lowercases (a pragmatic core set — identifiers
21
+ * that collide with these would need quoting anyway). */
22
+ const SQL_KEYWORDS = new Set(
23
+ (
24
+ 'select from where group by having order limit offset window and or not ' +
25
+ 'in is null like glob between exists case when then else end as on using ' +
26
+ 'join left right full outer inner cross natural union all except ' +
27
+ 'intersect distinct values with recursive asc desc collate cast'
28
+ ).split(' '),
29
+ );
30
+
31
+ interface Token {
32
+ readonly kind: 'word' | 'string' | 'comment' | 'punct' | 'param' | 'fragment';
33
+ readonly text: string;
34
+ }
35
+
36
+ /** Tokenize a body: strings/comments verbatim, words, `:params`,
37
+ * `@fragment` refs, single punctuation chars. */
38
+ function tokenize(body: string): Token[] {
39
+ const tokens: Token[] = [];
40
+ let i = 0;
41
+ while (i < body.length) {
42
+ const ch = body[i] as string;
43
+ if (/\s/.test(ch)) {
44
+ i += 1;
45
+ continue;
46
+ }
47
+ if (body.startsWith('--', i)) {
48
+ const nl = body.indexOf('\n', i);
49
+ const end = nl === -1 ? body.length : nl;
50
+ tokens.push({ kind: 'comment', text: body.slice(i, end).trimEnd() });
51
+ i = end;
52
+ continue;
53
+ }
54
+ if (body.startsWith('/*', i)) {
55
+ const close = body.indexOf('*/', i + 2);
56
+ const end = close === -1 ? body.length : close + 2;
57
+ tokens.push({ kind: 'comment', text: body.slice(i, end) });
58
+ i = end;
59
+ continue;
60
+ }
61
+ if (ch === "'") {
62
+ let j = i + 1;
63
+ for (;;) {
64
+ if (j >= body.length) break;
65
+ if (body[j] === "'") {
66
+ if (body[j + 1] === "'") j += 2;
67
+ else {
68
+ j += 1;
69
+ break;
70
+ }
71
+ } else j += 1;
72
+ }
73
+ tokens.push({ kind: 'string', text: body.slice(i, j) });
74
+ i = j;
75
+ continue;
76
+ }
77
+ if (ch === ':' && /[A-Za-z_]/.test(body[i + 1] ?? '')) {
78
+ let j = i + 1;
79
+ while (j < body.length && /[A-Za-z0-9_]/.test(body[j] as string)) j += 1;
80
+ tokens.push({ kind: 'param', text: body.slice(i, j) });
81
+ i = j;
82
+ continue;
83
+ }
84
+ if (ch === '@' && /[A-Za-z_]/.test(body[i + 1] ?? '')) {
85
+ let j = i + 1;
86
+ while (j < body.length && /[A-Za-z0-9_]/.test(body[j] as string)) j += 1;
87
+ tokens.push({ kind: 'fragment', text: body.slice(i, j) });
88
+ i = j;
89
+ continue;
90
+ }
91
+ if (/[A-Za-z_]/.test(ch)) {
92
+ let j = i + 1;
93
+ while (j < body.length && /[A-Za-z0-9_]/.test(body[j] as string)) j += 1;
94
+ tokens.push({ kind: 'word', text: body.slice(i, j) });
95
+ i = j;
96
+ continue;
97
+ }
98
+ // Multi-char operators stay one token (`||`, `>=`, `<=`, `<>`, `!=`, `==`).
99
+ const two = body.slice(i, i + 2);
100
+ if (['||', '>=', '<=', '<>', '!=', '=='].includes(two)) {
101
+ tokens.push({ kind: 'punct', text: two });
102
+ i += 2;
103
+ continue;
104
+ }
105
+ tokens.push({ kind: 'punct', text: ch });
106
+ i += 1;
107
+ }
108
+ return tokens;
109
+ }
110
+
111
+ /** Render tokens back to one line with canonical spacing + keyword case. */
112
+ function render(tokens: readonly Token[]): string {
113
+ let out = '';
114
+ let prev: Token | undefined;
115
+ for (const token of tokens) {
116
+ let text = token.text;
117
+ if (token.kind === 'word' && SQL_KEYWORDS.has(text.toLowerCase())) {
118
+ text = text.toLowerCase();
119
+ }
120
+ const glueLeft =
121
+ token.kind === 'punct' &&
122
+ (text === ',' || text === ')' || text === ';' || text === '.');
123
+ const glueRight =
124
+ prev !== undefined &&
125
+ ((prev.kind === 'punct' && (prev.text === '(' || prev.text === '.')) ||
126
+ prev.kind === 'fragment');
127
+ if (out.length > 0 && !glueLeft && !glueRight) out += ' ';
128
+ out += text;
129
+ prev = token;
130
+ }
131
+ return out;
132
+ }
133
+
134
+ /** Clause keywords that start a new line at paren depth 0. */
135
+ const CLAUSE_STARTERS = new Set([
136
+ 'select',
137
+ 'from',
138
+ 'where',
139
+ 'group',
140
+ 'having',
141
+ 'order',
142
+ 'limit',
143
+ 'window',
144
+ 'union',
145
+ 'except',
146
+ 'intersect',
147
+ ]);
148
+
149
+ /** Format a body: one clause per line; WHERE conjuncts one per line. */
150
+ function formatBody(body: string): string[] {
151
+ const tokens = tokenize(body);
152
+ const lines: string[] = [];
153
+ let current: Token[] = [];
154
+ let depth = 0;
155
+ let braceDepth = 0;
156
+ let inWhere = false;
157
+ let pendingBetween = 0;
158
+ const flush = (): void => {
159
+ if (current.length > 0) lines.push(render(current));
160
+ current = [];
161
+ };
162
+ for (const token of tokens) {
163
+ if (token.kind === 'comment') {
164
+ flush();
165
+ lines.push(token.text);
166
+ continue;
167
+ }
168
+ if (token.kind === 'punct') {
169
+ if (token.text === '(') depth += 1;
170
+ else if (token.text === ')') depth -= 1;
171
+ else if (token.text === '{') braceDepth += 1;
172
+ else if (token.text === '}') braceDepth -= 1;
173
+ }
174
+ if (token.kind === 'word' && depth === 0 && braceDepth === 0) {
175
+ const word = token.text.toLowerCase();
176
+ if (CLAUSE_STARTERS.has(word)) {
177
+ flush();
178
+ inWhere = word === 'where';
179
+ pendingBetween = 0;
180
+ } else if (inWhere) {
181
+ if (word === 'between') pendingBetween += 1;
182
+ else if (word === 'and') {
183
+ if (pendingBetween > 0) pendingBetween -= 1;
184
+ else flush();
185
+ }
186
+ }
187
+ }
188
+ current.push(token);
189
+ }
190
+ flush();
191
+ // Indent: clauses flush-left; WHERE continuation (`and …`) indented 2.
192
+ return lines.map((line) =>
193
+ /^(and|or)\b/.test(line) || line.startsWith('--') ? ` ${line}` : line,
194
+ );
195
+ }
196
+
197
+ function formatSignature(decl: {
198
+ readonly params: SyqlQueryDecl['params'];
199
+ }): string {
200
+ const parts: string[] = [];
201
+ const seenGroups = new Set<string>();
202
+ for (const param of decl.params) {
203
+ if (param.group !== undefined) {
204
+ if (seenGroups.has(param.group)) continue;
205
+ seenGroups.add(param.group);
206
+ const members = decl.params.filter((p) => p.group === param.group);
207
+ parts.push(`${members.map((p) => p.name).join('+')}?`);
208
+ continue;
209
+ }
210
+ if (param.flag) parts.push(`${param.name}?: flag`);
211
+ else parts.push(`${param.name}${param.optional ? '?' : ''}`);
212
+ }
213
+ return parts.join(', ');
214
+ }
215
+
216
+ /** The leading comment block (verbatim lines) before each declaration. */
217
+ function leadingComments(source: string): Map<number, string[]> {
218
+ // Map from declaration index (0-based, in order of appearance) to its
219
+ // preceding comment lines.
220
+ const out = new Map<number, string[]>();
221
+ let pending: string[] = [];
222
+ let declIndex = 0;
223
+ let i = 0;
224
+ while (i < source.length) {
225
+ const ch = source[i] as string;
226
+ if (/\s/.test(ch)) {
227
+ i += 1;
228
+ continue;
229
+ }
230
+ if (source.startsWith('--', i)) {
231
+ const nl = source.indexOf('\n', i);
232
+ const end = nl === -1 ? source.length : nl;
233
+ pending.push(source.slice(i, end).trimEnd());
234
+ i = end;
235
+ continue;
236
+ }
237
+ if (source.startsWith('/*', i)) {
238
+ const close = source.indexOf('*/', i + 2);
239
+ const end = close === -1 ? source.length : close + 2;
240
+ pending.push(source.slice(i, end));
241
+ i = end;
242
+ continue;
243
+ }
244
+ // A declaration keyword: attach pending comments, then skip through its
245
+ // brace block.
246
+ if (pending.length > 0) {
247
+ out.set(declIndex, pending);
248
+ pending = [];
249
+ }
250
+ declIndex += 1;
251
+ const brace = source.indexOf('{', i);
252
+ if (brace === -1) break;
253
+ let depth = 0;
254
+ let j = brace;
255
+ while (j < source.length) {
256
+ const c = source[j] as string;
257
+ if (c === "'") {
258
+ j += 1;
259
+ while (j < source.length && source[j] !== "'") j += 1;
260
+ } else if (source.startsWith('--', j)) {
261
+ const nl = source.indexOf('\n', j);
262
+ j = nl === -1 ? source.length : nl;
263
+ } else if (c === '{') depth += 1;
264
+ else if (c === '}') {
265
+ depth -= 1;
266
+ if (depth === 0) {
267
+ j += 1;
268
+ break;
269
+ }
270
+ }
271
+ j += 1;
272
+ }
273
+ i = j;
274
+ }
275
+ return out;
276
+ }
277
+
278
+ /** Format one `.syql` source file into its canonical form. Throws
279
+ * {@link TypegenError} when the source does not parse. */
280
+ export function formatSyql(file: string, source: string): string {
281
+ const parsed = parseSyqlFile(file, source);
282
+ const comments = leadingComments(source);
283
+ const decls: { order: number; text: string }[] = [];
284
+
285
+ // Re-derive declaration order (fragments/queries interleave in-source);
286
+ // parseSyqlFile keeps per-kind order, so re-scan the source for kind
287
+ // keywords to interleave faithfully.
288
+ const orderRe = /\b(query|fragment)\s+([A-Za-z][A-Za-z0-9]*)/g;
289
+ const blanked = source
290
+ .replace(/--[^\n]*/g, (m) => ' '.repeat(m.length))
291
+ .replace(/\/\*[\s\S]*?\*\//g, (m) => ' '.repeat(m.length))
292
+ .replace(/'(?:[^']|'')*'/g, (m) => ' '.repeat(m.length));
293
+ const sequence: { kind: string; name: string }[] = [];
294
+ let braceDepth = 0;
295
+ let lastIndex = 0;
296
+ for (let i = 0; i < blanked.length; i++) {
297
+ const c = blanked[i];
298
+ if (c === '{') braceDepth += 1;
299
+ else if (c === '}') braceDepth -= 1;
300
+ else if (braceDepth === 0) {
301
+ orderRe.lastIndex = i;
302
+ const m = orderRe.exec(blanked);
303
+ if (m !== null && m.index === i) {
304
+ sequence.push({ kind: m[1] as string, name: m[2] as string });
305
+ i = orderRe.lastIndex - 1;
306
+ lastIndex = orderRe.lastIndex;
307
+ }
308
+ }
309
+ }
310
+ void lastIndex;
311
+
312
+ sequence.forEach((entry, index) => {
313
+ const lines: string[] = [];
314
+ const leading = comments.get(index);
315
+ if (leading !== undefined) lines.push(...leading);
316
+ if (entry.kind === 'fragment') {
317
+ const decl = parsed.fragments.find((f) => f.name === entry.name);
318
+ if (decl === undefined) return;
319
+ lines.push(`fragment ${decl.name}(${formatSignature(decl)}) {`);
320
+ for (const line of formatBody(decl.body)) lines.push(` ${line}`);
321
+ lines.push('}');
322
+ } else {
323
+ const decl = parsed.queries.find((q) => q.name === entry.name);
324
+ if (decl === undefined) return;
325
+ const knobs: string[] = [];
326
+ if (decl.orderBy !== undefined) {
327
+ const dir = decl.orderBy.defaultDir === 'desc' ? ' desc' : '';
328
+ knobs.push(
329
+ ` orderBy ${decl.orderBy.allowed.join(' | ')} default ${decl.orderBy.defaultColumn}${dir}`,
330
+ );
331
+ }
332
+ if (decl.limit !== undefined) {
333
+ const parts: string[] = [];
334
+ if (decl.limit.max !== undefined) parts.push(`max ${decl.limit.max}`);
335
+ if (decl.limit.default !== undefined) {
336
+ parts.push(`default ${decl.limit.default}`);
337
+ }
338
+ knobs.push(` limit ${parts.join(' ')}`);
339
+ }
340
+ if (decl.variants === true) knobs.push(' variants');
341
+ lines.push(
342
+ `query ${decl.name}(${formatSignature(decl)})${knobs.length > 0 ? `\n${knobs.join('\n')}\n{` : ' {'}`,
343
+ );
344
+ for (const line of formatBody(decl.body)) lines.push(` ${line}`);
345
+ lines.push('}');
346
+ }
347
+ decls.push({ order: index, text: lines.join('\n') });
348
+ });
349
+
350
+ return `${decls.map((d) => d.text).join('\n\n')}\n`;
351
+ }
package/src/generate.ts CHANGED
@@ -40,13 +40,16 @@ import {
40
40
  type ManifestScopeSpec,
41
41
  parseManifest,
42
42
  } from './manifest';
43
+ import { buildNamingMap, type NamingTarget } from './naming';
43
44
  import {
44
45
  type AnalyzedQuery,
45
46
  analyzeQueryFile,
46
47
  type QueryDb,
48
+ type QueryNamingOptions,
47
49
  synthesizeDdl,
48
50
  } from './query';
49
51
  import { applyMigrationSql, type ParsedTable } from './sql';
52
+ import { analyzeSyqlFile } from './syql';
50
53
 
51
54
  export interface MigrationInput {
52
55
  /** Directory name, e.g. `0001_initial`. */
@@ -340,31 +343,36 @@ export function loadMigrations(migrationsDir: string): MigrationInput[] {
340
343
  });
341
344
  }
342
345
 
343
- /** One `.sql` named-query file: path (relative to the queries root, forward
344
- * slashes) + raw contents. A file may hold multiple statements. */
346
+ /** One named-query file (`.sql` the compatibility floor — or `.syql`, the
347
+ * DSL): path relative to the queries root (forward slashes) + raw contents.
348
+ * A file may hold multiple statements/declarations. */
345
349
  export interface QueryInput {
346
350
  /** Path relative to the queries root, forward-slashed (e.g.
347
- * `billing/invoices/list.sql`). Drives the path-derived default name. */
351
+ * `billing/invoices/list.sql`). Drives the path-derived default name for
352
+ * `.sql`; `.syql` queries carry their own names. */
348
353
  readonly file: string;
349
354
  readonly sql: string;
350
355
  }
351
356
 
352
- /** Recursively collect `*.sql` paths under `dir`, relative to `root`
353
- * (forward-slashed), sorted for deterministic emission. */
357
+ /** Recursively collect `*.sql` + `*.syql` paths under `dir`, relative to
358
+ * `root` (forward-slashed), sorted for deterministic emission. */
354
359
  function collectSqlFiles(root: string, dir: string): string[] {
355
360
  const out: string[] = [];
356
361
  for (const entry of readdirSync(dir, { withFileTypes: true })) {
357
362
  const abs = join(dir, entry.name);
358
363
  if (entry.isDirectory()) {
359
364
  out.push(...collectSqlFiles(root, abs));
360
- } else if (entry.isFile() && entry.name.endsWith('.sql')) {
365
+ } else if (
366
+ entry.isFile() &&
367
+ (entry.name.endsWith('.sql') || entry.name.endsWith('.syql'))
368
+ ) {
361
369
  out.push(relative(root, abs).split(sep).join('/'));
362
370
  }
363
371
  }
364
372
  return out;
365
373
  }
366
374
 
367
- /** Load `queries/**\/*.sql` recursively, ordered by relative path
375
+ /** Load `queries/**\/*.{sql,syql}` recursively, ordered by relative path
368
376
  * (deterministic emission). Folders are pure organization. */
369
377
  export function loadQueries(queriesDir: string): QueryInput[] {
370
378
  if (!existsSync(queriesDir)) return [];
@@ -380,7 +388,10 @@ export function loadQueries(queriesDir: string): QueryInput[] {
380
388
  * `analyze` prepares the statement (validating references) and reads its
381
389
  * column names + declared types; it executes once against the empty DB so
382
390
  * `declaredTypes` (decltype) is populated. */
383
- function makeQueryDb(ir: IrDocument): { db: QueryDb; close: () => void } {
391
+ export function makeQueryDb(ir: IrDocument): {
392
+ db: QueryDb;
393
+ close: () => void;
394
+ } {
384
395
  const sqlite = new Database(':memory:');
385
396
  sqlite.run(synthesizeDdl(ir));
386
397
  const db: QueryDb = {
@@ -408,13 +419,18 @@ function makeQueryDb(ir: IrDocument): { db: QueryDb; close: () => void } {
408
419
  export function analyzeQueries(
409
420
  ir: IrDocument,
410
421
  queries: readonly QueryInput[],
422
+ naming?: QueryNamingOptions,
411
423
  ): AnalyzedQuery[] {
412
424
  if (queries.length === 0) return [];
413
425
  const { db, close } = makeQueryDb(ir);
414
426
  try {
415
427
  // Each file may hold multiple statements; flatten in path order (stable).
428
+ // Frontends dispatch on extension; both produce the same AnalyzedQuery
429
+ // shape (§1 — nothing downstream knows the frontend).
416
430
  const analyzed = queries.flatMap((q) =>
417
- analyzeQueryFile(q.file, q.sql, ir, db),
431
+ q.file.endsWith('.syql')
432
+ ? analyzeSyqlFile(q.file, q.sql, ir, db, naming)
433
+ : analyzeQueryFile(q.file, q.sql, ir, db, naming),
418
434
  );
419
435
  // Names must be unique across the whole manifest — the filesystem no longer
420
436
  // guarantees uniqueness once `-- name:` overrides exist. Report BOTH source
@@ -476,9 +492,32 @@ export function generate(manifestDir: string): GenerateResult {
476
492
  const manifest = parseManifest(raw);
477
493
  const migrations = loadMigrations(resolve(manifestDir, manifest.migrations));
478
494
  const ir = buildIr(manifest, migrations);
495
+
496
+ // §5/§12 naming: the emitter targets this run generates (keyword hazards
497
+ // are only real on targets that exist), and the per-table collision check
498
+ // for the generated row types.
499
+ const targets: NamingTarget[] = ['ts'];
500
+ if (manifest.output.swift !== undefined) targets.push('swift');
501
+ if (manifest.output.kotlin !== undefined) targets.push('kotlin');
502
+ if (manifest.output.dart !== undefined) targets.push('dart');
503
+ const naming: QueryNamingOptions = {
504
+ naming: manifest.naming,
505
+ targets,
506
+ backend: manifest.queryBackend,
507
+ };
508
+ for (const table of ir.tables) {
509
+ buildNamingMap(
510
+ table.columns.map((c) => c.name),
511
+ manifest.naming,
512
+ MANIFEST_FILENAME,
513
+ `table ${table.name}`,
514
+ targets,
515
+ );
516
+ }
517
+
479
518
  const irJson = serializeIr(ir);
480
519
  const hash = irHash(irJson);
481
- const module = emitModule(ir, hash);
520
+ const module = emitModule(ir, hash, manifest.naming);
482
521
  const irPath = resolve(manifestDir, manifest.output.ir);
483
522
  const modulePath = resolve(manifestDir, manifest.output.module);
484
523
  const outputs: GeneratedOutput[] = [
@@ -497,7 +536,11 @@ export function generate(manifestDir: string): GenerateResult {
497
536
  kotlin?.queriesPath !== undefined ||
498
537
  dart?.queriesPath !== undefined;
499
538
  const analyzedQueries = wantsQueries
500
- ? analyzeQueries(ir, loadQueries(resolve(manifestDir, manifest.queries)))
539
+ ? analyzeQueries(
540
+ ir,
541
+ loadQueries(resolve(manifestDir, manifest.queries)),
542
+ naming,
543
+ )
501
544
  : [];
502
545
  if (wantsQueries && analyzedQueries.length === 0) {
503
546
  throw new TypegenError(
package/src/index.ts CHANGED
@@ -12,8 +12,14 @@ export * from './emit-queries-kotlin';
12
12
  export * from './emit-queries-swift';
13
13
  export * from './emit-swift';
14
14
  export * from './errors';
15
+ export * from './fmt';
15
16
  export * from './generate';
16
17
  export * from './ir';
18
+ export * from './lower';
19
+ export * from './lsp';
17
20
  export * from './manifest';
21
+ export * from './naming';
18
22
  export * from './query';
23
+ export * from './query-ir';
19
24
  export * from './sql';
25
+ export * from './syql';