@syncular/typegen 0.6.0 → 0.8.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/src/syql-ast.ts CHANGED
@@ -33,16 +33,8 @@ export type SyqlSemanticTemplateNode =
33
33
  | {
34
34
  readonly kind: 'when';
35
35
  readonly controls: readonly string[];
36
+ readonly explicitPresence: readonly boolean[];
36
37
  readonly body: readonly SyqlSemanticTemplateNode[];
37
- }
38
- | {
39
- readonly kind: 'scope' | 'cover';
40
- readonly bindings: readonly {
41
- readonly qualifier: string;
42
- readonly column: string;
43
- readonly operator: 'equal' | 'in';
44
- readonly values: readonly string[];
45
- }[];
46
38
  };
47
39
 
48
40
  export interface SyqlSemanticTemplate {
@@ -56,11 +48,13 @@ export type SyqlSemanticQueryParameter =
56
48
  readonly name: string;
57
49
  readonly optional: boolean;
58
50
  readonly type?: SyqlSemanticType;
51
+ readonly default?: false;
59
52
  }
60
53
  | {
61
- readonly kind: 'switch';
54
+ readonly kind: 'range';
62
55
  readonly name: string;
63
- readonly optional: true;
56
+ readonly optional: boolean;
57
+ readonly type?: SyqlSemanticType;
64
58
  }
