@@ -97,6 +97,8 @@ type TypeScriptSingleValueTypes<T extends SingleValuePostgreSQLTypes> = T extend
97
97
? Record < string , unknown >
98
98
: unknown
99
99
100
+ type AggregateFunctions = 'count' | 'sum' | 'avg' | 'min' | 'max'
101
+
100
102
type StripUnderscore < T extends string > = T extends `_${infer U } ` ? U : T
101
103
102
104
type TypeScriptTypes < T extends PostgreSQLTypes > = T extends ArrayPostgreSQLTypes
@@ -392,21 +394,74 @@ type ParseField<Input extends string> = Input extends ''
392
394
* Parses a field excluding embedded resources, without preceding field renaming.
393
395
* This is one of the following:
394
396
* - `field`
397
+ * - `field.aggregate()`
398
+ * - `field.aggregate()::type`
399
+ * - `field::type`
400
+ * - `field::type.aggregate()`
401
+ * - `field::type.aggregate()::type`
402
+ * - `field->json...`
403
+ * - `field->json.aggregate()`
404
+ * - `field->json.aggregate()::type`
405
+ * - `field->json::type`
406
+ * - `field->json::type.aggregate()`
407
+ * - `field->json::type.aggregate()::type`
408
+ */
409
+ type ParseFieldWithoutEmbeddedResource < Input extends string > =
410
+ ParseFieldWithoutEmbeddedResourceAndAggregation < Input > extends [ infer Field , `${infer Remainder } `]
411
+ ? ParseFieldAggregation < EatWhitespace < Remainder > > extends [
412
+ `${infer AggregateFunction } `,
413
+ `${infer Remainder } `
414
+ ]
415
+ ? ParseFieldTypeCast < EatWhitespace < Remainder > > extends [ infer Type , `${infer Remainder } `]
416
+ ? // `field.aggregate()::type`
417
+ [
418
+ Omit < Field , 'name' | 'original' | 'type' > & {
419
+ name : AggregateFunction
420
+ original : AggregateFunction
421
+ type : Type
422
+ } ,
423
+ EatWhitespace < Remainder >
424
+ ]
425
+ : ParseFieldTypeCast < EatWhitespace < Remainder > > extends ParserError < string >
426
+ ? ParseFieldTypeCast < EatWhitespace < Remainder > >
427
+ : // `field.aggregate()`
428
+ [
429
+ Omit < Field , 'name' | 'original' > & {
430
+ name : AggregateFunction
431
+ original : AggregateFunction
432
+ } ,
433
+ EatWhitespace < Remainder >
434
+ ]
435
+ : ParseFieldAggregation < EatWhitespace < Remainder > > extends ParserError < string >
436
+ ? ParseFieldAggregation < EatWhitespace < Remainder > >
437
+ : // `field`
438
+ [ Field , EatWhitespace < Remainder > ]
439
+ : CreateParserErrorIfRequired <
440
+ ParseFieldWithoutEmbeddedResourceAndAggregation < Input > ,
441
+ `Expected field at \`${Input } \``
442
+ >
443
+
444
+ /**
445
+ * Parses a field excluding embedded resources or aggregation, without preceding field renaming.
446
+ * This is one of the following:
447
+ * - `field`
395
448
* - `field::type`
396
449
* - `field->json...`
397
450
* - `field->json...::type`
398
451
*/
399
- type ParseFieldWithoutEmbeddedResource < Input extends string > = Input extends ''
400
- ? ParserError < 'Empty string' >
401
- : ParseFieldWithoutEmbeddedResourceAndTypeCast < Input > extends [ infer Field , `${infer Remainder } `]
402
- ? ParseFieldTypeCast < EatWhitespace < Remainder > > extends [ infer Type , `${infer Remainder } `]
403
- ? // `field::type`
404
- [ Field & { type : Type } , EatWhitespace < Remainder > ]
405
- : ParseFieldTypeCast < EatWhitespace < Remainder > > extends ParserError < string >
406
- ? ParseFieldTypeCast < EatWhitespace < Remainder > >
407
- : // `field`
408
- [ Field , EatWhitespace < Remainder > ]
409
- : ParserError < `Expected identifier at \`${Input } \``>
452
+ type ParseFieldWithoutEmbeddedResourceAndAggregation < Input extends string > =
453
+ ParseFieldWithoutEmbeddedResourceAndTypeCast < Input > extends [ infer Field , `${infer Remainder } `]
454
+ ? ParseFieldTypeCast < EatWhitespace < Remainder > > extends [ infer Type , `${infer Remainder } `]
455
+ ? // `field::type` or `field->json...::type`
456
+ [ Omit < Field , 'type' > & { type : Type } , EatWhitespace < Remainder > ]
457
+ : ParseFieldTypeCast < EatWhitespace < Remainder > > extends ParserError < string >
458
+ ? ParseFieldTypeCast < EatWhitespace < Remainder > >
459
+ : // `field` or `field->json...`
460
+ [ Field , EatWhitespace < Remainder > ]
461
+ : CreateParserErrorIfRequired <
462
+ ParseFieldWithoutEmbeddedResourceAndTypeCast < Input > ,
463
+ `Expected field at \`${Input } \``
464
+ >
410
465
411
466
/**
412
467
* Parses a field excluding embedded resources or typecasting, without preceding field renaming.
@@ -443,6 +498,25 @@ type ParseFieldTypeCast<Input extends string> = EatWhitespace<Input> extends `::
443
498
: ParserError < `Invalid type for \`::\` operator at \`$ { Remainder } \``>
444
499
: Input
445
500
501
+ /**
502
+ * Parses a field aggregation (`.max()`), returning a tuple of ["Aggregate function", "Remainder of text"]
503
+ * or the original string input indicating that no aggregation was found.
504
+ */
505
+ type ParseFieldAggregation < Input extends string > =
506
+ EatWhitespace < Input > extends `.${infer Remainder } `
507
+ ? ParseIdentifier < EatWhitespace < Remainder > > extends [
508
+ `${infer FunctionName } `,
509
+ `${infer Remainder } `
510
+ ]
511
+ ? // Ensure that aggregation function is valid.
512
+ FunctionName extends AggregateFunctions
513
+ ? EatWhitespace < Remainder > extends `()${infer Remainder } `
514
+ ? [ FunctionName , EatWhitespace < Remainder > ]
515
+ : ParserError < `Expected \`( ) \` after \`. \` operator \`${FunctionName } \``>
516
+ : ParserError < `Invalid type for \`. \` operator \`$ { FunctionName } \``>
517
+ : ParserError < `Invalid type for \`. \` operator at \`$ { Remainder } \``>
518
+ : Input
519
+
446
520
/**
447
521
* Parses a node.
448
522
* A node is one of the following:
0 commit comments