@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.
- package/README.md +38 -9
- package/dist/emit-queries.js +63 -5
- package/dist/emit.js +32 -0
- package/dist/generate.d.ts +4 -0
- package/dist/generate.js +18 -6
- package/dist/index.d.ts +5 -0
- package/dist/index.js +5 -0
- package/dist/manifest.d.ts +3 -0
- package/dist/manifest.js +8 -2
- package/dist/query-ir.js +24 -1
- package/dist/query.d.ts +30 -0
- package/dist/query.js +139 -8
- package/dist/syql-ast.d.ts +95 -0
- package/dist/syql-ast.js +138 -0
- package/dist/syql-lexer.d.ts +40 -0
- package/dist/syql-lexer.js +367 -0
- package/dist/syql-modules.d.ts +21 -0
- package/dist/syql-modules.js +129 -0
- package/dist/syql-parser.d.ts +132 -0
- package/dist/syql-parser.js +604 -0
- package/dist/syql-template-parser.d.ts +58 -0
- package/dist/syql-template-parser.js +350 -0
- package/dist/syql.d.ts +19 -0
- package/dist/syql.js +196 -0
- package/package.json +3 -3
- package/src/emit-queries.ts +86 -5
- package/src/emit.ts +38 -0
- package/src/generate.ts +32 -5
- package/src/index.ts +5 -0
- package/src/manifest.ts +11 -2
- package/src/query-ir.ts +24 -1
- package/src/query.ts +207 -8
- package/src/syql-ast.ts +277 -0
- package/src/syql-lexer.ts +514 -0
- package/src/syql-modules.ts +217 -0
- package/src/syql-parser.ts +1021 -0
- package/src/syql-template-parser.ts +582 -0
- package/src/syql.ts +300 -1
package/src/emit-queries.ts
CHANGED
|
@@ -71,7 +71,27 @@ function bindExpr(
|
|
|
71
71
|
return isOptional(param) ? `${access}.${key} ?? null` : `params.${key}`;
|
|
72
72
|
}
|
|
73
73
|
|
|
74
|
-
function
|
|
74
|
+
function reactiveParam(query: AnalyzedQuery, name: string): string {
|
|
75
|
+
const param = query.params.find((candidate) => candidate.name === name);
|
|
76
|
+
if (param === undefined) throw new Error(`unknown reactive param ${name}`);
|
|
77
|
+
return `String(params.${propertyKey(param.langName)})`;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
function scopeKeyExpression(
|
|
81
|
+
query: AnalyzedQuery,
|
|
82
|
+
pattern: string,
|
|
83
|
+
variable: string,
|
|
84
|
+
param: string,
|
|
85
|
+
): string {
|
|
86
|
+
const marker = `{${variable}}`;
|
|
87
|
+
const index = pattern.indexOf(marker);
|
|
88
|
+
if (index < 0) throw new Error(`scope pattern ${pattern} lacks ${marker}`);
|
|
89
|
+
const prefix = pattern.slice(0, index);
|
|
90
|
+
const suffix = pattern.slice(index + marker.length);
|
|
91
|
+
return `${quote(prefix)} + ${reactiveParam(query, param)} + ${quote(suffix)}`;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
function emitQuery(query: AnalyzedQuery, hash: string): string {
|
|
75
95
|
const Row = `${pascalCase(query.name)}Row`;
|
|
76
96
|
const Params = `${pascalCase(query.name)}Params`;
|
|
77
97
|
const lines: string[] = [];
|
|
@@ -130,7 +150,7 @@ function emitQuery(query: AnalyzedQuery): string {
|
|
|
130
150
|
|
|
131
151
|
// Tables dependency set (for useRawSql {tables} / useQuery).
|
|
132
152
|
lines.push(
|
|
133
|
-
`/** Tables ${quote(query.name)} reads
|
|
153
|
+
`/** Tables ${quote(query.name)} reads (compatibility/export surface). */`,
|
|
134
154
|
);
|
|
135
155
|
lines.push(
|
|
136
156
|
`export const ${query.name}Tables = [${query.tables.map(quote).join(', ')}] as const;`,
|
|
@@ -253,12 +273,14 @@ function emitQuery(query: AnalyzedQuery): string {
|
|
|
253
273
|
// set, and a `bind(params)` → positional array (always paired with the
|
|
254
274
|
// sqlFor selection). Typed by the query's own Row/Params.
|
|
255
275
|
lines.push(
|
|
256
|
-
`/**
|
|
276
|
+
`/** Revisioned reactive descriptor for \`useQuery(${query.name}Query${hasParams ? ', params' : ''})\`. */`,
|
|
257
277
|
);
|
|
258
278
|
const paramsTypeArg = hasParams ? Params : 'undefined';
|
|
259
279
|
lines.push(
|
|
260
280
|
`export const ${query.name}Query: NamedQuery<${Row}, ${paramsTypeArg}> = {`,
|
|
261
281
|
);
|
|
282
|
+
lines.push(` id: ${quote(`${hash}/${query.name}`)},`);
|
|
283
|
+
lines.push(` hasParams: ${hasParams},`);
|
|
262
284
|
lines.push(` sql: ${sqlConst},`);
|
|
263
285
|
if (query.variants !== undefined) {
|
|
264
286
|
lines.push(` sqlFor: (params: ${Params}) => ${selectFn}(params).sql,`);
|
|
@@ -266,6 +288,40 @@ function emitQuery(query: AnalyzedQuery): string {
|
|
|
266
288
|
lines.push(` sqlFor: (params: ${Params}) => ${composeFn}(params),`);
|
|
267
289
|
}
|
|
268
290
|
lines.push(` tables: ${query.name}Tables,`);
|
|
291
|
+
const reactiveUsesParams = query.reactive.dependencies.some((dependency) =>
|
|
292
|
+
dependency.scopes.some((scope) => scope.params.length > 0),
|
|
293
|
+
);
|
|
294
|
+
lines.push(` dependencies: (${reactiveUsesParams ? 'params' : ''}) => [`);
|
|
295
|
+
for (const dependency of query.reactive.dependencies) {
|
|
296
|
+
const keys = dependency.scopes.flatMap((scope) =>
|
|
297
|
+
scope.params.map((param) =>
|
|
298
|
+
scopeKeyExpression(query, scope.pattern, scope.variable, param),
|
|
299
|
+
),
|
|
300
|
+
);
|
|
301
|
+
lines.push(
|
|
302
|
+
` { table: ${quote(dependency.table)}${keys.length > 0 ? `, scopeKeys: [${keys.join(', ')}]` : ''} },`,
|
|
303
|
+
);
|
|
304
|
+
}
|
|
305
|
+
lines.push(' ],');
|
|
306
|
+
const coverageUsesParams = query.reactive.coverage.some(
|
|
307
|
+
(coverage) =>
|
|
308
|
+
coverage.units.length > 0 ||
|
|
309
|
+
coverage.fixedScopes.some((scope) => scope.params.length > 0),
|
|
310
|
+
);
|
|
311
|
+
lines.push(` coverage: (${coverageUsesParams ? 'params' : ''}) => [`);
|
|
312
|
+
for (const coverage of query.reactive.coverage) {
|
|
313
|
+
const fixed = coverage.fixedScopes
|
|
314
|
+
.map(
|
|
315
|
+
(scope) =>
|
|
316
|
+
`${propertyKey(scope.variable)}: [${scope.params.map((param) => reactiveParam(query, param)).join(', ')}]`,
|
|
317
|
+
)
|
|
318
|
+
.join(', ');
|
|
319
|
+
const fixedPart = fixed.length > 0 ? `, fixedScopes: { ${fixed} }` : '';
|
|
320
|
+
lines.push(
|
|
321
|
+
` { base: { table: ${quote(coverage.table)}, variable: ${quote(coverage.variable)}${fixedPart} }, units: [${coverage.units.map((param) => reactiveParam(query, param)).join(', ')}] },`,
|
|
322
|
+
);
|
|
323
|
+
}
|
|
324
|
+
lines.push(' ],');
|
|
269
325
|
if (query.variants !== undefined) {
|
|
270
326
|
lines.push(` bind: (params: ${Params}) => ${selectFn}(params).bind,`);
|
|
271
327
|
} else if (hasParams) {
|
|
@@ -273,6 +329,11 @@ function emitQuery(query: AnalyzedQuery): string {
|
|
|
273
329
|
} else {
|
|
274
330
|
lines.push(' bind: () => [],');
|
|
275
331
|
}
|
|
332
|
+
if (query.reactive.rowKey !== undefined) {
|
|
333
|
+
lines.push(
|
|
334
|
+
` rowKey: (row) => [${query.reactive.rowKey.map((key) => `row.${propertyKey(key)}`).join(', ')}],`,
|
|
335
|
+
);
|
|
336
|
+
}
|
|
276
337
|
lines.push('};');
|
|
277
338
|
return lines.join('\n');
|
|
278
339
|
}
|
|
@@ -311,24 +372,44 @@ export function emitQueriesModule(
|
|
|
311
372
|
' ): unknown[] | Promise<unknown[]>;',
|
|
312
373
|
'}',
|
|
313
374
|
'',
|
|
314
|
-
'/** A named-query descriptor —
|
|
375
|
+
'/** A named-query descriptor — checked SQL plus revisioned reactive metadata and a',
|
|
315
376
|
' * `bind(params)` → positional args. Consumed by',
|
|
316
377
|
" * `@syncular/react`'s `useQuery`. `Row` is the projection row",
|
|
317
378
|
' * type; `Params` is `undefined` for a param-less query. `sqlFor`',
|
|
318
379
|
' * (present only with an orderBy knob) composes the statement from a',
|
|
319
380
|
' * generate-time-checked column allowlist. */',
|
|
320
381
|
'export interface NamedQuery<Row, Params = undefined> {',
|
|
382
|
+
' readonly id: string;',
|
|
383
|
+
' readonly hasParams: boolean;',
|
|
321
384
|
' readonly sql: string;',
|
|
322
385
|
' readonly tables: readonly string[];',
|
|
323
386
|
' readonly bind: (params: Params) => readonly QueryValue[];',
|
|
324
387
|
' readonly sqlFor?: (params: Params) => string;',
|
|
388
|
+
' readonly dependencies: (params: Params) => readonly QueryDependency[];',
|
|
389
|
+
' readonly coverage: (params: Params) => readonly WindowCoverage[];',
|
|
390
|
+
' readonly rowKey?: (row: Row) => readonly QueryValue[];',
|
|
325
391
|
' /** Phantom — carries the Row type for `useQuery` inference. */',
|
|
326
392
|
' readonly __row?: Row;',
|
|
327
393
|
'}',
|
|
394
|
+
'',
|
|
395
|
+
'export interface QueryDependency {',
|
|
396
|
+
' readonly table: string;',
|
|
397
|
+
' readonly scopeKeys?: readonly string[];',
|
|
398
|
+
'}',
|
|
399
|
+
'',
|
|
400
|
+
'export interface WindowCoverage {',
|
|
401
|
+
' readonly base: {',
|
|
402
|
+
' readonly table: string;',
|
|
403
|
+
' readonly variable: string;',
|
|
404
|
+
' readonly fixedScopes?: Readonly<Record<string, readonly string[]>>;',
|
|
405
|
+
' readonly params?: string;',
|
|
406
|
+
' };',
|
|
407
|
+
' readonly units: readonly string[];',
|
|
408
|
+
'}',
|
|
328
409
|
].join('\n'),
|
|
329
410
|
);
|
|
330
411
|
for (const query of queries) {
|
|
331
|
-
parts.push(emitQuery(query));
|
|
412
|
+
parts.push(emitQuery(query, hash));
|
|
332
413
|
}
|
|
333
414
|
return `${parts.join('\n\n')}\n`;
|
|
334
415
|
}
|
package/src/emit.ts
CHANGED
|
@@ -163,6 +163,26 @@ function emitRowInterfaces(table: IrTable, naming: NamingMode): string {
|
|
|
163
163
|
return lines.join('\n');
|
|
164
164
|
}
|
|
165
165
|
|
|
166
|
+
function emitTableDescriptor(table: IrTable, naming: NamingMode): string {
|
|
167
|
+
const type = pascalCase(table.name);
|
|
168
|
+
const primary = table.columns.find(
|
|
169
|
+
(column) => column.name === table.primaryKey,
|
|
170
|
+
);
|
|
171
|
+
if (primary === undefined)
|
|
172
|
+
throw new Error(`table ${table.name} lacks its primary key`);
|
|
173
|
+
const field =
|
|
174
|
+
naming === 'camel' ? snakeToCamel(table.primaryKey) : table.primaryKey;
|
|
175
|
+
const constant = `${naming === 'camel' ? snakeToCamel(table.name) : table.name}Table`;
|
|
176
|
+
return [
|
|
177
|
+
`/** Typed mutation/resource descriptor for ${quote(table.name)}. */`,
|
|
178
|
+
`export const ${constant}: SyncTable<${type}Row, ${type}Insert, ${type}Update, ${appTsType(primary)}> = {`,
|
|
179
|
+
` name: ${quote(table.name)},`,
|
|
180
|
+
` primaryKey: ${quote(field)},`,
|
|
181
|
+
` physicalPrimaryKey: ${quote(table.primaryKey)},`,
|
|
182
|
+
'};',
|
|
183
|
+
].join('\n');
|
|
184
|
+
}
|
|
185
|
+
|
|
166
186
|
function emitSubscription(sub: IrSubscription): string {
|
|
167
187
|
const params: string[] = [];
|
|
168
188
|
for (const scope of sub.scopes) {
|
|
@@ -218,9 +238,27 @@ export function emitModule(
|
|
|
218
238
|
`// irHash: ${hash}`,
|
|
219
239
|
].join('\n'),
|
|
220
240
|
);
|
|
241
|
+
parts.push(
|
|
242
|
+
[
|
|
243
|
+
'/** Structural descriptor consumed by renderer bindings; phantom type',
|
|
244
|
+
' * fields make row/insert/update/id inference available without imports. */',
|
|
245
|
+
'export interface SyncTable<Row, Insert, Update, Id> {',
|
|
246
|
+
' readonly name: string;',
|
|
247
|
+
' /** Language-facing key used in generated row and mutation types. */',
|
|
248
|
+
' readonly primaryKey: keyof Row & string;',
|
|
249
|
+
' /** Physical SQLite/wire primary-key column. */',
|
|
250
|
+
' readonly physicalPrimaryKey: string;',
|
|
251
|
+
' readonly __row?: Row;',
|
|
252
|
+
' readonly __insert?: Insert;',
|
|
253
|
+
' readonly __update?: Update;',
|
|
254
|
+
' readonly __id?: Id;',
|
|
255
|
+
'}',
|
|
256
|
+
].join('\n'),
|
|
257
|
+
);
|
|
221
258
|
parts.push(emitSchema(ir));
|
|
222
259
|
for (const table of ir.tables) {
|
|
223
260
|
parts.push(emitRowInterfaces(table, naming));
|
|
261
|
+
parts.push(emitTableDescriptor(table, naming));
|
|
224
262
|
}
|
|
225
263
|
for (const sub of ir.subscriptions) {
|
|
226
264
|
parts.push(emitSubscription(sub));
|
package/src/generate.ts
CHANGED
|
@@ -48,6 +48,7 @@ import {
|
|
|
48
48
|
type QueryNamingOptions,
|
|
49
49
|
synthesizeDdl,
|
|
50
50
|
} from './query';
|
|
51
|
+
import { serializeQueryIr } from './query-ir';
|
|
51
52
|
import { applyMigrationSql, type ParsedTable } from './sql';
|
|
52
53
|
import { analyzeSyqlFile } from './syql';
|
|
53
54
|
|
|
@@ -463,6 +464,10 @@ export interface GenerateResult {
|
|
|
463
464
|
/** Serialized IR document (the exact bytes of the `.ir.json` output). */
|
|
464
465
|
readonly irJson: string;
|
|
465
466
|
readonly hash: string;
|
|
467
|
+
/** Serialized QueryIR and its content hash. Query descriptors use this hash
|
|
468
|
+
* so a SQL-only edit cannot reuse a stale reactive cache entry. */
|
|
469
|
+
readonly queryIrJson: string;
|
|
470
|
+
readonly queryHash: string;
|
|
466
471
|
/** Generated TS module source (the exact bytes of the `.ts` output). */
|
|
467
472
|
readonly module: string;
|
|
468
473
|
readonly irPath: string;
|
|
@@ -525,12 +530,19 @@ export function generate(manifestDir: string): GenerateResult {
|
|
|
525
530
|
{ path: modulePath, content: module },
|
|
526
531
|
];
|
|
527
532
|
// Opt-in native emitters — each present only when the manifest requests it.
|
|
528
|
-
const {
|
|
533
|
+
const {
|
|
534
|
+
queryIr: queryIrPath,
|
|
535
|
+
queries: tsQueriesPath,
|
|
536
|
+
swift,
|
|
537
|
+
kotlin,
|
|
538
|
+
dart,
|
|
539
|
+
} = manifest.output;
|
|
529
540
|
|
|
530
541
|
// Named queries: analyzed once (SELECT-only, typed against the IR via
|
|
531
542
|
// SQLite), then emitted per-language into its OWN file so schema-only
|
|
532
543
|
// consumers never churn. Only analyzed when SOME query output is requested.
|
|
533
544
|
const wantsQueries =
|
|
545
|
+
queryIrPath !== undefined ||
|
|
534
546
|
tsQueriesPath !== undefined ||
|
|
535
547
|
swift?.queriesPath !== undefined ||
|
|
536
548
|
kotlin?.queriesPath !== undefined ||
|
|
@@ -549,10 +561,19 @@ export function generate(manifestDir: string): GenerateResult {
|
|
|
549
561
|
);
|
|
550
562
|
}
|
|
551
563
|
|
|
564
|
+
const queryIrJson = serializeQueryIr(analyzedQueries);
|
|
565
|
+
const queryHash = irHash(queryIrJson);
|
|
566
|
+
if (queryIrPath !== undefined) {
|
|
567
|
+
outputs.push({
|
|
568
|
+
path: resolve(manifestDir, queryIrPath),
|
|
569
|
+
content: queryIrJson,
|
|
570
|
+
});
|
|
571
|
+
}
|
|
572
|
+
|
|
552
573
|
if (tsQueriesPath !== undefined) {
|
|
553
574
|
outputs.push({
|
|
554
575
|
path: resolve(manifestDir, tsQueriesPath),
|
|
555
|
-
content: emitQueriesModule(analyzedQueries,
|
|
576
|
+
content: emitQueriesModule(analyzedQueries, queryHash, ir.irVersion),
|
|
556
577
|
});
|
|
557
578
|
}
|
|
558
579
|
if (swift !== undefined) {
|
|
@@ -565,7 +586,7 @@ export function generate(manifestDir: string): GenerateResult {
|
|
|
565
586
|
path: resolve(manifestDir, swift.queriesPath),
|
|
566
587
|
content: emitQueriesSwiftModule(
|
|
567
588
|
analyzedQueries,
|
|
568
|
-
|
|
589
|
+
queryHash,
|
|
569
590
|
ir.irVersion,
|
|
570
591
|
swift.enumName,
|
|
571
592
|
),
|
|
@@ -582,7 +603,7 @@ export function generate(manifestDir: string): GenerateResult {
|
|
|
582
603
|
path: resolve(manifestDir, kotlin.queriesPath),
|
|
583
604
|
content: emitQueriesKotlinModule(
|
|
584
605
|
analyzedQueries,
|
|
585
|
-
|
|
606
|
+
queryHash,
|
|
586
607
|
ir.irVersion,
|
|
587
608
|
kotlin.package,
|
|
588
609
|
kotlin.objectName,
|
|
@@ -598,7 +619,11 @@ export function generate(manifestDir: string): GenerateResult {
|
|
|
598
619
|
if (dart.queriesPath !== undefined) {
|
|
599
620
|
outputs.push({
|
|
600
621
|
path: resolve(manifestDir, dart.queriesPath),
|
|
601
|
-
content: emitQueriesDartModule(
|
|
622
|
+
content: emitQueriesDartModule(
|
|
623
|
+
analyzedQueries,
|
|
624
|
+
queryHash,
|
|
625
|
+
ir.irVersion,
|
|
626
|
+
),
|
|
602
627
|
});
|
|
603
628
|
}
|
|
604
629
|
}
|
|
@@ -606,6 +631,8 @@ export function generate(manifestDir: string): GenerateResult {
|
|
|
606
631
|
ir,
|
|
607
632
|
irJson,
|
|
608
633
|
hash,
|
|
634
|
+
queryIrJson,
|
|
635
|
+
queryHash,
|
|
609
636
|
module,
|
|
610
637
|
irPath,
|
|
611
638
|
modulePath,
|
package/src/index.ts
CHANGED
|
@@ -23,3 +23,8 @@ export * from './query';
|
|
|
23
23
|
export * from './query-ir';
|
|
24
24
|
export * from './sql';
|
|
25
25
|
export * from './syql';
|
|
26
|
+
export * from './syql-ast';
|
|
27
|
+
export * from './syql-lexer';
|
|
28
|
+
export * from './syql-modules';
|
|
29
|
+
export * from './syql-parser';
|
|
30
|
+
export * from './syql-template-parser';
|
package/src/manifest.ts
CHANGED
|
@@ -26,7 +26,8 @@
|
|
|
26
26
|
* final entry must cover the final migration.
|
|
27
27
|
* - Subscription scope values are literals or whole-value `{param}`
|
|
28
28
|
* placeholders (partial templates are unsupported).
|
|
29
|
-
* - `output.ir` / `output.module` are the always-emitted TS defaults
|
|
29
|
+
* - `output.ir` / `output.module` are the always-emitted TS defaults;
|
|
30
|
+
* `output.queryIr` opts into the deterministic analyzed-query document.
|
|
30
31
|
* `output.swift` / `output.kotlin` / `output.dart` are OPT-IN native
|
|
31
32
|
* emitters — a bare string is the output path; an object carries
|
|
32
33
|
* language-appropriate options (Kotlin `package`/`objectName`, Swift
|
|
@@ -105,6 +106,9 @@ export interface DartOutput {
|
|
|
105
106
|
export interface ManifestOutput {
|
|
106
107
|
readonly ir: string;
|
|
107
108
|
readonly module: string;
|
|
109
|
+
/** Opt-in deterministic QueryIR JSON. This is also the source of the
|
|
110
|
+
* generated query-descriptor hash, so SQL-only changes rotate cache IDs. */
|
|
111
|
+
readonly queryIr?: string;
|
|
108
112
|
/** Opt-in TS named-queries output path (a sibling `.ts` file). Undefined →
|
|
109
113
|
* named queries are not generated for TS. */
|
|
110
114
|
readonly queries?: string;
|
|
@@ -400,6 +404,7 @@ export function parseManifest(raw: unknown): Manifest {
|
|
|
400
404
|
}
|
|
401
405
|
let ir = './syncular.ir.json';
|
|
402
406
|
let module = './syncular.generated.ts';
|
|
407
|
+
let queryIr: string | undefined;
|
|
403
408
|
let queriesOut: string | undefined;
|
|
404
409
|
let swift: SwiftOutput | undefined;
|
|
405
410
|
let kotlin: KotlinOutput | undefined;
|
|
@@ -408,13 +413,16 @@ export function parseManifest(raw: unknown): Manifest {
|
|
|
408
413
|
const output = asObject(obj.output, 'output');
|
|
409
414
|
rejectUnknownKeys(
|
|
410
415
|
output,
|
|
411
|
-
['ir', 'module', 'queries', 'swift', 'kotlin', 'dart'],
|
|
416
|
+
['ir', 'module', 'queryIr', 'queries', 'swift', 'kotlin', 'dart'],
|
|
412
417
|
'output',
|
|
413
418
|
);
|
|
414
419
|
if (output.ir !== undefined) ir = asString(output.ir, 'output.ir');
|
|
415
420
|
if (output.module !== undefined) {
|
|
416
421
|
module = asString(output.module, 'output.module');
|
|
417
422
|
}
|
|
423
|
+
if (output.queryIr !== undefined) {
|
|
424
|
+
queryIr = asString(output.queryIr, 'output.queryIr');
|
|
425
|
+
}
|
|
418
426
|
if (output.queries !== undefined) {
|
|
419
427
|
queriesOut = asString(output.queries, 'output.queries');
|
|
420
428
|
}
|
|
@@ -427,6 +435,7 @@ export function parseManifest(raw: unknown): Manifest {
|
|
|
427
435
|
const outputSpec: ManifestOutput = {
|
|
428
436
|
ir,
|
|
429
437
|
module,
|
|
438
|
+
...(queryIr !== undefined ? { queryIr } : {}),
|
|
430
439
|
...(queriesOut !== undefined ? { queries: queriesOut } : {}),
|
|
431
440
|
...(swift !== undefined ? { swift } : {}),
|
|
432
441
|
...(kotlin !== undefined ? { kotlin } : {}),
|
package/src/query-ir.ts
CHANGED
|
@@ -14,7 +14,7 @@ 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: 2,
|
|
18
18
|
queries: queries.map((query) => ({
|
|
19
19
|
name: query.name,
|
|
20
20
|
file: query.file,
|
|
@@ -37,8 +37,31 @@ export function serializeQueryIr(queries: readonly AnalyzedQuery[]): string {
|
|
|
37
37
|
type: column.type,
|
|
38
38
|
nullable: column.nullable,
|
|
39
39
|
fidelity: column.fidelity,
|
|
40
|
+
...(column.origin !== undefined ? { origin: column.origin } : {}),
|
|
40
41
|
})),
|
|
41
42
|
tables: query.tables,
|
|
43
|
+
reactive: {
|
|
44
|
+
dependencies: query.reactive.dependencies.map((dependency) => ({
|
|
45
|
+
table: dependency.table,
|
|
46
|
+
scopes: dependency.scopes.map((scope) => ({
|
|
47
|
+
variable: scope.variable,
|
|
48
|
+
pattern: scope.pattern,
|
|
49
|
+
params: scope.params,
|
|
50
|
+
})),
|
|
51
|
+
})),
|
|
52
|
+
coverage: query.reactive.coverage.map((coverage) => ({
|
|
53
|
+
table: coverage.table,
|
|
54
|
+
variable: coverage.variable,
|
|
55
|
+
units: coverage.units,
|
|
56
|
+
fixedScopes: coverage.fixedScopes.map((scope) => ({
|
|
57
|
+
variable: scope.variable,
|
|
58
|
+
params: scope.params,
|
|
59
|
+
})),
|
|
60
|
+
})),
|
|
61
|
+
...(query.reactive.rowKey !== undefined
|
|
62
|
+
? { rowKey: query.reactive.rowKey }
|
|
63
|
+
: {}),
|
|
64
|
+
},
|
|
42
65
|
// §6 knob metadata — emitted only when declared.
|
|
43
66
|
...(query.orderBy !== undefined
|
|
44
67
|
? {
|