@syncular/typegen 0.15.47 → 0.16.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/README.md +4 -0
- package/dist/emit-queries-rust.js +1 -1
- package/dist/emit-queries.js +12 -4
- package/dist/index.d.ts +1 -0
- package/dist/index.js +1 -0
- package/dist/lsp.js +13 -2
- package/dist/query-ir.js +3 -1
- package/dist/query.d.ts +12 -0
- package/dist/query.js +170 -152
- package/dist/syql-diagnostics.d.ts +77 -0
- package/dist/syql-diagnostics.js +72 -0
- package/dist/syql-lowering.js +1 -0
- package/dist/syql-validator.js +9 -1
- package/package.json +3 -3
- package/src/emit-queries-rust.ts +1 -1
- package/src/emit-queries.ts +14 -4
- package/src/index.ts +1 -0
- package/src/lsp.ts +18 -5
- package/src/query-ir.ts +3 -1
- package/src/query.ts +200 -168
- package/src/syql-diagnostics.ts +160 -0
- package/src/syql-lowering.ts +1 -0
- package/src/syql-validator.ts +12 -1
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
import type { SyqlLexErrorCode } from './syql-lexer.js';
|
|
2
|
+
import type { SyqlLoweringErrorCode } from './syql-lowering.js';
|
|
3
|
+
import type { SyqlModuleErrorCode } from './syql-modules.js';
|
|
4
|
+
import type { SyqlParseErrorCode } from './syql-parser.js';
|
|
5
|
+
import type { SyqlSemanticErrorCode } from './syql-semantics.js';
|
|
6
|
+
import type { SyqlTemplateParseErrorCode } from './syql-template-parser.js';
|
|
7
|
+
import type { SyqlValidationErrorCode } from './syql-validator.js';
|
|
8
|
+
export type SyqlRuntimeErrorCode = 'SYQL_RUNTIME_MISSING_REQUIRED_INPUT' | 'SYQL_RUNTIME_UNKNOWN_INPUT' | 'SYQL_RUNTIME_INVALID_INPUT' | 'SYQL_RUNTIME_INVALID_GROUP' | 'SYQL_RUNTIME_INVALID_SORT' | 'SYQL_RUNTIME_INVALID_LIMIT';
|
|
9
|
+
export type SyqlDiagnosticCode = SyqlLexErrorCode | SyqlParseErrorCode | SyqlTemplateParseErrorCode | SyqlModuleErrorCode | SyqlSemanticErrorCode | SyqlValidationErrorCode | SyqlLoweringErrorCode | 'SYQL9001_PROJECT_CONTEXT' | SyqlRuntimeErrorCode;
|
|
10
|
+
/** Stable compiler and generated-runtime diagnostic remedies keyed by code. */
|
|
11
|
+
export declare const SYQL_DIAGNOSTIC_REMEDIES: {
|
|
12
|
+
readonly SYQL1001_UNTERMINATED_STRING: "Close the string literal with a matching single quote.";
|
|
13
|
+
readonly SYQL1002_UNTERMINATED_IDENTIFIER: "Close the quoted identifier with its matching delimiter.";
|
|
14
|
+
readonly SYQL1003_UNTERMINATED_COMMENT: "Close the block comment with */.";
|
|
15
|
+
readonly SYQL1004_UNTERMINATED_IMPORT_PATH: "Close the import path with a matching double quote.";
|
|
16
|
+
readonly SYQL2001_EXPECTED_TOKEN: "Insert the token named by the diagnostic at the reported source position.";
|
|
17
|
+
readonly SYQL2002_INVALID_NAME: "Rename the declaration or parameter to a valid lower-camel identifier.";
|
|
18
|
+
readonly SYQL2003_RESERVED_NAME: "Rename the declaration or parameter so it does not use a reserved SYQL name.";
|
|
19
|
+
readonly SYQL2004_DUPLICATE_NAME: "Give each declaration, parameter, group member, and profile a unique name in its scope.";
|
|
20
|
+
readonly SYQL2005_INVALID_IMPORT: "Use a relative .syql import with explicit imported predicate names.";
|
|
21
|
+
readonly SYQL2006_EMPTY_TEMPLATE: "Add a SQL expression or statement inside the braces.";
|
|
22
|
+
readonly SYQL2007_FORBIDDEN_SEMICOLON: "Remove the semicolon from the embedded SQL template.";
|
|
23
|
+
readonly SYQL2008_INVALID_MEMBER: "Use a member supported by the surrounding SYQL declaration.";
|
|
24
|
+
readonly SYQL2009_INVALID_INTEGER: "Use a decimal integer within the range named by the diagnostic.";
|
|
25
|
+
readonly SYQL2010_INVALID_PAGE_RANGE: "Choose default and maximum page sizes that satisfy the declared range.";
|
|
26
|
+
readonly SYQL2011_INVALID_PARAMETER: "Rewrite the parameter using a supported value, optional, range, group, sort, or limit form.";
|
|
27
|
+
readonly SYQL2012_INVALID_QUERY_BODY: "Rewrite the query body using the clause order and forms defined by the SYQL grammar.";
|
|
28
|
+
readonly SYQL3001_EXPECTED_EMBEDDED_TOKEN: "Insert the required token in the embedded SQL construct.";
|
|
29
|
+
readonly SYQL3002_INVALID_BIND: "Bind a declared input with the :name form in a supported SQL position.";
|
|
30
|
+
readonly SYQL3003_INVALID_PREDICATE_CALL: "Call the predicate with declared bind arguments and matching parentheses.";
|
|
31
|
+
readonly SYQL3004_INVALID_WHEN: "Use when with declared controls and a nonempty conditional SQL body.";
|
|
32
|
+
readonly SYQL3005_INVALID_REACTIVE_DIRECTIVE: "Rewrite the reactive directive using a supported revision-1 form.";
|
|
33
|
+
readonly SYQL3006_FORBIDDEN_TEMPLATE_NODE: "Remove the conditional or predicate construct from this SQL context.";
|
|
34
|
+
readonly SYQL3007_UNEXPECTED_BRACE: "Remove the unmatched brace or close the surrounding embedded construct.";
|
|
35
|
+
readonly SYQL3008_FORBIDDEN_PARAMETER_FORM: "Use a value bind in this context instead of a range, group, sort, or limit control.";
|
|
36
|
+
readonly SYQL4001_IMPORT_OUTSIDE_ROOT: "Move the imported file under the configured query root and use a relative path.";
|
|
37
|
+
readonly SYQL4002_MODULE_NOT_FOUND: "Create the imported .syql file or correct the relative import path.";
|
|
38
|
+
readonly SYQL4003_IMPORT_CYCLE: "Remove one import edge so predicate modules form an acyclic graph.";
|
|
39
|
+
readonly SYQL4004_UNKNOWN_PREDICATE: "Export and import the predicate under the referenced name.";
|
|
40
|
+
readonly SYQL4005_DUPLICATE_IMPORT_TARGET: "Import each local predicate name once in the module.";
|
|
41
|
+
readonly SYQL4006_DUPLICATE_QUERY: "Give every query in the project a unique public name.";
|
|
42
|
+
readonly SYQL5001_UNKNOWN_PREDICATE: "Define the predicate locally or import it under the referenced name.";
|
|
43
|
+
readonly SYQL5002_PREDICATE_CYCLE: "Remove the recursive predicate call so expansion terminates.";
|
|
44
|
+
readonly SYQL5003_PREDICATE_ARITY: "Pass exactly the number of bind arguments declared by the predicate.";
|
|
45
|
+
readonly SYQL5004_CLOSED_PREDICATE: "Declare each external bind as a predicate parameter and pass it at the call site.";
|
|
46
|
+
readonly SYQL5005_UNUSED_PREDICATE_PARAMETER: "Use the predicate parameter in its body or remove it from the signature.";
|
|
47
|
+
readonly SYQL5006_UNDECLARED_BIND: "Declare the bind as a query input or predicate parameter.";
|
|
48
|
+
readonly SYQL5007_UNUSED_INPUT: "Use the query input in SQL or remove it from the public signature.";
|
|
49
|
+
readonly SYQL5008_INVALID_CONTROL: "Use an optional input, group, sort, or limit name as the control.";
|
|
50
|
+
readonly SYQL5009_MISSING_DOMINANCE: "Guard every optional bind with a when condition that proves its presence.";
|
|
51
|
+
readonly SYQL5010_UNUSED_CONTROL: "Use the control in the query body or remove it from the signature.";
|
|
52
|
+
readonly SYQL5011_TYPE_CONFLICT: "Make every declaration and SQL use of the bind agree on one SYQL type.";
|
|
53
|
+
readonly SYQL6001_INVALID_PLACEMENT: "Move the SYQL construct to the SQL clause named by the diagnostic.";
|
|
54
|
+
readonly SYQL6002_INVALID_SQL: "Correct the reported SQLite syntax, table, column, function, or bind error.";
|
|
55
|
+
readonly SYQL6003_NONDETERMINISTIC_SQL: "Replace nondeterministic SQL with values supplied through query inputs or stored columns.";
|
|
56
|
+
readonly SYQL6004_TYPE_CONFLICT: "Align the declared input type with the schema column and SQL expression types.";
|
|
57
|
+
readonly SYQL6005_INVALID_SYNC_QUERY: "Select stable row identity and preserve the scope coverage required for reactive sync.";
|
|
58
|
+
readonly SYQL6006_INVALID_SORT: "Use declared sort profiles with deterministic ORDER BY terms and a stable tie-breaker.";
|
|
59
|
+
readonly SYQL6007_INVALID_LIMIT: "Use the declared limit control and keep its default and maximum within the supported range.";
|
|
60
|
+
readonly SYQL6008_INVALID_IDENTITY: "Declare identity columns that are selected, non-null, and sufficient to identify each result row.";
|
|
61
|
+
readonly SYQL7001_ENUMERATION_LIMIT: "Use the auto or neutralize backend, reduce control combinations, or raise the explicit compiler limit.";
|
|
62
|
+
readonly SYQL7002_INTERNAL_LOWERING: "Report the query and diagnostic to the Syncular maintainers.";
|
|
63
|
+
readonly SYQL9001_PROJECT_CONTEXT: "Open the file under a project with a valid syncular.json, migrations, schema output, and query root.";
|
|
64
|
+
readonly SYQL_RUNTIME_MISSING_REQUIRED_INPUT: "Pass the required generated-query input.";
|
|
65
|
+
readonly SYQL_RUNTIME_UNKNOWN_INPUT: "Remove the unknown key from the generated-query input object.";
|
|
66
|
+
readonly SYQL_RUNTIME_INVALID_INPUT: "Pass a value that matches the generated input type and nullability.";
|
|
67
|
+
readonly SYQL_RUNTIME_INVALID_GROUP: "Pass either the complete generated group shape or omit the optional group.";
|
|
68
|
+
readonly SYQL_RUNTIME_INVALID_SORT: "Pass one of the sort profile names exported for the generated query.";
|
|
69
|
+
readonly SYQL_RUNTIME_INVALID_LIMIT: "Pass an integer within the generated query limit range.";
|
|
70
|
+
};
|
|
71
|
+
/** Generate a deterministic JSON-ready code-to-remedy catalog. */
|
|
72
|
+
export declare function generateSyqlDiagnosticCatalog(): Readonly<Array<{
|
|
73
|
+
readonly code: SyqlDiagnosticCode;
|
|
74
|
+
readonly remedy: string;
|
|
75
|
+
}>>;
|
|
76
|
+
/** Resolve a diagnostic remedy without requiring a narrowed code type. */
|
|
77
|
+
export declare function syqlDiagnosticRemedy(code: string): string | undefined;
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
/** Stable compiler and generated-runtime diagnostic remedies keyed by code. */
|
|
2
|
+
export const SYQL_DIAGNOSTIC_REMEDIES = {
|
|
3
|
+
SYQL1001_UNTERMINATED_STRING: 'Close the string literal with a matching single quote.',
|
|
4
|
+
SYQL1002_UNTERMINATED_IDENTIFIER: 'Close the quoted identifier with its matching delimiter.',
|
|
5
|
+
SYQL1003_UNTERMINATED_COMMENT: 'Close the block comment with */.',
|
|
6
|
+
SYQL1004_UNTERMINATED_IMPORT_PATH: 'Close the import path with a matching double quote.',
|
|
7
|
+
SYQL2001_EXPECTED_TOKEN: 'Insert the token named by the diagnostic at the reported source position.',
|
|
8
|
+
SYQL2002_INVALID_NAME: 'Rename the declaration or parameter to a valid lower-camel identifier.',
|
|
9
|
+
SYQL2003_RESERVED_NAME: 'Rename the declaration or parameter so it does not use a reserved SYQL name.',
|
|
10
|
+
SYQL2004_DUPLICATE_NAME: 'Give each declaration, parameter, group member, and profile a unique name in its scope.',
|
|
11
|
+
SYQL2005_INVALID_IMPORT: 'Use a relative .syql import with explicit imported predicate names.',
|
|
12
|
+
SYQL2006_EMPTY_TEMPLATE: 'Add a SQL expression or statement inside the braces.',
|
|
13
|
+
SYQL2007_FORBIDDEN_SEMICOLON: 'Remove the semicolon from the embedded SQL template.',
|
|
14
|
+
SYQL2008_INVALID_MEMBER: 'Use a member supported by the surrounding SYQL declaration.',
|
|
15
|
+
SYQL2009_INVALID_INTEGER: 'Use a decimal integer within the range named by the diagnostic.',
|
|
16
|
+
SYQL2010_INVALID_PAGE_RANGE: 'Choose default and maximum page sizes that satisfy the declared range.',
|
|
17
|
+
SYQL2011_INVALID_PARAMETER: 'Rewrite the parameter using a supported value, optional, range, group, sort, or limit form.',
|
|
18
|
+
SYQL2012_INVALID_QUERY_BODY: 'Rewrite the query body using the clause order and forms defined by the SYQL grammar.',
|
|
19
|
+
SYQL3001_EXPECTED_EMBEDDED_TOKEN: 'Insert the required token in the embedded SQL construct.',
|
|
20
|
+
SYQL3002_INVALID_BIND: 'Bind a declared input with the :name form in a supported SQL position.',
|
|
21
|
+
SYQL3003_INVALID_PREDICATE_CALL: 'Call the predicate with declared bind arguments and matching parentheses.',
|
|
22
|
+
SYQL3004_INVALID_WHEN: 'Use when with declared controls and a nonempty conditional SQL body.',
|
|
23
|
+
SYQL3005_INVALID_REACTIVE_DIRECTIVE: 'Rewrite the reactive directive using a supported revision-1 form.',
|
|
24
|
+
SYQL3006_FORBIDDEN_TEMPLATE_NODE: 'Remove the conditional or predicate construct from this SQL context.',
|
|
25
|
+
SYQL3007_UNEXPECTED_BRACE: 'Remove the unmatched brace or close the surrounding embedded construct.',
|
|
26
|
+
SYQL3008_FORBIDDEN_PARAMETER_FORM: 'Use a value bind in this context instead of a range, group, sort, or limit control.',
|
|
27
|
+
SYQL4001_IMPORT_OUTSIDE_ROOT: 'Move the imported file under the configured query root and use a relative path.',
|
|
28
|
+
SYQL4002_MODULE_NOT_FOUND: 'Create the imported .syql file or correct the relative import path.',
|
|
29
|
+
SYQL4003_IMPORT_CYCLE: 'Remove one import edge so predicate modules form an acyclic graph.',
|
|
30
|
+
SYQL4004_UNKNOWN_PREDICATE: 'Export and import the predicate under the referenced name.',
|
|
31
|
+
SYQL4005_DUPLICATE_IMPORT_TARGET: 'Import each local predicate name once in the module.',
|
|
32
|
+
SYQL4006_DUPLICATE_QUERY: 'Give every query in the project a unique public name.',
|
|
33
|
+
SYQL5001_UNKNOWN_PREDICATE: 'Define the predicate locally or import it under the referenced name.',
|
|
34
|
+
SYQL5002_PREDICATE_CYCLE: 'Remove the recursive predicate call so expansion terminates.',
|
|
35
|
+
SYQL5003_PREDICATE_ARITY: 'Pass exactly the number of bind arguments declared by the predicate.',
|
|
36
|
+
SYQL5004_CLOSED_PREDICATE: 'Declare each external bind as a predicate parameter and pass it at the call site.',
|
|
37
|
+
SYQL5005_UNUSED_PREDICATE_PARAMETER: 'Use the predicate parameter in its body or remove it from the signature.',
|
|
38
|
+
SYQL5006_UNDECLARED_BIND: 'Declare the bind as a query input or predicate parameter.',
|
|
39
|
+
SYQL5007_UNUSED_INPUT: 'Use the query input in SQL or remove it from the public signature.',
|
|
40
|
+
SYQL5008_INVALID_CONTROL: 'Use an optional input, group, sort, or limit name as the control.',
|
|
41
|
+
SYQL5009_MISSING_DOMINANCE: 'Guard every optional bind with a when condition that proves its presence.',
|
|
42
|
+
SYQL5010_UNUSED_CONTROL: 'Use the control in the query body or remove it from the signature.',
|
|
43
|
+
SYQL5011_TYPE_CONFLICT: 'Make every declaration and SQL use of the bind agree on one SYQL type.',
|
|
44
|
+
SYQL6001_INVALID_PLACEMENT: 'Move the SYQL construct to the SQL clause named by the diagnostic.',
|
|
45
|
+
SYQL6002_INVALID_SQL: 'Correct the reported SQLite syntax, table, column, function, or bind error.',
|
|
46
|
+
SYQL6003_NONDETERMINISTIC_SQL: 'Replace nondeterministic SQL with values supplied through query inputs or stored columns.',
|
|
47
|
+
SYQL6004_TYPE_CONFLICT: 'Align the declared input type with the schema column and SQL expression types.',
|
|
48
|
+
SYQL6005_INVALID_SYNC_QUERY: 'Select stable row identity and preserve the scope coverage required for reactive sync.',
|
|
49
|
+
SYQL6006_INVALID_SORT: 'Use declared sort profiles with deterministic ORDER BY terms and a stable tie-breaker.',
|
|
50
|
+
SYQL6007_INVALID_LIMIT: 'Use the declared limit control and keep its default and maximum within the supported range.',
|
|
51
|
+
SYQL6008_INVALID_IDENTITY: 'Declare identity columns that are selected, non-null, and sufficient to identify each result row.',
|
|
52
|
+
SYQL7001_ENUMERATION_LIMIT: 'Use the auto or neutralize backend, reduce control combinations, or raise the explicit compiler limit.',
|
|
53
|
+
SYQL7002_INTERNAL_LOWERING: 'Report the query and diagnostic to the Syncular maintainers.',
|
|
54
|
+
SYQL9001_PROJECT_CONTEXT: 'Open the file under a project with a valid syncular.json, migrations, schema output, and query root.',
|
|
55
|
+
SYQL_RUNTIME_MISSING_REQUIRED_INPUT: 'Pass the required generated-query input.',
|
|
56
|
+
SYQL_RUNTIME_UNKNOWN_INPUT: 'Remove the unknown key from the generated-query input object.',
|
|
57
|
+
SYQL_RUNTIME_INVALID_INPUT: 'Pass a value that matches the generated input type and nullability.',
|
|
58
|
+
SYQL_RUNTIME_INVALID_GROUP: 'Pass either the complete generated group shape or omit the optional group.',
|
|
59
|
+
SYQL_RUNTIME_INVALID_SORT: 'Pass one of the sort profile names exported for the generated query.',
|
|
60
|
+
SYQL_RUNTIME_INVALID_LIMIT: 'Pass an integer within the generated query limit range.',
|
|
61
|
+
};
|
|
62
|
+
/** Generate a deterministic JSON-ready code-to-remedy catalog. */
|
|
63
|
+
export function generateSyqlDiagnosticCatalog() {
|
|
64
|
+
return Object.keys(SYQL_DIAGNOSTIC_REMEDIES)
|
|
65
|
+
.sort()
|
|
66
|
+
.map((code) => ({ code, remedy: SYQL_DIAGNOSTIC_REMEDIES[code] }));
|
|
67
|
+
}
|
|
68
|
+
/** Resolve a diagnostic remedy without requiring a narrowed code type. */
|
|
69
|
+
export function syqlDiagnosticRemedy(code) {
|
|
70
|
+
const remedies = SYQL_DIAGNOSTIC_REMEDIES;
|
|
71
|
+
return remedies[code];
|
|
72
|
+
}
|
package/dist/syql-lowering.js
CHANGED
|
@@ -309,6 +309,7 @@ function lowerStatement(validated, ir, db, naming, sql, sortProfile, activationM
|
|
|
309
309
|
...(activationMask === undefined ? {} : { activationMask }),
|
|
310
310
|
sql: namedSql,
|
|
311
311
|
positionalSql: analyzed.positionalSql,
|
|
312
|
+
relations: analyzed.relations,
|
|
312
313
|
binds: bindNames.map((name) => planBind(name, owners, conditionBinds, query.conditions, validated.limit, query)),
|
|
313
314
|
},
|
|
314
315
|
analysis: { ...analyzed, sourceSql: namedSql, sql: namedSql },
|
package/dist/syql-validator.js
CHANGED
|
@@ -303,7 +303,15 @@ class Validator {
|
|
|
303
303
|
const activeStructure = this.#inspect(activeSql, location);
|
|
304
304
|
this.#validateStatementShape(activeSql, activeStructure, logical.declaration, location);
|
|
305
305
|
this.#validateDeterminism(activeSql, logical.declaration.statement.span);
|
|
306
|
-
|
|
306
|
+
let refs;
|
|
307
|
+
try {
|
|
308
|
+
refs = scanTableRefs(activeSql, this.#ir);
|
|
309
|
+
}
|
|
310
|
+
catch (error) {
|
|
311
|
+
// Preserve SQLite's source-spanned diagnostics for invalid relations.
|
|
312
|
+
this.#validateSqlite(activeSql, logical, []);
|
|
313
|
+
this.#fail('SYQL6002_INVALID_SQL', logical.declaration.statement.span, error instanceof Error ? error.message : String(error));
|
|
314
|
+
}
|
|
307
315
|
this.#validatePortableProfile(activeSql, logical.declaration.statement.span, refs);
|
|
308
316
|
this.#validateSqlite(activeSql, logical, refs);
|
|
309
317
|
const bindSymbols = this.#bindSymbols(logical.declaration);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@syncular/typegen",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.16.1",
|
|
4
4
|
"description": "Syncular schema-to-TypeScript type generator and the `syncular` CLI",
|
|
5
5
|
"license": "Apache-2.0",
|
|
6
6
|
"author": "Benjamin Kniffler",
|
|
@@ -56,9 +56,9 @@
|
|
|
56
56
|
"!dist/**/*.test.d.ts"
|
|
57
57
|
],
|
|
58
58
|
"dependencies": {
|
|
59
|
-
"@syncular/core": "0.
|
|
59
|
+
"@syncular/core": "0.16.1"
|
|
60
60
|
},
|
|
61
61
|
"devDependencies": {
|
|
62
|
-
"@syncular/server": "0.
|
|
62
|
+
"@syncular/server": "0.16.1"
|
|
63
63
|
}
|
|
64
64
|
}
|
package/src/emit-queries-rust.ts
CHANGED
|
@@ -892,7 +892,7 @@ function emitSupport(clientCrate: string): string {
|
|
|
892
892
|
' ));',
|
|
893
893
|
' }',
|
|
894
894
|
' hex.as_bytes()',
|
|
895
|
-
' .
|
|
895
|
+
' .chunks(2)',
|
|
896
896
|
' .map(|pair| {',
|
|
897
897
|
' let pair = std::str::from_utf8(pair)',
|
|
898
898
|
' .map_err(|_| decode_error(query, column, "bytes", "non-ASCII $bytes envelope"))?;',
|
package/src/emit-queries.ts
CHANGED
|
@@ -314,15 +314,15 @@ function emitSyqlQuery(query: AnalyzedQuery, hash: string): string {
|
|
|
314
314
|
: `raw?: ${Params}`;
|
|
315
315
|
const validated = hasParams ? `${query.name}Validate(raw)` : undefined;
|
|
316
316
|
const statementType = hasParams
|
|
317
|
-
? `
|
|
318
|
-
: '
|
|
319
|
-
lines.push(`const ${query.name}Statements: ${statementType}[] = [`);
|
|
317
|
+
? `QueryRelationPlan & { bind: (params: ${Params}) => QueryValue[] }`
|
|
318
|
+
: 'QueryRelationPlan & { bind: () => QueryValue[] }';
|
|
319
|
+
lines.push(`const ${query.name}Statements: (${statementType})[] = [`);
|
|
320
320
|
for (const statement of metadata.plan.statements) {
|
|
321
321
|
const binds = statement.binds
|
|
322
322
|
.map((bind) => syqlBindExpr(query, bind, 'params'))
|
|
323
323
|
.join(', ');
|
|
324
324
|
lines.push(
|
|
325
|
-
` { sql: ${quote(statement.positionalSql)}, bind: (${hasParams ? 'params' : ''}) => [${binds}] },`,
|
|
325
|
+
` { sql: ${quote(statement.positionalSql)}, relations: ${JSON.stringify(statement.relations)}, bind: (${hasParams ? 'params' : ''}) => [${binds}] },`,
|
|
326
326
|
);
|
|
327
327
|
}
|
|
328
328
|
lines.push('];');
|
|
@@ -399,6 +399,7 @@ function emitSyqlQuery(query: AnalyzedQuery, hash: string): string {
|
|
|
399
399
|
);
|
|
400
400
|
}
|
|
401
401
|
lines.push(` tables: ${query.name}Tables,`);
|
|
402
|
+
lines.push(` relationPlans: ${query.name}Statements,`);
|
|
402
403
|
lines.push(
|
|
403
404
|
` resultColumns: [${query.columns.map((column) => `{ name: ${quote(column.langName)}, type: ${quote(column.type)}, nullable: ${column.nullable} }`).join(', ')}],`,
|
|
404
405
|
);
|
|
@@ -540,6 +541,9 @@ function emitQuery(query: AnalyzedQuery, hash: string): string {
|
|
|
540
541
|
lines.push(` sql: ${sqlConst},`);
|
|
541
542
|
lines.push(` mapRow: ${query.name}MapRow,`);
|
|
542
543
|
lines.push(` tables: ${query.name}Tables,`);
|
|
544
|
+
lines.push(
|
|
545
|
+
` relationPlans: [{ sql: ${sqlConst}, relations: ${JSON.stringify(query.relations)} }],`,
|
|
546
|
+
);
|
|
543
547
|
lines.push(
|
|
544
548
|
` resultColumns: [${query.columns.map((column) => `{ name: ${quote(column.langName)}, type: ${quote(column.type)}, nullable: ${column.nullable} }`).join(', ')}],`,
|
|
545
549
|
);
|
|
@@ -683,6 +687,11 @@ export function emitQueriesModule(
|
|
|
683
687
|
" * `@syncular/react`'s `useQuery`. `Row` is the projection row",
|
|
684
688
|
' * type; `Params` is `undefined` for a param-less query. `sqlFor`',
|
|
685
689
|
' * selects a checked revision-1 SYQL physical statement when needed. */',
|
|
690
|
+
'export interface QueryRelationPlan {',
|
|
691
|
+
' readonly sql: string;',
|
|
692
|
+
' readonly relations: readonly { readonly table: string; readonly start: number; readonly end: number; readonly alias?: string }[];',
|
|
693
|
+
'}',
|
|
694
|
+
'',
|
|
686
695
|
'export interface NamedQuery<Row, Params = undefined> {',
|
|
687
696
|
' readonly id: string;',
|
|
688
697
|
' readonly hasParams: boolean;',
|
|
@@ -690,6 +699,7 @@ export function emitQueriesModule(
|
|
|
690
699
|
' readonly mapRow: (row: Readonly<Record<string, unknown>>) => Row;',
|
|
691
700
|
' readonly tables: readonly string[];',
|
|
692
701
|
' readonly resultColumns: readonly QueryResultColumn[];',
|
|
702
|
+
' readonly relationPlans: readonly QueryRelationPlan[];',
|
|
693
703
|
' readonly bind: (params: Params) => readonly QueryValue[];',
|
|
694
704
|
' readonly sqlFor?: (params: Params) => string;',
|
|
695
705
|
' readonly dependencies: (params: Params) => readonly QueryDependency[];',
|
package/src/index.ts
CHANGED
package/src/lsp.ts
CHANGED
|
@@ -9,6 +9,7 @@ import type { IrDocument } from './ir';
|
|
|
9
9
|
import { MANIFEST_FILENAME, parseManifest } from './manifest';
|
|
10
10
|
import { lockedMigrationNames, readMigrationLock } from './migration-lock';
|
|
11
11
|
import type { QueryDb, QueryNamingOptions } from './query';
|
|
12
|
+
import { syqlDiagnosticRemedy } from './syql-diagnostics';
|
|
12
13
|
import { SyqlFrontendError, type SyqlSourceSpan } from './syql-lexer';
|
|
13
14
|
import type { SyqlLoweredQuery } from './syql-lowering';
|
|
14
15
|
import { lowerSyqlQuery } from './syql-lowering';
|
|
@@ -49,6 +50,7 @@ interface Diagnostic {
|
|
|
49
50
|
severity: number;
|
|
50
51
|
source: string;
|
|
51
52
|
code?: string;
|
|
53
|
+
data?: { readonly remedy: string };
|
|
52
54
|
message: string;
|
|
53
55
|
}
|
|
54
56
|
|
|
@@ -384,14 +386,25 @@ export class SyqlLanguageServer {
|
|
|
384
386
|
error instanceof SyqlFrontendError &&
|
|
385
387
|
resolve(error.sourceFile) === file
|
|
386
388
|
) {
|
|
389
|
+
const remedy = syqlDiagnosticRemedy(error.code);
|
|
387
390
|
return {
|
|
388
391
|
range: spanRange(text, error.span),
|
|
389
392
|
severity: 1,
|
|
390
393
|
source: 'syncular',
|
|
391
394
|
code: error.code,
|
|
395
|
+
...(remedy === undefined ? {} : { data: { remedy } }),
|
|
392
396
|
message: error.detail,
|
|
393
397
|
};
|
|
394
398
|
}
|
|
399
|
+
const projectContextError =
|
|
400
|
+
this.#context(pathToFileURL(file).href).kind === 'error';
|
|
401
|
+
const code =
|
|
402
|
+
error instanceof SyqlFrontendError
|
|
403
|
+
? error.code
|
|
404
|
+
: projectContextError
|
|
405
|
+
? 'SYQL9001_PROJECT_CONTEXT'
|
|
406
|
+
: undefined;
|
|
407
|
+
const remedy = code === undefined ? undefined : syqlDiagnosticRemedy(code);
|
|
395
408
|
return {
|
|
396
409
|
range: {
|
|
397
410
|
start: { line: 0, character: 0 },
|
|
@@ -402,11 +415,11 @@ export class SyqlLanguageServer {
|
|
|
402
415
|
},
|
|
403
416
|
severity: 1,
|
|
404
417
|
source: 'syncular',
|
|
405
|
-
...(
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
418
|
+
...(code === undefined ? {} : { code }),
|
|
419
|
+
...(remedy === undefined ? {} : { data: { remedy } }),
|
|
420
|
+
message: projectContextError
|
|
421
|
+
? `SYQL9001_PROJECT_CONTEXT: ${message}`
|
|
422
|
+
: message,
|
|
410
423
|
};
|
|
411
424
|
}
|
|
412
425
|
|
package/src/query-ir.ts
CHANGED
|
@@ -14,13 +14,14 @@ import type { AnalyzedQuery } from './query';
|
|
|
14
14
|
/** Serialize analyzed queries as the deterministic QueryIR JSON document. */
|
|
15
15
|
export function serializeQueryIr(queries: readonly AnalyzedQuery[]): string {
|
|
16
16
|
const doc = {
|
|
17
|
-
queryIrVersion:
|
|
17
|
+
queryIrVersion: 4,
|
|
18
18
|
queries: queries.map((query) => ({
|
|
19
19
|
name: query.name,
|
|
20
20
|
file: query.file,
|
|
21
21
|
sourceSql: query.sourceSql,
|
|
22
22
|
sql: query.sql,
|
|
23
23
|
positionalSql: query.positionalSql,
|
|
24
|
+
relations: query.relations,
|
|
24
25
|
params: query.params.map((param) => ({
|
|
25
26
|
name: param.name,
|
|
26
27
|
langName: param.langName,
|
|
@@ -101,6 +102,7 @@ export function serializeQueryIr(queries: readonly AnalyzedQuery[]): string {
|
|
|
101
102
|
: { activationMask: statement.activationMask }),
|
|
102
103
|
sql: statement.sql,
|
|
103
104
|
positionalSql: statement.positionalSql,
|
|
105
|
+
relations: statement.relations,
|
|
104
106
|
binds: statement.binds.map((bind) => ({ ...bind })),
|
|
105
107
|
})),
|
|
106
108
|
},
|