@syncular/typegen 0.4.1 → 0.5.0

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,367 @@
1
+ /**
2
+ * Lossless lexer for the revision-1 SYQL frontend (`docs/SYQL.md` §2).
3
+ *
4
+ * The lexer owns the SQLite atomic-token boundary used by parsing, formatting,
5
+ * bind discovery, predicate expansion, and editor tooling. It never rewrites
6
+ * source text: concatenating every non-EOF token reproduces the input exactly.
7
+ */
8
+ import { TypegenError } from './errors.js';
9
+ /** A source-spanned, stable-code error shared by the lexer and parser. */
10
+ export class SyqlFrontendError extends TypegenError {
11
+ code;
12
+ span;
13
+ sourceFile;
14
+ constructor(code, span, message) {
15
+ super(`${span.file}:${span.start.line}:${span.start.column}`, `${code}: ${message}`);
16
+ this.name = 'SyqlFrontendError';
17
+ this.code = code;
18
+ this.span = span;
19
+ this.sourceFile = span.file;
20
+ }
21
+ }
22
+ export function isSyqlTrivia(token) {
23
+ return (token.kind === 'whitespace' ||
24
+ token.kind === 'line-comment' ||
25
+ token.kind === 'block-comment');
26
+ }
27
+ function isAsciiDigit(code) {
28
+ return code >= 0x30 && code <= 0x39;
29
+ }
30
+ function isAsciiHex(code) {
31
+ return (isAsciiDigit(code) ||
32
+ (code >= 0x41 && code <= 0x46) ||
33
+ (code >= 0x61 && code <= 0x66));
34
+ }
35
+ function isAsciiLetter(code) {
36
+ return (code >= 0x41 && code <= 0x5a) || (code >= 0x61 && code <= 0x7a);
37
+ }
38
+ function isIdentifierStart(code) {
39
+ // SQLite treats non-ASCII characters as alphabetic identifier characters.
40
+ return code === 0x5f || isAsciiLetter(code) || code >= 0x80;
41
+ }
42
+ function isIdentifierContinue(code) {
43
+ return isIdentifierStart(code) || isAsciiDigit(code);
44
+ }
45
+ function isAsciiWhitespace(code) {
46
+ return code === 0x20 || (code >= 0x09 && code <= 0x0d);
47
+ }
48
+ function isPunctuation(ch) {
49
+ return '(){}[],.;?'.includes(ch);
50
+ }
51
+ const THREE_CHAR_OPERATORS = new Set(['->>']);
52
+ const TWO_CHAR_OPERATORS = new Set([
53
+ '->',
54
+ '||',
55
+ '<<',
56
+ '>>',
57
+ '<=',
58
+ '>=',
59
+ '==',
60
+ '!=',
61
+ '<>',
62
+ ]);
63
+ function scanDigitRun(source, start, predicate) {
64
+ let i = start;
65
+ let consumed = false;
66
+ while (i < source.length) {
67
+ const code = source.charCodeAt(i);
68
+ if (predicate(code)) {
69
+ consumed = true;
70
+ i += 1;
71
+ continue;
72
+ }
73
+ // SQLite 3.46 permits one underscore between digits.
74
+ if (consumed &&
75
+ code === 0x5f &&
76
+ i + 1 < source.length &&
77
+ predicate(source.charCodeAt(i + 1))) {
78
+ i += 2;
79
+ consumed = true;
80
+ continue;
81
+ }
82
+ break;
83
+ }
84
+ return i;
85
+ }
86
+ class Lexer {
87
+ #source;
88
+ #file;
89
+ #tokens = [];
90
+ #offset = 0;
91
+ #line = 1;
92
+ #column = 1;
93
+ #braceDepth = 0;
94
+ #expectImportPath = false;
95
+ constructor(file, source) {
96
+ this.#file = file;
97
+ this.#source = source;
98
+ }
99
+ lex() {
100
+ while (this.#offset < this.#source.length)
101
+ this.#next();
102
+ const atEnd = this.#position();
103
+ this.#tokens.push({
104
+ kind: 'eof',
105
+ text: '',
106
+ span: { file: this.#file, start: atEnd, end: atEnd },
107
+ });
108
+ return this.#tokens;
109
+ }
110
+ #position() {
111
+ return {
112
+ offset: this.#offset,
113
+ line: this.#line,
114
+ column: this.#column,
115
+ };
116
+ }
117
+ #advanceTo(end) {
118
+ while (this.#offset < end) {
119
+ const code = this.#source.charCodeAt(this.#offset);
120
+ if (code === 0x0d) {
121
+ if (this.#offset + 1 < end &&
122
+ this.#source.charCodeAt(this.#offset + 1) === 0x0a) {
123
+ this.#offset += 2;
124
+ }
125
+ else {
126
+ this.#offset += 1;
127
+ }
128
+ this.#line += 1;
129
+ this.#column = 1;
130
+ }
131
+ else if (code === 0x0a) {
132
+ this.#offset += 1;
133
+ this.#line += 1;
134
+ this.#column = 1;
135
+ }
136
+ else {
137
+ const point = this.#source.codePointAt(this.#offset);
138
+ this.#offset += point > 0xffff ? 2 : 1;
139
+ this.#column += 1;
140
+ }
141
+ }
142
+ }
143
+ #emit(kind, end) {
144
+ const start = this.#position();
145
+ const text = this.#source.slice(this.#offset, end);
146
+ this.#advanceTo(end);
147
+ const token = {
148
+ kind,
149
+ text,
150
+ span: { file: this.#file, start, end: this.#position() },
151
+ };
152
+ this.#tokens.push(token);
153
+ this.#afterSignificant(token);
154
+ }
155
+ #afterSignificant(token) {
156
+ if (isSyqlTrivia(token))
157
+ return;
158
+ if (token.kind === 'punctuation') {
159
+ if (token.text === '{')
160
+ this.#braceDepth += 1;
161
+ else if (token.text === '}')
162
+ this.#braceDepth = Math.max(0, this.#braceDepth - 1);
163
+ }
164
+ if (token.kind === 'identifier' &&
165
+ token.text === 'from' &&
166
+ this.#braceDepth === 0) {
167
+ this.#expectImportPath = true;
168
+ return;
169
+ }
170
+ if (this.#expectImportPath)
171
+ this.#expectImportPath = false;
172
+ }
173
+ #fail(code, start, end, message) {
174
+ this.#advanceTo(end);
175
+ throw new SyqlFrontendError(code, { file: this.#file, start, end: this.#position() }, message);
176
+ }
177
+ #next() {
178
+ const startOffset = this.#offset;
179
+ const code = this.#source.charCodeAt(startOffset);
180
+ const ch = this.#source[startOffset];
181
+ if (startOffset === 0 && code === 0xfeff) {
182
+ this.#emit('whitespace', startOffset + 1);
183
+ return;
184
+ }
185
+ if (isAsciiWhitespace(code)) {
186
+ let end = startOffset + 1;
187
+ while (end < this.#source.length &&
188
+ isAsciiWhitespace(this.#source.charCodeAt(end))) {
189
+ end += 1;
190
+ }
191
+ this.#emit('whitespace', end);
192
+ return;
193
+ }
194
+ if (this.#source.startsWith('--', startOffset)) {
195
+ let end = startOffset + 2;
196
+ while (end < this.#source.length) {
197
+ const current = this.#source.charCodeAt(end);
198
+ if (current === 0x0a || current === 0x0d)
199
+ break;
200
+ end += 1;
201
+ }
202
+ this.#emit('line-comment', end);
203
+ return;
204
+ }
205
+ if (this.#source.startsWith('/*', startOffset)) {
206
+ const close = this.#source.indexOf('*/', startOffset + 2);
207
+ if (close === -1) {
208
+ this.#fail('SYQL1003_UNTERMINATED_COMMENT', this.#position(), this.#source.length, 'unterminated block comment');
209
+ }
210
+ this.#emit('block-comment', close + 2);
211
+ return;
212
+ }
213
+ if (this.#expectImportPath && ch === '"') {
214
+ this.#scanImportPath();
215
+ return;
216
+ }
217
+ if ((ch === 'x' || ch === 'X') && this.#source[startOffset + 1] === "'") {
218
+ this.#scanDelimited("'", 'blob', 'SYQL1001_UNTERMINATED_STRING');
219
+ return;
220
+ }
221
+ if (ch === "'") {
222
+ this.#scanDelimited("'", 'string', 'SYQL1001_UNTERMINATED_STRING');
223
+ return;
224
+ }
225
+ if (ch === '"' || ch === '`') {
226
+ this.#scanDelimited(ch, 'quoted-identifier', 'SYQL1002_UNTERMINATED_IDENTIFIER');
227
+ return;
228
+ }
229
+ if (ch === '[') {
230
+ const close = this.#source.indexOf(']', startOffset + 1);
231
+ if (close === -1) {
232
+ this.#fail('SYQL1002_UNTERMINATED_IDENTIFIER', this.#position(), this.#source.length, 'unterminated bracketed identifier');
233
+ }
234
+ this.#emit('quoted-identifier', close + 1);
235
+ return;
236
+ }
237
+ if (ch === ':') {
238
+ const next = this.#source.charCodeAt(startOffset + 1);
239
+ if (isIdentifierStart(next)) {
240
+ let end = startOffset + 2;
241
+ while (end < this.#source.length &&
242
+ isIdentifierContinue(this.#source.charCodeAt(end))) {
243
+ end += 1;
244
+ }
245
+ this.#emit('bind', end);
246
+ }
247
+ else {
248
+ this.#emit('operator', startOffset + 1);
249
+ }
250
+ return;
251
+ }
252
+ if (ch === '@') {
253
+ const next = this.#source.charCodeAt(startOffset + 1);
254
+ if (isIdentifierStart(next)) {
255
+ let end = startOffset + 2;
256
+ while (end < this.#source.length &&
257
+ isIdentifierContinue(this.#source.charCodeAt(end))) {
258
+ end += 1;
259
+ }
260
+ this.#emit('at-identifier', end);
261
+ }
262
+ else {
263
+ this.#emit('operator', startOffset + 1);
264
+ }
265
+ return;
266
+ }
267
+ if (isAsciiDigit(code) ||
268
+ (ch === '.' && isAsciiDigit(this.#source.charCodeAt(startOffset + 1)))) {
269
+ this.#emit('number', this.#scanNumber(startOffset));
270
+ return;
271
+ }
272
+ if (isIdentifierStart(code)) {
273
+ let end = startOffset + 1;
274
+ while (end < this.#source.length &&
275
+ isIdentifierContinue(this.#source.charCodeAt(end))) {
276
+ end += 1;
277
+ }
278
+ this.#emit('identifier', end);
279
+ return;
280
+ }
281
+ if (isPunctuation(ch)) {
282
+ this.#emit('punctuation', startOffset + 1);
283
+ return;
284
+ }
285
+ const three = this.#source.slice(startOffset, startOffset + 3);
286
+ if (THREE_CHAR_OPERATORS.has(three)) {
287
+ this.#emit('operator', startOffset + 3);
288
+ return;
289
+ }
290
+ const two = this.#source.slice(startOffset, startOffset + 2);
291
+ if (TWO_CHAR_OPERATORS.has(two)) {
292
+ this.#emit('operator', startOffset + 2);
293
+ return;
294
+ }
295
+ // Keep unknown SQLite punctuation lossless and let the parser/reference
296
+ // SQLite validator issue the contextual error later.
297
+ this.#emit('operator', startOffset + 1);
298
+ }
299
+ #scanDelimited(delimiter, kind, code) {
300
+ const start = this.#position();
301
+ const contentStart = kind === 'blob' ? this.#offset + 2 : this.#offset + 1;
302
+ let end = contentStart;
303
+ while (end < this.#source.length) {
304
+ if (this.#source[end] !== delimiter) {
305
+ end += 1;
306
+ continue;
307
+ }
308
+ if (this.#source[end + 1] === delimiter) {
309
+ end += 2;
310
+ continue;
311
+ }
312
+ this.#emit(kind, end + 1);
313
+ return;
314
+ }
315
+ this.#fail(code, start, this.#source.length, kind === 'string' || kind === 'blob'
316
+ ? 'unterminated SQL string literal'
317
+ : 'unterminated quoted identifier');
318
+ }
319
+ #scanImportPath() {
320
+ const start = this.#position();
321
+ let end = this.#offset + 1;
322
+ while (end < this.#source.length) {
323
+ const code = this.#source.charCodeAt(end);
324
+ if (code === 0x0a || code === 0x0d)
325
+ break;
326
+ if (code === 0x5c) {
327
+ end += 2;
328
+ continue;
329
+ }
330
+ if (code === 0x22) {
331
+ this.#emit('import-path', end + 1);
332
+ return;
333
+ }
334
+ end += 1;
335
+ }
336
+ this.#fail('SYQL1004_UNTERMINATED_IMPORT_PATH', start, Math.min(end, this.#source.length), 'unterminated JSON import path');
337
+ }
338
+ #scanNumber(start) {
339
+ if (this.#source[start] === '0' &&
340
+ (this.#source[start + 1] === 'x' || this.#source[start + 1] === 'X') &&
341
+ isAsciiHex(this.#source.charCodeAt(start + 2))) {
342
+ return scanDigitRun(this.#source, start + 2, isAsciiHex);
343
+ }
344
+ let end = start;
345
+ if (this.#source[end] !== '.') {
346
+ end = scanDigitRun(this.#source, end, isAsciiDigit);
347
+ }
348
+ if (this.#source[end] === '.') {
349
+ end += 1;
350
+ end = scanDigitRun(this.#source, end, isAsciiDigit);
351
+ }
352
+ if (this.#source[end] === 'e' || this.#source[end] === 'E') {
353
+ let exponent = end + 1;
354
+ if (this.#source[exponent] === '+' || this.#source[exponent] === '-') {
355
+ exponent += 1;
356
+ }
357
+ const exponentEnd = scanDigitRun(this.#source, exponent, isAsciiDigit);
358
+ if (exponentEnd > exponent)
359
+ end = exponentEnd;
360
+ }
361
+ return end;
362
+ }
363
+ }
364
+ /** Lex one complete `.syql` source file, including trivia and an EOF token. */
365
+ export function lexSyqlSource(file, source) {
366
+ return new Lexer(file, source).lex();
367
+ }
@@ -0,0 +1,21 @@
1
+ import { type SyqlImportDeclaration, type SyqlSyntaxFile } from './syql-parser.js';
2
+ export type SyqlModuleErrorCode = 'SYQL4001_IMPORT_OUTSIDE_ROOT' | 'SYQL4002_MODULE_NOT_FOUND' | 'SYQL4003_IMPORT_CYCLE' | 'SYQL4004_UNKNOWN_PREDICATE' | 'SYQL4005_DUPLICATE_IMPORT_TARGET' | 'SYQL4006_DUPLICATE_QUERY';
3
+ export type SyqlSourceLoader = (canonicalPath: string) => string | undefined;
4
+ export interface SyqlImportEdge {
5
+ readonly from: string;
6
+ readonly to: string;
7
+ readonly declaration: SyqlImportDeclaration;
8
+ }
9
+ export interface SyqlModuleGraph {
10
+ readonly root: string;
11
+ readonly entries: readonly string[];
12
+ /** Dependency-first deterministic module order. */
13
+ readonly modules: readonly SyqlSyntaxFile[];
14
+ readonly moduleByPath: ReadonlyMap<string, SyqlSyntaxFile>;
15
+ readonly edges: readonly SyqlImportEdge[];
16
+ }
17
+ /**
18
+ * Parse and resolve every module reachable from the configured entry files.
19
+ * Paths passed to `load` are absolute, normalized paths under `root`.
20
+ */
21
+ export declare function buildSyqlModuleGraph(root: string, entries: readonly string[], load: SyqlSourceLoader): SyqlModuleGraph;
@@ -0,0 +1,129 @@
1
+ /** Revision-1 SYQL import graph and predicate-library resolver (§4). */
2
+ import { dirname, isAbsolute, relative, resolve, sep } from 'node:path';
3
+ import { SyqlFrontendError, } from './syql-lexer.js';
4
+ import { parseSyqlSyntaxFile, } from './syql-parser.js';
5
+ function startPosition() {
6
+ return { offset: 0, line: 1, column: 1 };
7
+ }
8
+ function startSpan(file) {
9
+ const position = startPosition();
10
+ return { file, start: position, end: position };
11
+ }
12
+ function isWithinRoot(root, candidate) {
13
+ const path = relative(root, candidate);
14
+ return (path === '' ||
15
+ (!isAbsolute(path) && path !== '..' && !path.startsWith(`..${sep}`)));
16
+ }
17
+ function displayPath(root, file) {
18
+ const path = relative(root, file);
19
+ return path === '' ? '.' : path.split(sep).join('/');
20
+ }
21
+ class ModuleGraphBuilder {
22
+ #root;
23
+ #load;
24
+ #moduleByPath = new Map();
25
+ #edges = [];
26
+ #order = [];
27
+ #state = new Map();
28
+ #stack = [];
29
+ #queryNames = new Map();
30
+ constructor(root, load) {
31
+ this.#root = resolve(root);
32
+ this.#load = load;
33
+ }
34
+ build(entries) {
35
+ const canonicalEntries = entries.map((entry) => this.#entryPath(entry));
36
+ for (const entry of canonicalEntries)
37
+ this.#visit(entry);
38
+ this.#validateImports();
39
+ this.#validateGlobalQueryNames();
40
+ return {
41
+ root: this.#root,
42
+ entries: canonicalEntries,
43
+ modules: this.#order,
44
+ moduleByPath: this.#moduleByPath,
45
+ edges: this.#edges,
46
+ };
47
+ }
48
+ #entryPath(entry) {
49
+ const candidate = resolve(this.#root, entry);
50
+ if (!isWithinRoot(this.#root, candidate)) {
51
+ this.#fail('SYQL4001_IMPORT_OUTSIDE_ROOT', startSpan(candidate), `entry ${JSON.stringify(entry)} resolves outside the configured queries root`);
52
+ }
53
+ return candidate;
54
+ }
55
+ #visit(file, incoming) {
56
+ const state = this.#state.get(file);
57
+ if (state === 'complete')
58
+ return this.#moduleByPath.get(file);
59
+ if (state === 'active') {
60
+ const cycleStart = this.#stack.indexOf(file);
61
+ const cycle = [...this.#stack.slice(cycleStart), file]
62
+ .map((item) => displayPath(this.#root, item))
63
+ .join(' -> ');
64
+ this.#fail('SYQL4003_IMPORT_CYCLE', incoming?.span ?? startSpan(file), `SYQL import cycle: ${cycle}`);
65
+ }
66
+ const source = this.#load(file);
67
+ if (source === undefined) {
68
+ this.#fail('SYQL4002_MODULE_NOT_FOUND', incoming?.span ?? startSpan(file), `SYQL module not found: ${displayPath(this.#root, file)}`);
69
+ }
70
+ const module = parseSyqlSyntaxFile(file, source);
71
+ this.#moduleByPath.set(file, module);
72
+ this.#state.set(file, 'active');
73
+ this.#stack.push(file);
74
+ for (const declaration of module.imports) {
75
+ const target = resolve(dirname(file), declaration.path);
76
+ if (!isWithinRoot(this.#root, target)) {
77
+ this.#fail('SYQL4001_IMPORT_OUTSIDE_ROOT', declaration.span, `import ${JSON.stringify(declaration.path)} escapes the configured queries root`);
78
+ }
79
+ this.#edges.push({ from: file, to: target, declaration });
80
+ this.#visit(target, declaration);
81
+ }
82
+ this.#stack.pop();
83
+ this.#state.set(file, 'complete');
84
+ this.#order.push(module);
85
+ return module;
86
+ }
87
+ #validateImports() {
88
+ const importedTargetsByModule = new Map();
89
+ for (const edge of this.#edges) {
90
+ const target = this.#moduleByPath.get(edge.to);
91
+ const predicates = new Set(target.predicates.map((predicate) => predicate.name));
92
+ let targets = importedTargetsByModule.get(edge.from);
93
+ if (targets === undefined) {
94
+ targets = new Set();
95
+ importedTargetsByModule.set(edge.from, targets);
96
+ }
97
+ for (const item of edge.declaration.items) {
98
+ const key = `${edge.to}\0${item.imported}`;
99
+ if (targets.has(key)) {
100
+ this.#fail('SYQL4005_DUPLICATE_IMPORT_TARGET', item.span, `predicate ${JSON.stringify(item.imported)} is imported more than once from ${displayPath(this.#root, edge.to)}`);
101
+ }
102
+ targets.add(key);
103
+ if (!predicates.has(item.imported)) {
104
+ this.#fail('SYQL4004_UNKNOWN_PREDICATE', item.span, `${displayPath(this.#root, edge.to)} does not export predicate ${JSON.stringify(item.imported)}`);
105
+ }
106
+ }
107
+ }
108
+ }
109
+ #validateGlobalQueryNames() {
110
+ for (const module of this.#order) {
111
+ for (const query of module.queries) {
112
+ if (this.#queryNames.has(query.name)) {
113
+ this.#fail('SYQL4006_DUPLICATE_QUERY', query.nameSpan, `duplicate query name ${JSON.stringify(query.name)} across the configured SYQL graph`);
114
+ }
115
+ this.#queryNames.set(query.name, query.nameSpan);
116
+ }
117
+ }
118
+ }
119
+ #fail(code, span, message) {
120
+ throw new SyqlFrontendError(code, span, message);
121
+ }
122
+ }
123
+ /**
124
+ * Parse and resolve every module reachable from the configured entry files.
125
+ * Paths passed to `load` are absolute, normalized paths under `root`.
126
+ */
127
+ export function buildSyqlModuleGraph(root, entries, load) {
128
+ return new ModuleGraphBuilder(root, load).build(entries);
129
+ }
@@ -0,0 +1,132 @@
1
+ /** Revision-1 SYQL container parser (`docs/SYQL.md` §3). */
2
+ import { type SyqlSourceSpan, type SyqlToken } from './syql-lexer.js';
3
+ import { type SyqlEmbeddedTemplate } from './syql-template-parser.js';
4
+ export type SyqlBaseType = 'string' | 'integer' | 'float' | 'boolean' | 'json' | 'bytes' | 'blob_ref' | 'crdt';
5
+ export interface SyqlValueType {
6
+ readonly base: SyqlBaseType;
7
+ readonly nullable: boolean;
8
+ readonly span: SyqlSourceSpan;
9
+ }
10
+ export interface SyqlValueParameter {
11
+ readonly kind: 'value';
12
+ readonly name: string;
13
+ readonly optional: boolean;
14
+ readonly type?: SyqlValueType;
15
+ readonly span: SyqlSourceSpan;
16
+ readonly nameSpan: SyqlSourceSpan;
17
+ }
18
+ export interface SyqlSwitchParameter {
19
+ readonly kind: 'switch';
20
+ readonly name: string;
21
+ readonly optional: true;
22
+ readonly span: SyqlSourceSpan;
23
+ readonly nameSpan: SyqlSourceSpan;
24
+ }
25
+ export interface SyqlGroupMember {
26
+ readonly name: string;
27
+ readonly type?: SyqlValueType;
28
+ readonly span: SyqlSourceSpan;
29
+ readonly nameSpan: SyqlSourceSpan;
30
+ }
31
+ export interface SyqlGroupParameter {
32
+ readonly kind: 'group';
33
+ readonly name: string;
34
+ readonly optional: true;
35
+ readonly members: readonly SyqlGroupMember[];
36
+ readonly span: SyqlSourceSpan;
37
+ readonly nameSpan: SyqlSourceSpan;
38
+ }
39
+ export type SyqlQueryParameter = SyqlValueParameter | SyqlSwitchParameter | SyqlGroupParameter;
40
+ export interface SyqlPredicateParameter {
41
+ readonly name: string;
42
+ readonly type?: SyqlValueType;
43
+ readonly span: SyqlSourceSpan;
44
+ readonly nameSpan: SyqlSourceSpan;
45
+ }
46
+ /** A lossless SQL token template between a pair of container braces. */
47
+ export interface SyqlTemplate {
48
+ readonly text: string;
49
+ readonly tokens: readonly SyqlToken[];
50
+ /** Structurally parsed SYQL nodes embedded in the otherwise-lossless SQL. */
51
+ readonly tree: SyqlEmbeddedTemplate;
52
+ /** Span of the inner text, excluding braces. */
53
+ readonly span: SyqlSourceSpan;
54
+ }
55
+ export interface SyqlImportItem {
56
+ readonly imported: string;
57
+ readonly local: string;
58
+ readonly span: SyqlSourceSpan;
59
+ }
60
+ export interface SyqlImportDeclaration {
61
+ readonly kind: 'import';
62
+ readonly items: readonly SyqlImportItem[];
63
+ /** Decoded JSON path. */
64
+ readonly path: string;
65
+ readonly span: SyqlSourceSpan;
66
+ }
67
+ export interface SyqlPredicateDeclaration {
68
+ readonly kind: 'predicate';
69
+ readonly name: string;
70
+ readonly parameters: readonly SyqlPredicateParameter[];
71
+ readonly body: SyqlTemplate;
72
+ readonly span: SyqlSourceSpan;
73
+ readonly nameSpan: SyqlSourceSpan;
74
+ }
75
+ export interface SyqlSqlSection {
76
+ readonly kind: 'sql';
77
+ readonly body: SyqlTemplate;
78
+ readonly span: SyqlSourceSpan;
79
+ }
80
+ export interface SyqlSortProfile {
81
+ readonly name: string;
82
+ readonly order: SyqlTemplate;
83
+ readonly span: SyqlSourceSpan;
84
+ readonly nameSpan: SyqlSourceSpan;
85
+ }
86
+ export interface SyqlSortSection {
87
+ readonly kind: 'sort';
88
+ readonly control: string;
89
+ readonly defaultProfile: string;
90
+ readonly profiles: readonly SyqlSortProfile[];
91
+ readonly span: SyqlSourceSpan;
92
+ readonly controlSpan: SyqlSourceSpan;
93
+ }
94
+ export interface SyqlPageDeclaration {
95
+ readonly kind: 'page';
96
+ readonly control: string;
97
+ readonly defaultSize: number;
98
+ readonly maxSize: number;
99
+ readonly span: SyqlSourceSpan;
100
+ readonly controlSpan: SyqlSourceSpan;
101
+ }
102
+ export interface SyqlIdentityDeclaration {
103
+ readonly kind: 'identity';
104
+ readonly fields: readonly string[];
105
+ readonly span: SyqlSourceSpan;
106
+ }
107
+ export interface SyqlQueryDeclaration {
108
+ readonly kind: 'query';
109
+ readonly name: string;
110
+ readonly parameters: readonly SyqlQueryParameter[];
111
+ readonly sql: SyqlSqlSection;
112
+ readonly sort?: SyqlSortSection;
113
+ readonly page?: SyqlPageDeclaration;
114
+ readonly identity?: SyqlIdentityDeclaration;
115
+ readonly span: SyqlSourceSpan;
116
+ readonly nameSpan: SyqlSourceSpan;
117
+ }
118
+ export type SyqlDeclaration = SyqlPredicateDeclaration | SyqlQueryDeclaration;
119
+ export interface SyqlSyntaxFile {
120
+ readonly kind: 'file';
121
+ readonly file: string;
122
+ readonly source: string;
123
+ readonly tokens: readonly SyqlToken[];
124
+ readonly imports: readonly SyqlImportDeclaration[];
125
+ readonly declarations: readonly SyqlDeclaration[];
126
+ readonly predicates: readonly SyqlPredicateDeclaration[];
127
+ readonly queries: readonly SyqlQueryDeclaration[];
128
+ readonly span: SyqlSourceSpan;
129
+ }
130
+ export type SyqlParseErrorCode = 'SYQL2001_EXPECTED_TOKEN' | 'SYQL2002_INVALID_NAME' | 'SYQL2003_RESERVED_NAME' | 'SYQL2004_DUPLICATE_NAME' | 'SYQL2005_INVALID_IMPORT' | 'SYQL2006_EMPTY_TEMPLATE' | 'SYQL2007_FORBIDDEN_SEMICOLON' | 'SYQL2008_INVALID_MEMBER' | 'SYQL2009_INVALID_INTEGER' | 'SYQL2010_INVALID_PAGE_RANGE' | 'SYQL2011_INVALID_PARAMETER';
131
+ /** Parse one `.syql` file using the destructive revision-1 grammar. */
132
+ export declare function parseSyqlSyntaxFile(file: string, source: string): SyqlSyntaxFile;