Skip to content

Commit

Permalink
[parked] fix: types of QL return (#10)
Browse files Browse the repository at this point in the history
* add test to reproduce problem

* Introduce `UnionToIntersection` (#22)

* Introduce UnionToIntersection

* Add changelog entry

* Fix trailing spaces

* Changelog for `SELECT.from.ref`

---------

Co-authored-by: Daniel O'Grady <[email protected]>
Co-authored-by: Christian Georgi <[email protected]>
Co-authored-by: Christian Georgi <[email protected]>
  • Loading branch information
4 people committed Jan 17, 2024
1 parent 60f1be9 commit 03ccd1e
Show file tree
Hide file tree
Showing 4 changed files with 51 additions and 4 deletions.
7 changes: 6 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,20 +10,25 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/).

- Type for special error listener `srv.on('error')`

### Changed

- `source`, `column_expr`, and `predicate` have been converted to partial intersection types. This offers all possible optional properties. You will have to make sure to check their presence when accessing them

### Fixed

- `srv.send` overload to also allow optional headers
- Reflected types like `cds.entity`, `cds.struct`, `cds.Association` are now properly exposed
- `cds.builtin.types` got a more accurate type
- The `LinkedEntity.drafts` property is now optional. At runtime, it's only set for drafted entities.
- `cds.model` is marked as modifiable (for tests only!)
- `SELECT.from` got its `ref` property back


## Version 0.1.0 - 2023-12-13

### Changed

- Rework of the export structure of the main `cds` facade object, so that e.g. `cds.Request` and `cds.User` work again.
- Rework of the export structure of the main `cds` facade object, so that e.g. `cds.Request` and `cds.User` work again

### Fixed

Expand Down
7 changes: 4 additions & 3 deletions apis/cqn.d.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { entity } from './csn' // cyclic dependency
import { UnionToIntersection, UnionsToIntersections } from './internal/inference'

// FIXME: a union type would be more appropriate here
export type Query = Partial<SELECT & INSERT & UPDATE & DELETE & CREATE & DROP & UPSERT>
Expand Down Expand Up @@ -66,9 +67,9 @@ type data = Record<string, any>
type name = string

/** @private */
type source = (ref | SELECT) & { as?: name, join?: name, on?: xpr }
export type column_expr = expr & { as?: name, cast?: any, expand?: column_expr[], inline?: column_expr[] }
export type predicate = _xpr
type source = UnionToIntersection<ref | SELECT> & { as?: name, join?: name, on?: xpr }
export type column_expr = UnionToIntersection<expr> & { as?: name, cast?: any, expand?: column_expr[], inline?: column_expr[] }
export type predicate = UnionsToIntersections<_xpr>

/** @private */
type ordering_term = expr & { sort?: 'asc' | 'desc', nulls?: 'first' | 'last' }
Expand Down
34 changes: 34 additions & 0 deletions apis/internal/inference.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,3 +30,37 @@ export type Unwrap<T> = T extends ArrayConstructable
: T extends Array<infer U>
? U
: T


/*
* the following three types are used to convert union types to intersection types.
* We need these as our types currently lack generics in places where we would need them to clearly decide
* on a subtype in the case of a union type. This leads to the following problem:
*
* ```ts
* type A = { a: number }
* type B = { b: string }
* type Foo = A | B
* function f(): Foo { ... }
* const x = f()
* x.a // error, could also be B
* ```
*
* While we should have:
*
* ```ts
* function f<T extends Foo>(): T { ... }
* const x = f<A>()
* x.a
* ```ts
*
* Since we don't do that yet, we opt for intersection types instead.
* By also wrapping it in Partial, we at least force the user to check for the presence of any
* attribute they try to access.
*
* Places where these types are used are subject to a rework!
* the idea behind the conversion can be found in this excellent writeup: https://fettblog.eu/typescript-union-to-intersection/
*/
export type Scalarise<A> = A extends Array<infer N> ? N : A
export type UnionToIntersection<U> = Partial<(U extends any ? (k: U) => void : never) extends (k: infer I) => void ? I : never>
export type UnionsToIntersections<U> = Array<UnionToIntersection<Scalarise<U>>>
7 changes: 7 additions & 0 deletions test/typescript/apis/project/cds-ql.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,13 @@ sel.from(Foos)
sel.columns("x") // x was suggested by code completion
sel.SELECT.columns?.filter(e => !e) // check if this is array

// ensure ql returns a proper CQN
const s = SELECT.from(Foos).columns('ID').where('ID =', 42)
s.SELECT.from.ref
s.SELECT.columns?.[0].ref
s.SELECT.where?.[0].ref
s.SELECT.where?.[2].val

INSERT.into(Foos).columns("x") // x was suggested by code completion
let ins: INSERT<Foo>
ins = INSERT.into(Foos, {})
Expand Down

0 comments on commit 03ccd1e

Please sign in to comment.