65
59
  | {
66
60
  readonly kind: 'group';
@@ -85,8 +79,13 @@ export type SyqlSemanticDeclaration =
85
79
  | {
86
80
  readonly kind: 'query';
87
81
  readonly name: string;
82
+ readonly sync: boolean;
83
+ readonly syncBy?: {
84
+ readonly qualifier: string;
85
+ readonly column: string;
86
+ };
88
87
  readonly parameters: readonly SyqlSemanticQueryParameter[];
89
- readonly sql: SyqlSemanticTemplate;
88
+ readonly statement: SyqlSemanticTemplate;
90
89
  readonly sort?: {
91
90
  readonly control: string;
92
91
  readonly defaultProfile: string;
@@ -95,12 +94,11 @@ export type SyqlSemanticDeclaration =
95
94
  readonly order: SyqlSemanticTemplate;
96
95
  }[];
97
96
  };
98
- readonly page?: {
97
+ readonly limit?: {
99
98
  readonly control: string;
100
99
  readonly defaultSize: number;
101
100
  readonly maxSize: number;
102
101
  };
103
- readonly identity?: readonly string[];
104
102
  };
105
103
 
106
104
  export interface SyqlSemanticFile {
@@ -135,8 +133,14 @@ function semanticMember(member: SyqlGroupMember): {
135
133
  function semanticParameter(
136
134
  parameter: SyqlQueryParameter,
137
135
  ): SyqlSemanticQueryParameter {
138
- if (parameter.kind === 'switch') {
139
- return { kind: 'switch', name: parameter.name, optional: true };
136
+ if (parameter.kind === 'range') {
137
+ const type = semanticType(parameter.type);
138
+ return {
139
+ kind: 'range',
140
+ name: parameter.name,
141
+ optional: parameter.optional,
142
+ ...(type === undefined ? {} : { type }),
143
+ };
140
144
  }
141
145
  if (parameter.kind === 'group') {
142
146
  return {
@@ -152,6 +156,7 @@ function semanticParameter(
152
156
  name: parameter.name,
153
157
  optional: parameter.optional,
154
158
  ...(type === undefined ? {} : { type }),
159
+ ...(parameter.default === undefined ? {} : { default: parameter.default }),
155
160
  };
156
161
  }
157
162
 
@@ -181,18 +186,11 @@ function semanticNode(
181
186
  return {
182
187
  kind: 'when',
183
188
  controls: node.controls,
189
+ explicitPresence: node.explicitPresence,
184
190
  body: semanticNodes(node.body.nodes),
185
191
  };
186
192
  }
187
- return {
188
- kind: node.kind,
189
- bindings: node.bindings.map((binding) => ({
190
- qualifier: binding.column.qualifier,
191
- column: binding.column.name,
192
- operator: binding.operator,
193
- values: binding.values.map((value) => value.name),
194
- })),
195
- };
193
+ return undefined;
196
194
  }
197
195
 
198
196
  function semanticNodes(
@@ -231,8 +229,17 @@ function semanticDeclaration(
231
229
  return {
232
230
  kind: 'query',
233
231
  name: declaration.name,
232
+ sync: declaration.sync,
233
+ ...(declaration.syncBy === undefined
234
+ ? {}
235
+ : {
236
+ syncBy: {
237
+ qualifier: declaration.syncBy.qualifier,
238
+ column: declaration.syncBy.column,
239
+ },
240
+ }),
234
241
  parameters: declaration.parameters.map(semanticParameter),
235
- sql: semanticTemplate(declaration.sql.body),
242
+ statement: semanticTemplate(declaration.statement),
236
243
  ...(declaration.sort === undefined
237
244
  ? {}
238
245
  : {
@@ -245,18 +252,15 @@ function semanticDeclaration(
245
252
  })),
246
253
  },
247
254
  }),
248
- ...(declaration.page === undefined
255
+ ...(declaration.limit === undefined
249
256
  ? {}
250
257
  : {
251
- page: {
252
- control: declaration.page.control,
253
- defaultSize: declaration.page.defaultSize,
254
- maxSize: declaration.page.maxSize,
258
+ limit: {
259
+ control: declaration.limit.control,
260
+ defaultSize: declaration.limit.defaultSize,
261
+ maxSize: declaration.limit.maxSize,
255
262
  },
256
263
  }),
257
- ...(declaration.identity === undefined
258
- ? {}
259
- : { identity: declaration.identity.fields }),
260
264
  };
261
265
  }
262
266
 
@@ -18,10 +18,10 @@ import {
18
18
  import { lexSyqlSqlSource } from './syql-lexer';
19
19
  import type {
20
20
  SyqlLogicalQuery,
21
- SyqlLogicalReactiveNode,
22
21
  SyqlLogicalTemplateNode,
23
22
  SyqlLogicalWhenNode,
24
23
  } from './syql-semantics';
24
+ import { syqlRangeEndBind, syqlRangeStartBind } from './syql-semantics';
25
25
  import type { SyqlValidatedQuery } from './syql-validator';
26
26
 
27
27
  export type SyqlLoweringErrorCode =
@@ -50,7 +50,7 @@ const DEFAULT_NAMING: QueryNamingOptions = {
50
50
  backend: 'auto',
51
51
  };
52
52
  const DEFAULT_ENUMERATION_LIMIT = 256;
53
- const PAGE_BIND = '__syqlPageSize';
53
+ const LIMIT_BIND = '__syqlLimit';
54
54
 
55
55
  interface ValueOwner {
56
56
  readonly kind: 'value' | 'group-member';
@@ -70,17 +70,6 @@ function fail(
70
70
  );
71
71
  }
72
72
 
73
- function renderDirective(node: SyqlLogicalReactiveNode): string {
74
- const predicates = node.directive.bindings.map((binding) => {
75
- const column = `${binding.column.qualifier}.${binding.column.name}`;
76
- const values = binding.values.map((value) => `:${value.name}`);
77
- return binding.operator === 'equal'
78
- ? `${column} = ${values[0]}`
79
- : `${column} in (${values.join(', ')})`;
80
- });
81
- return `(${predicates.join(' and ')})`;
82
- }
83
-
84
73
  function renderTemplate(
85
74
  nodes: readonly SyqlLogicalTemplateNode[],
86
75
  conditions: ReadonlyMap<SyqlLogicalWhenNode, number>,
@@ -98,9 +87,6 @@ function renderTemplate(
98
87
  if (node.kind === 'predicate') {
99
88
  return `(${renderTemplate(node.body, conditions, mode)})`;
100
89
  }
101
- if (node.kind === 'scope' || node.kind === 'cover') {
102
- return renderDirective(node);
103
- }
104
90
  if (node.kind !== 'when') {
105
91
  throw new Error('revision-1 lowering found an unknown logical node');
106
92
  }
@@ -148,7 +134,7 @@ function composeControls(
148
134
  sql: string,
149
135
  file: string,
150
136
  sortSql: string | undefined,
151
- page: SyqlValidatedQuery['page'],
137
+ limit: SyqlValidatedQuery['limit'],
152
138
  ): string {
153
139
  let out = sql.trim();
154
140
  if (sortSql !== undefined) {
@@ -157,10 +143,10 @@ function composeControls(
157
143
  .slice(insertion)
158
144
  .trimStart()}`.trim();
159
145
  }
160
- if (page !== undefined) {
146
+ if (limit !== undefined) {
161
147
  // `coalesce` gives the generator's metadata-only execution a valid value;
162
148
  // runtimes still validate and bind the effective size before execution.
163
- out = `${out.trimEnd()} limit min(coalesce(:${PAGE_BIND}, ${page.defaultSize}), ${page.maxSize})`;
149
+ out = `${out.trimEnd()} limit min(coalesce(:${LIMIT_BIND}, ${limit.defaultSize}), ${limit.maxSize})`;
164
150
  }
165
151
  return out;
166
152
  }
@@ -179,7 +165,7 @@ function headersFor(
179
165
  names: readonly string[],
180
166
  valueOwners: ReadonlyMap<string, ValueOwner>,
181
167
  conditionBinds: ReadonlyMap<string, number>,
182
- page: SyqlValidatedQuery['page'],
168
+ limit: SyqlValidatedQuery['limit'],
183
169
  query: SyqlLogicalQuery,
184
170
  ): string {
185
171
  return names
@@ -187,7 +173,7 @@ function headersFor(
187
173
  const value = valueOwners.get(name);
188
174
  if (value !== undefined) return `-- param :${name} ${value.type}`;
189
175
  if (conditionBinds.has(name)) return `-- param :${name} boolean`;
190
- if (name === PAGE_BIND && page !== undefined)
176
+ if (name === LIMIT_BIND && limit !== undefined)
191
177
  return `-- param :${name} integer`;
192
178
  return fail(
193
179
  'SYQL7002_INTERNAL_LOWERING',
@@ -203,7 +189,7 @@ function planBind(
203
189
  valueOwners: ReadonlyMap<string, ValueOwner>,
204
190
  conditionBinds: ReadonlyMap<string, number>,
205
191
  conditions: readonly SyqlLogicalWhenNode[],
206
- page: SyqlValidatedQuery['page'],
192
+ limit: SyqlValidatedQuery['limit'],
207
193
  query: SyqlLogicalQuery,
208
194
  ): QuerySyqlPlanBind {
209
195
  const value = valueOwners.get(name);
@@ -229,12 +215,12 @@ function planBind(
229
215
  controls: (conditions[condition] as SyqlLogicalWhenNode).controls,
230
216
  };
231
217
  }
232
- if (name === PAGE_BIND && page !== undefined) {
218
+ if (name === LIMIT_BIND && limit !== undefined) {
233
219
  return {
234
- kind: 'page',
220
+ kind: 'limit',
235
221
  name,
236
222
  type: 'integer',
237
- input: page.control,
223
+ input: limit.control,
238
224
  };
239
225
  }
240
226
  return fail(
@@ -253,7 +239,7 @@ function publicInputs(
253
239
  const publicNames = [
254
240
  ...declaration.parameters.map((parameter) => parameter.name),
255
241
  ...(declaration.sort === undefined ? [] : [declaration.sort.control]),
256
- ...(declaration.page === undefined ? [] : [declaration.page.control]),
242
+ ...(declaration.limit === undefined ? [] : [declaration.limit.control]),
257
243
  ];
258
244
  const mapped = new Map(
259
245
  buildNamingMap(
@@ -267,12 +253,35 @@ function publicInputs(
267
253
  const result: QuerySyqlPublicInput[] = declaration.parameters.map(
268
254
  (parameter) => {
269
255
  const langName = mapped.get(parameter.name) as string;
270
- if (parameter.kind === 'switch') {
256
+ if (parameter.kind === 'range') {
257
+ const type = validated.bindTypes.get(
258
+ syqlRangeStartBind(parameter.name),
259
+ );
260
+ if (type === undefined) {
261
+ return fail(
262
+ 'SYQL7002_INTERNAL_LOWERING',
263
+ query,
264
+ `range ${parameter.name} has no resolved element type`,
265
+ );
266
+ }
271
267
  return {
272
- kind: 'switch',
268
+ kind: 'group',
273
269
  name: parameter.name,
274
270
  langName,
275
- default: false,
271
+ members: [
272
+ {
273
+ name: 'start',
274
+ langName: 'start',
275
+ type: type.base,
276
+ nullable: type.nullable,
277
+ },
278
+ {
279
+ name: 'end',
280
+ langName: 'end',
281
+ type: type.base,
282
+ nullable: type.nullable,
283
+ },
284
+ ],
276
285
  };
277
286
  }
278
287
  if (parameter.kind === 'group') {
@@ -320,7 +329,10 @@ function publicInputs(
320
329
  langName,
321
330
  type: type.base,
322
331
  nullable: type.nullable,
323
- required: !parameter.optional,
332
+ required: !parameter.optional && parameter.default === undefined,
333
+ ...(parameter.default === undefined
334
+ ? {}
335
+ : { default: parameter.default }),
324
336
  };
325
337
  },
326
338
  );
@@ -343,13 +355,13 @@ function publicInputs(
343
355
  })),
344
356
  });
345
357
  }
346
- if (validated.page !== undefined) {
358
+ if (validated.limit !== undefined) {
347
359
  result.push({
348
- kind: 'page',
349
- name: validated.page.control,
350
- langName: mapped.get(validated.page.control) as string,
351
- defaultSize: validated.page.defaultSize,
352
- maxSize: validated.page.maxSize,
360
+ kind: 'limit',
361
+ name: validated.limit.control,
362
+ langName: mapped.get(validated.limit.control) as string,
363
+ defaultSize: validated.limit.defaultSize,
364
+ maxSize: validated.limit.maxSize,
353
365
  });
354
366
  }
355
367
  return result;
@@ -360,8 +372,21 @@ function valueOwners(
360
372
  ): ReadonlyMap<string, ValueOwner> {
361
373
  const owners = new Map<string, ValueOwner>();
362
374
  for (const parameter of validated.logical.declaration.parameters) {
363
- if (parameter.kind === 'switch') continue;
364
- if (parameter.kind === 'group') {
375
+ if (parameter.kind === 'range') {
376
+ for (const [name, member] of [
377
+ [syqlRangeStartBind(parameter.name), 'start'],
378
+ [syqlRangeEndBind(parameter.name), 'end'],
379
+ ] as const) {
380
+ const type = validated.bindTypes.get(name);
381
+ if (type === undefined) continue;
382
+ owners.set(name, {
383
+ kind: 'group-member',
384
+ input: parameter.name,
385
+ member,
386
+ type: type.base,
387
+ });
388
+ }
389
+ } else if (parameter.kind === 'group') {
365
390
  for (const member of parameter.members) {
366
391
  const type = validated.bindTypes.get(member.name);
367
392
  if (type === undefined) continue;
@@ -415,7 +440,7 @@ function lowerStatement(
415
440
  names,
416
441
  owners,
417
442
  conditionBinds,
418
- validated.page,
443
+ validated.limit,
419
444
  query,
420
445
  );
421
446
  const candidate = headers.length === 0 ? sql : `${headers}\n${sql}`;
@@ -429,7 +454,8 @@ function lowerStatement(
429
454
  ...naming,
430
455
  internalParams: [
431
456
  ...conditionBinds.keys(),
432
- ...(validated.page === undefined ? [] : [PAGE_BIND]),
457
+ ...(validated.limit === undefined ? [] : [LIMIT_BIND]),
458
+ ...[...owners.keys()].filter((name) => name.startsWith('__syqlRange')),
433
459
  ],
434
460
  },
435
461
  );
@@ -450,7 +476,7 @@ function lowerStatement(
450
476
  owners,
451
477
  conditionBinds,
452
478
  query.conditions,
453
- validated.page,
479
+ validated.limit,
454
480
  query,
455
481
  ),
456
482
  ),
@@ -500,7 +526,7 @@ export function lowerSyqlQuery(
500
526
  body,
501
527
  logical.module.file,
502
528
  profile.sql,
503
- validated.page,
529
+ validated.limit,
504
530
  );
505
531
  const lowered = lowerStatement(
506
532
  validated,
@@ -555,7 +581,7 @@ export function lowerSyqlQuery(
555
581
  body,
556
582
  logical.module.file,
557
583
  profile.sql,
558
- validated.page,
584
+ validated.limit,
559
585
  );
560
586
  const lowered = lowerStatement(
561
587
  validated,