1
+ import type { Definition } from '../csn'
2
+ import type { entity } from '../linked/classes'
3
+ import type { column_expr , ref } from '../cqn'
4
+ import type { ArrayConstructable , Constructable , SingularInstanceType , Unwrap , UnwrappedInstanceType } from './inference'
5
+ import { ConstructedQuery } from '../ql'
6
+ import { KVPairs , DeepRequired } from './util'
7
+
8
+ // https://cap.cloud.sap/docs/node.js/cds-ql?q=projection#projection-functions
9
+ type Projection < T > = ( e : QLExtensions < T extends ArrayConstructable ? SingularInstanceType < T > : T > ) => void
10
+ type Primitive = string | number | boolean | Date
11
+ type NonPrimitive < T > = Exclude < T , string | number | boolean | symbol | bigint | null | undefined >
12
+ type EntityDescription = entity | Definition | string // FIXME: Definition not allowed here?, FIXME: { name: string } | ?
13
+ type PK = number | string | object
14
+ // used as a catch-all type for using tagged template strings: SELECT `foo`. from `bar` etc.
15
+ // the resulting signatures are actually not very strongly typed, but they at least accept template strings
16
+ // when run in strict mode.
17
+ // This signature has to be added to a method as intersection type.
18
+ // Defining overloads with it will override preceding signatures and the other way around.
19
+ type TaggedTemplateQueryPart < T > = ( strings : TemplateStringsArray , ...params : unknown [ ] ) => T
20
+
21
+ type QueryArtefact = {
22
+
23
+ /**
24
+ * Alias for this attribute.
25
+ */
26
+ as ( alias : string ) : void ,
27
+
28
+ /**
29
+ * Accesses any nested attribute based on a [path](https://cap.cloud.sap/cap/docs/java/query-api#path-expressions):
30
+ * `X.get('a.b.c.d')`. Note that you will not receive
31
+ * proper typing after this call.
32
+ * To still have access to typed results, use
33
+ * `X.a().b().c().d()` instead.
34
+ */
35
+ get ( path : string ) : any ,
36
+
37
+ }
38
+
39
+ // Type for query pieces that can either be chained to build more complex queries or
40
+ // awaited to materialise the result:
41
+ // `Awaitable<SELECT<Book>, Book> = SELECT<Book> & Promise<Book>`
42
+ //
43
+ // While the benefit is probably not immediately obvious as we don't exactly
44
+ // save a lot of typing over explicitly writing `SELECT<Book> & Promise<Book>`,
45
+ // it makes the semantics more explicit. Also sets us up for when TypeScript ever
46
+ // improves their generics to support:
47
+ //
48
+ // `Awaitable<T> = T extends unknown<infer I> ? (T & Promise<I>) : never`
49
+ // (at the time of writing, infering the first generic parameter of ANY type
50
+ // does not seem to be possible.)
51
+ export type Awaitable < T , I > = T & Promise < I >
52
+
53
+ // note to self: don't try to rewrite these intersection types into overloads.
54
+ // It does not work because TaggedTemplateQueryPart will not fit in as regular overload
55
+ export interface ByKey {
56
+ byKey ( primaryKey ?: PK ) : this
57
+ }
58
+
59
+ // unwrap the target of a query and extract its keys.
60
+ // Normalise to scalar,
61
+ // or fall back to general strings/column expressions
62
+ type KeyOfTarget < T , F = string | column_expr > = T extends ConstructedQuery < infer U >
63
+ ? ( U extends ArrayConstructable // Books
64
+ ? keyof SingularInstanceType < U >
65
+ : U extends Constructable // Book
66
+ ? keyof InstanceType < U >
67
+ : F )
68
+ : F
69
+
70
+ type KeyOfSingular < T > = Unwrap < T > extends T
71
+ ? keyof T
72
+ : keyof Unwrap < T >
73
+
74
+ // as static SELECT borrows the type of Columns directly,
75
+ // we need this second type argument to explicitly specific that "this"
76
+ // refers to a STATIC<T>, not to a Columns. Or else we could not chain
77
+ // other QL functions to .columns
78
+ export interface Columns < T , This = undefined > {
79
+ columns :
80
+ ( ( ...col : KeyOfSingular < T > [ ] ) => This extends undefined ? this : This )
81
+ & ( ( col : KeyOfSingular < T > [ ] ) => This extends undefined ? this : This )
82
+ & ( ( ...col : ( string | column_expr ) [ ] ) => This extends undefined ? this : This )
83
+ & ( ( col : ( string | column_expr ) [ ] ) => This extends undefined ? this : This )
84
+ & TaggedTemplateQueryPart < This extends undefined ? this : This >
85
+ }
86
+
87
+ type Op = '=' | '<' | '>' | '<=' | '>=' | '!=' | 'in' | 'like'
88
+ type WS = '' | ' '
89
+ type Expression < E extends string | number | bigint | boolean > = `${E } ${WS } ${Op } ${WS } `
90
+ type ColumnValue = Primitive | Readonly < Primitive [ ] > | SELECT < any > // not entirely sure why Readonly is required here
91
+ // TODO: it would be nicer to check for E[x] for the value instead of Primitive, where x is the key
92
+ type Expressions < L , E > = KVPairs < L , Expression < Exclude < keyof E , symbol > > , ColumnValue > extends true
93
+ ? L
94
+ // fallback: allow for any string. Important for when user renamed properties
95
+ : KVPairs < L , Expression < string > , ColumnValue > extends true
96
+ ? L
97
+ : never
98
+
99
+ type HavingWhere < This , E > =
100
+ /**
101
+ * @param predicate - An object with keys that are valid fields of the target entity and values that are compared to the respective fields.
102
+ * @example
103
+ * ```js
104
+ * SELECT.from(Books).where({ ID: 42 }) // where ID is a valid field of Book
105
+ * SELECT.from(Books).having({ ID: 42 }) // where ID is a valid field of Book
106
+ * ```
107
+ */
108
+ ( ( predicate : Partial < { [ column in KeyOfTarget < This extends ConstructedQuery < infer E > ? E : never , never > ] : any } > ) => This )
109
+ /**
110
+ * @param expr - An array of expressions, where every odd element is a valid field of the target entity and every even element is a value that is compared to the respective field.
111
+ * @example
112
+ * ```js
113
+ * SELECT.from(Books).where(['ID =', 42 ]) // where ID is a valid, numerical field of Book
114
+ * SELECT.from(Books).having(['ID =', 42 ]) // where ID is a valid, numerical field of Book
115
+ *```
116
+ */
117
+ & ( < const L extends unknown [ ] > ( ...expr : Expressions < L , UnwrappedInstanceType < E > > ) => This )
118
+ & ( ( ...expr : string [ ] ) => This )
119
+ & TaggedTemplateQueryPart < This >
120
+
121
+ export interface Having < T > {
122
+ having : HavingWhere < this, T >
123
+ }
124
+
125
+ export interface Where < T > {
126
+ where : HavingWhere < this, T >
127
+ }
128
+
129
+ export interface GroupBy {
130
+ groupBy : TaggedTemplateQueryPart < this>
131
+ & ( ( columns : Partial < { [ column in KeyOfTarget < this extends ConstructedQuery < infer E > ? E : never , never > ] : any } > ) => this)
132
+ & ( ( ...expr : string [ ] ) => this)
133
+ & ( ( ref : ref ) => this)
134
+ // columns currently not being auto-completed due to complexity
135
+ }
136
+
137
+ export interface OrderBy < T > {
138
+ orderBy : TaggedTemplateQueryPart < this>
139
+ & ( ( ...col : KeyOfSingular < T > [ ] ) => this)
140
+ & ( ( ...expr : string [ ] ) => this)
141
+ }
142
+
143
+ export interface Limit {
144
+ limit : TaggedTemplateQueryPart < this>
145
+ & ( ( rows : number , offset ?: number ) => this)
146
+ }
147
+
148
+ export interface And {
149
+ and : TaggedTemplateQueryPart < this>
150
+ & ( ( predicate : object ) => this)
151
+ & ( ( ...expr : any [ ] ) => this)
152
+ }
153
+
154
+ export interface InUpsert < T > {
155
+ data ( block : ( e : T ) => void ) : this
156
+
157
+ entries ( ...entries : object [ ] ) : this
158
+
159
+ values ( ...val : ( null | Primitive ) [ ] ) : this
160
+ values ( val : ( null | Primitive ) [ ] ) : this
161
+
162
+ rows ( ...row : ( null | Primitive ) [ ] [ ] ) : this
163
+ rows ( row : ( null | Primitive ) [ ] [ ] ) : this
164
+
165
+ into : ( < T extends ArrayConstructable > ( entity : T ) => this)
166
+ & TaggedTemplateQueryPart < this>
167
+ & ( ( entity : EntityDescription ) => this)
168
+ }
169
+
170
+ // don't wrap QLExtensions in more QLExtensions (indirection to work around recursive definition)
171
+ export type QLExtensions < T > = T extends QLExtensions_ < any > ? T : QLExtensions_ < DeepRequired < T > >
172
+
173
+ /**
174
+ * QLExtensions are properties that are attached to entities in CQL contexts.
175
+ * They are passed down to all properties recursively.
176
+ */
177
+ // have to exclude undefined from the type, or we'd end up with a distribution of Subqueryable
178
+ // over T and undefined, which gives us zero code completion within the callable.
179
+ type QLExtensions_ < T > = { [ Key in keyof T ] : QLExtensions < T [ Key ] > } & QueryArtefact & Subqueryable < Exclude < T , undefined > >
180
+
181
+ /**
182
+ * Adds the ability for subqueries to structured properties.
183
+ * The final result of each subquery will be the property itself:
184
+ * `Book.title` == `Subqueryable<Book>.title()`
185
+ */
186
+ type Subqueryable < T > = T extends Primitive ? unknown
187
+ // composition of many/ association to many
188
+ : T extends readonly unknown [ ] ? {
189
+
190
+ /**
191
+ * @example
192
+ * ```js
193
+ * SELECT.from(Books, b => b.author)
194
+ * ```
195
+ * means: "select all books and project each book's author"
196
+ *
197
+ * whereas
198
+ * ```js
199
+ * SELECT.from(Books, b => b.author(a => a.ID))
200
+ * ```
201
+ * means: "select all books, subselect each book's author's ID
202
+ *
203
+ * Note that you do not need to return anything from these subqueries.
204
+ */
205
+ ( fn : ( ( a : QLExtensions < T [ number ] > ) => any ) | '*' ) : T [ number ] ,
206
+ }
207
+ // composition of one/ association to one
208
+ : {
209
+
210
+ /**
211
+ * @example
212
+ * ```js
213
+ * SELECT.from(Books, b => b.author)
214
+ * ```
215
+ * means: "select all books and project each book's author"
216
+ *
217
+ * whereas
218
+ * ```js
219
+ * SELECT.from(Books, b => b.author(a => a.ID))
220
+ * ```
221
+ * means: "select all books, subselect each book's author's ID
222
+ *
223
+ * Note that you do not need to return anything from these subqueries.
224
+ */
225
+ ( fn : ( ( a : QLExtensions < T > ) => any ) | '*' ) : T ,
226
+ }
0 commit comments