Skip to content

Commit

Permalink
Merge branch 'main' into fix/ql-inupsert-type-inference
Browse files Browse the repository at this point in the history
  • Loading branch information
stockbal committed Dec 2, 2024
2 parents b462352 + 3c27b65 commit ab8d73b
Show file tree
Hide file tree
Showing 8 changed files with 96 additions and 39 deletions.
5 changes: 5 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,13 @@ This project adheres to [Semantic Versioning](http://semver.org/).
The format is based on [Keep a Changelog](http://keepachangelog.com/).

## Version 0.9.0 - tbd
### Added
- Adds missing properties for `log` in `cds.env`

### Fixed

- Use `Required` instead of `DeepRequired` in projection function to avoid complexity errors from TypeScript
- Added missing type inference for `.set`/`.with` of `UPDATE`
- Added missing type inference for `.entries` of `UPSERT` and `INSERT`

## Version 0.8.0 - 24-11-26
Expand Down
8 changes: 8 additions & 0 deletions apis/env.d.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import { levels } from './log'

/**
* Access to the configuration for Node.js runtime and tools.
* The object is the effective result of configuration merged from various sources,
Expand All @@ -14,6 +16,12 @@ export const env: {
[key: string]: any,
},
profiles: string[],
log: {
user: boolean,
levels: Record<string, Lowercase<keyof typeof levels>>,
als_custom_fields: Record<string, number>,
cls_custom_fields: string[],
},
requires: env.Requires,
folders: {
app: string,
Expand Down
4 changes: 2 additions & 2 deletions apis/internal/query.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import type { entity } from '../linked/classes'
import type { column_expr, ref } from '../cqn'
import type { ArrayConstructable, Constructable, SingularInstanceType, Unwrap, UnwrappedInstanceType } from './inference'
import { ConstructedQuery } from '../ql'
import { KVPairs, DeepRequired } from './util'
import { KVPairs } from './util'

// 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
Expand Down Expand Up @@ -169,7 +169,7 @@ export interface InUpsert<T> {
}

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

/**
* QLExtensions are properties that are attached to entities in CQL contexts.
Expand Down
41 changes: 22 additions & 19 deletions apis/ql.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,7 @@ export declare class QL<T> {
& ((...entries: object[]) => UPSERT<T>) & ((entries: object[]) => UPSERT<T>)

UPDATE: typeof UPDATE<T>
& typeof UPDATE.entity<_TODO>
& typeof UPDATE.entity

DELETE: typeof DELETE<T>
& ((...entries: object[]) => DELETE<T>) & ((entries: object[]) => DELETE<T>)
Expand Down Expand Up @@ -233,34 +233,37 @@ export class DELETE<T> extends ConstructedQuery<T> {
DELETE: CQN.DELETE['DELETE']

}
// operator for qbe expression
type QbeOp = '=' | '-=' | '+=' | '*=' | '/=' | '%='

export interface UPDATE<T> extends Where<T>, And, ByKey {}
export class UPDATE<T> extends ConstructedQuery<T> {
private constructor();

// cds-typer plural
// FIXME: this returned UPDATE<SingularInstanceType<T>> before. should UPDATE<Books>.entity(...) return Book or Books?
static entity<T extends ArrayConstructable> (entity: T, primaryKey?: PK): UPDATE<InstanceType<T>>

static entity (entity: EntityDescription, primaryKey?: PK): UPDATE<StaticAny>

static entity<T extends Constructable> (entity: T, primaryKey?: PK): UPDATE<T>

// currently no easy way to restrict T from being a primitive type
static entity<T> (entity: T, primaryKey?: PK): UPDATE<T>

// with (block: (e:T)=>void) : this
// set (block: (e:T)=>void) : this
set: TaggedTemplateQueryPart<this>
& ((data: object) => this);

with: TaggedTemplateQueryPart<this>
& ((data: object) => this)
static entity: (TaggedTemplateQueryPart<UPDATE<StaticAny>>)
// UPDATE<SingularInstanceType<T>> is used here so type inference in set/with has the property keys of the singular type
& (<T extends ArrayConstructable> (entity: T, primaryKey?: PK) => UPDATE<SingularInstanceType<T>>)
& (<T extends Constructable> (entity: T, primaryKey?: PK) => UPDATE<InstanceType<T>>)
& ((entity: EntityDescription, primaryKey?: PK) => UPDATE<StaticAny>)
& (<T> (entity: T, primaryKey?: PK) => UPDATE<T>)

set: UpdateSet<this, T>
with: UpdateSet<this, T>

UPDATE: CQN.UPDATE['UPDATE']

}

/**
* Represents updatable block that can be passed to either `.set` or `.with`
* of an `UPDATE` query
*/
type UpdateSet<This, T> = TaggedTemplateQueryPart<This>
// simple value > title: 'Some Title'
// qbe expression > stock: { '-=': quantity }
// cqn expression > descr: {xpr: [{ref:[descr]}, '||', 'Some addition to descr.']}
& ((data: {[P in keyof T]?: T[P] | {[op in QbeOp]?: T[P]} | CQN.xpr}) => This)

export class CREATE<T> extends ConstructedQuery<T> {
private constructor();

Expand Down
33 changes: 18 additions & 15 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

5 changes: 5 additions & 0 deletions test/typescript/apis/project/cds-env.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,11 @@ env.folders.foo = ''
env.build = ''
env.hana = ''

env.log.levels['cli'] === 'debug'
env.log.cls_custom_fields.length
env.log.als_custom_fields['query'] === 0
Object.keys(env.log.als_custom_fields)

env.requires.auth.kind = ''
env.requires.auth.credentials!.url = ''
env.requires.auth.credentials!.clientid = ''
Expand Down
38 changes: 35 additions & 3 deletions test/typescript/apis/project/cds-ql.ts
Original file line number Diff line number Diff line change
Expand Up @@ -96,11 +96,11 @@ ins.columns("x") // x was suggested by code completion
ins.INSERT.into === "foo"
INSERT.into("Bla").as(SELECT.from("Foo"))

let upd: UPDATE<Foos>
let upd: UPDATE<Foo>
upd = UPDATE(Foo, 42)
upd.set({})
upd.set({x:4})
upd = UPDATE.entity(Foos)
upd.set({})
upd.set({x:1})
upd.UPDATE.entity === "foo"

let ups:UPSERT<Foo>
Expand Down Expand Up @@ -215,6 +215,11 @@ SELECT.from(Foos).columns(f => {
const number: QLExtensions<number> = f.x
})

// deep projection nesting
SELECT.from(Foos, f => f.ref(r => r.ref(r => r.ref(r => {
const x: number = r.x
}))))

// @ts-expect-error invalid key of result line
SELECT.from(Foos).columns(['entityIDColumn', 'parentIDColumn']).then(r => r[0].some)
SELECT.from(Foos).columns(['entityIDColumn', 'parentIDColumn']).then(r => r[0].ref)
Expand Down Expand Up @@ -319,3 +324,30 @@ UPSERT.into({} as Definition, { x : 4, "other": 5 }, { a : 4})
UPSERT.into({} as Definition, [{ x : 4, "other": 5 }])

UPSERT.into({} as linked.classes.entity, { "a": 4 })

// UPDATE: checks with typed classes
UPDATE(Foo, 42).set({ x: 4}).where({ x: 44 })
UPDATE(Foos, 42).set({ x: 4}).where({ x: 44 })
// @ts-expect-error - invalid property of Foo
UPDATE(Foos, 4).set({ aa: 4 });
// @ts-expect-error - invalid property type of Foo.x
UPDATE(Foo).where({ x: 4 }).set({ x: 'asdf', ref: { x: 4 }})
UPDATE(Foos).where({ x: 4 }).set({ x: 4, ref: { x: 4 }})
UPDATE.entity(Foos).set({ x: 4});

UPDATE.entity(Foos).set({
x: {'+=': 4 },
ref: { x: 5 },
y: { xpr: [{ ref: ["asdf"] }, "||", "asdf"] }
});

// @ts-expect-error - invalid operator of qbe expression
UPDATE(Foo).with({x: {'--': 4}})

// @ts-expect-error - invalid property name of xpr
UPDATE(Foos).with({x: {xpr: [{funcs: ""}]}})

// untyped, no syntax errors
UPDATE.entity("Foos").set({ test: {xp: "4"} });
UPDATE.entity({} as linked.classes.entity).set({ asdf: 434 });
UPDATE`Foos`.set`x = 4`.where`x > 10`
1 change: 1 addition & 0 deletions test/typescript/apis/project/dummy.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
export class Foo {
static readonly drafts: typeof Foo
x: number = 42
y?: string
ref?: Foo
refs?: Foo[]
}
Expand Down

0 comments on commit ab8d73b

Please sign in to comment.