Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Ironing #25

Closed
wants to merge 23 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 5 additions & 5 deletions apis/cqn.d.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
import { entity } from './csn' // cyclic dependency
import { UnionToIntersection, UnionsToIntersections } from './internal/inference'
import { UnionToIntersection } from './internal/inference'

// FIXME: a union type would be more appropriate here
export type Query = Partial<SELECT & INSERT & UPDATE & DELETE & CREATE & DROP & UPSERT>
export type Query = SELECT | INSERT | UPDATE | DELETE | CREATE | DROP | UPSERT

export type SELECT = { SELECT: {
distinct?: true,
Expand Down Expand Up @@ -67,9 +67,9 @@ type data = Record<string, any>
type name = string

/** @private */
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>
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

/** @private */
type ordering_term = expr & { sort?: 'asc' | 'desc', nulls?: 'first' | 'last' }
Expand Down
5 changes: 3 additions & 2 deletions apis/csn.d.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { SELECT, ref, predicate } from './cqn'
import type { UnionToIntersection } from './internal/inference'

/**
* A parsed CDS model in CSN object notation.
Expand Down Expand Up @@ -43,7 +44,7 @@ export type FQN = string
/**
* Definitions are the central elements of a CDS model.
*/
export type Definition = context & service & type & struct & entity & Association
export type Definition = UnionToIntersection<context | service | type | struct | entity | Association>
// NOTE: If we use & instead of | CSN.definitions values would be reduced to <never>

/**
Expand All @@ -55,7 +56,7 @@ export type Extension = {
includes?: FQN[],
}

export type Element = type & struct & Association
export type Element = UnionToIntersection<type | struct | Association>

export type kinds = 'type' | 'entity' | 'event' | 'service' | 'context' | 'struct'

Expand Down
7 changes: 5 additions & 2 deletions apis/internal/inference.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,8 @@ export interface ArrayConstructable<T = any> {

// concrete singular type.
// `SingularType<typeof Books>` == `Book`.
export type SingularType<T extends ArrayConstructable<T>> = InstanceType<T>[number]
export type SingularInstanceType<T extends ArrayConstructable> = InstanceType<T>[number]
export type PluralInstanceType<T extends Constructable> = InstanceType<Array<T>>

// Convenient way of unwrapping the inner type from array-typed values, as well as the value type itself
// `class MyArray<T> extends Array<T>``
Expand All @@ -26,7 +27,7 @@ export type SingularType<T extends ArrayConstructable<T>> = InstanceType<T>[numb
// This type introduces an indirection that streamlines their behaviour for both cases.
// For any scalar type `Unwrap` behaves idempotent.
export type Unwrap<T> = T extends ArrayConstructable
? SingularType<T>
? SingularInstanceType<T>
: T extends Array<infer U>
? U
: T
Expand Down Expand Up @@ -62,5 +63,7 @@ export type Unwrap<T> = T extends ArrayConstructable
* 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 Pluralise<S> = S extends Array<any> ? S : Array<S>
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>>>
export type FIXME = any
149 changes: 149 additions & 0 deletions apis/internal/query.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,149 @@
import type { Definition } from '../csn'
import type { LinkedEntity } from '../linked'
import type { column_expr } from '../cqn'
import type { ArrayConstructable, SingularInstanceType } from './inference'


// https://cap.cloud.sap/docs/node.js/cds-ql?q=projection#projection-functions
type Projection<T> = (e: QLExtensions<T extends ArrayConstructable ? SingularInstanceType<T> : T>) => void
type Primitive = string | number | boolean | Date
type EntityDescription = LinkedEntity | Definition | string // FIXME: Definition not allowed here?, FIXME: { name: string } | ?
type PK = number | string | object
// used as a catch-all type for using tagged template strings: SELECT `foo`. from `bar` etc.
// the resulting signatures are actually not very strongly typed, but they at least accept template strings
// when run in strict mode.
// This signature has to be added to a method as intersection type.
// Defining overloads with it will override preceding signatures and the other way around.
type TaggedTemplateQueryPart<T> = (strings: TemplateStringsArray, ...params: unknown[]) => T

type QueryArtefact = {

/**
* Alias for this attribute.
*/
as (alias: string): void,

/**
* Accesses any nested attribute based on a [path](https://cap.cloud.sap/cap/docs/java/query-api#path-expressions):
* `X.get('a.b.c.d')`. Note that you will not receive
* proper typing after this call.
* To still have access to typed results, use
* `X.a().b().c().d()` instead.
*/
get (path: string): any,

}

// Type for query pieces that can either be chained to build more complex queries or
// awaited to materialise the result:
// `Awaitable<SELECT<Book>, Book> = SELECT<Book> & Promise<Book>`
//
// While the benefit is probably not immediately obvious as we don't exactly
// save a lot of typing over explicitly writing `SELECT<Book> & Promise<Book>`,
// it makes the semantics more explicit. Also sets us up for when TypeScript ever
// improves their generics to support:
//
// `Awaitable<T> = T extends unknown<infer I> ? (T & Promise<I>) : never`
// (at the time of writing, infering the first generic parameter of ANY type
// does not seem to be possible.)
export type Awaitable<T, I> = T & Promise<I>

// note to self: don't try to rewrite these intersection types into overloads.
// It does not work because TaggedTemplateQueryPart will not fit in as regular overload
export interface Columns<This = undefined> {
columns: TaggedTemplateQueryPart<This extends undefined ? this : This>
& (<T>(...col: (T extends ArrayConstructable ? keyof SingularInstanceType<T> : keyof T)[]) => This extends undefined ? this : This)
& ((...col: (string | column_expr)[]) => This extends undefined ? this : This)
& ((col: (string | column_expr)[]) => This extends undefined ? this : This)
}

export interface Having {
having: TaggedTemplateQueryPart<this>
& ((...expr: string[]) => this)
& ((predicate: object) => this)
}

export interface GroupBy {
groupBy: TaggedTemplateQueryPart<this>
& ((...expr: string[]) => this)
}

export interface OrderBy {
orderBy: TaggedTemplateQueryPart<this>
& ((...expr: string[]) => this)
}

export interface Limit {
limit: TaggedTemplateQueryPart<this>
& ((rows: number, offset?: number) => this)
}

export interface Where {
where: TaggedTemplateQueryPart<this>
& ((predicate: object) => this)
& ((...expr: any[]) => this)
}

export interface And {
and: TaggedTemplateQueryPart<this>
& ((predicate: object) => this)
& ((...expr: any[]) => this)
}

// don't wrap QLExtensions in more QLExtensions (indirection to work around recursive definition)
export type QLExtensions<T> = T extends QLExtensions_<any> ? T : QLExtensions_<T>

/**
* QLExtensions are properties that are attached to entities in CQL contexts.
* They are passed down to all properties recursively.
*/
// have to exclude undefined from the type, or we'd end up with a distribution of Subqueryable
// over T and undefined, which gives us zero code completion within the callable.
type QLExtensions_<T> = { [Key in keyof T]: QLExtensions<T[Key]> } & QueryArtefact & Subqueryable<Exclude<T, undefined>>

/**
* Adds the ability for subqueries to structured properties.
* The final result of each subquery will be the property itself:
* `Book.title` == `Subqueryable<Book>.title()`
*/
type Subqueryable<T> = T extends Primitive ? unknown
// composition of many/ association to many
: T extends readonly unknown[] ? {

/**
* @example
* ```js
* SELECT.from(Books, b => b.author)
* ```
* means: "select all books and project each book's author"
*
* whereas
* ```js
* SELECT.from(Books, b => b.author(a => a.ID))
* ```
* means: "select all books, subselect each book's author's ID
*
* Note that you do not need to return anything from these subqueries.
*/
(fn: ((a: QLExtensions<T[number]>) => any) | '*'): T[number],
}
// composition of one/ association to one
: {

/**
* @example
* ```js
* SELECT.from(Books, b => b.author)
* ```
* means: "select all books and project each book's author"
*
* whereas
* ```js
* SELECT.from(Books, b => b.author(a => a.ID))
* ```
* means: "select all books, subselect each book's author's ID
*
* Note that you do not need to return anything from these subqueries.
*/
(fn: ((a: QLExtensions<T>) => any) | '*'): T,
}
4 changes: 2 additions & 2 deletions apis/linked.d.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { CSN, FQN, Association, Definition, entity, kinds } from './csn'

export type LinkedDefinition = linked & Definition & LinkedEntity & LinkedAssociation
export type Definitions = { [name: string]: LinkedDefinition }
export type LinkedDefinition = linked | Definition | LinkedEntity | LinkedAssociation
export type IterableMap<T extends LinkedDefinition> = { [name: string]: T } & ((namespace: string) => IterableMap<T>) & Iterable<T> // FIXME-D: should the lambda part return IterableMap again? That allows x(...)(...)(...)...
// FIXME: this is only a temporary alias. Definitions is actually correct,
// but the name may be misleading, as it is indeed a mapping of strings to LinkedDefinition objects.
export type LinkedDefinitions = Definitions
Expand Down
Loading