diff --git a/CHANGELOG.md b/CHANGELOG.md index b3b1ce7b..23d979e9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -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 diff --git a/apis/env.d.ts b/apis/env.d.ts index ba4a4c84..60ecda8b 100644 --- a/apis/env.d.ts +++ b/apis/env.d.ts @@ -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, @@ -14,6 +16,12 @@ export const env: { [key: string]: any, }, profiles: string[], + log: { + user: boolean, + levels: Record>, + als_custom_fields: Record, + cls_custom_fields: string[], + }, requires: env.Requires, folders: { app: string, diff --git a/apis/internal/query.d.ts b/apis/internal/query.d.ts index ddd614d4..37573672 100644 --- a/apis/internal/query.d.ts +++ b/apis/internal/query.d.ts @@ -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 = (e: QLExtensions : T>) => void @@ -169,7 +169,7 @@ export interface InUpsert { } // don't wrap QLExtensions in more QLExtensions (indirection to work around recursive definition) -export type QLExtensions = T extends QLExtensions_ ? T : QLExtensions_> +export type QLExtensions = T extends QLExtensions_ ? T : QLExtensions_> /** * QLExtensions are properties that are attached to entities in CQL contexts. diff --git a/apis/ql.d.ts b/apis/ql.d.ts index 2b2a9eb1..c3f54386 100644 --- a/apis/ql.d.ts +++ b/apis/ql.d.ts @@ -62,7 +62,7 @@ export declare class QL { & ((...entries: object[]) => UPSERT) & ((entries: object[]) => UPSERT) UPDATE: typeof UPDATE - & typeof UPDATE.entity<_TODO> + & typeof UPDATE.entity DELETE: typeof DELETE & ((...entries: object[]) => DELETE) & ((entries: object[]) => DELETE) @@ -233,34 +233,37 @@ export class DELETE extends ConstructedQuery { DELETE: CQN.DELETE['DELETE'] } +// operator for qbe expression +type QbeOp = '=' | '-=' | '+=' | '*=' | '/=' | '%=' export interface UPDATE extends Where, And, ByKey {} export class UPDATE extends ConstructedQuery { private constructor(); - // cds-typer plural - // FIXME: this returned UPDATE> before. should UPDATE.entity(...) return Book or Books? - static entity (entity: T, primaryKey?: PK): UPDATE> - - static entity (entity: EntityDescription, primaryKey?: PK): UPDATE - - static entity (entity: T, primaryKey?: PK): UPDATE - - // currently no easy way to restrict T from being a primitive type - static entity (entity: T, primaryKey?: PK): UPDATE - - // with (block: (e:T)=>void) : this - // set (block: (e:T)=>void) : this - set: TaggedTemplateQueryPart - & ((data: object) => this); - - with: TaggedTemplateQueryPart - & ((data: object) => this) + static entity: (TaggedTemplateQueryPart>) + // UPDATE> is used here so type inference in set/with has the property keys of the singular type + & ( (entity: T, primaryKey?: PK) => UPDATE>) + & ( (entity: T, primaryKey?: PK) => UPDATE>) + & ((entity: EntityDescription, primaryKey?: PK) => UPDATE) + & ( (entity: T, primaryKey?: PK) => UPDATE) + set: UpdateSet + with: UpdateSet + UPDATE: CQN.UPDATE['UPDATE'] } +/** + * Represents updatable block that can be passed to either `.set` or `.with` + * of an `UPDATE` query + */ +type UpdateSet = TaggedTemplateQueryPart + // 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 extends ConstructedQuery { private constructor(); diff --git a/package-lock.json b/package-lock.json index 1187acd8..309e40cd 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "@cap-js/cds-types", - "version": "0.7.0", + "version": "0.8.0", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "@cap-js/cds-types", - "version": "0.7.0", + "version": "0.8.0", "hasInstallScript": true, "license": "SEE LICENSE IN LICENSE", "dependencies": { @@ -713,10 +713,11 @@ } }, "node_modules/@eslint/js": { - "version": "9.15.0", - "resolved": "https://registry.npmjs.org/@eslint/js/-/js-9.15.0.tgz", - "integrity": "sha512-tMTqrY+EzbXmKJR5ToI8lxu7jaN5EdmrBFJpQk5JmSlyLsx6o4t27r883K5xsLuCYCpfKBCGswMSWXsM+jB7lg==", + "version": "9.16.0", + "resolved": "https://registry.npmjs.org/@eslint/js/-/js-9.16.0.tgz", + "integrity": "sha512-tw2HxzQkrbeuvyj1tG2Yqq+0H9wGoI2IMk4EOsQeX+vmd75FtJAzf+gTA69WF+baUKRYQ3x2kbLE08js5OsTVg==", "dev": true, + "license": "MIT", "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" } @@ -1276,10 +1277,11 @@ } }, "node_modules/@sap/cds": { - "version": "8.4.2", - "resolved": "https://registry.npmjs.org/@sap/cds/-/cds-8.4.2.tgz", - "integrity": "sha512-wHiPU+PciyG6L7oGglUq22ji+aV5SFwef/gF/rnIheMirzxpDlHq+K8GwqmcrrFu3GWRioQGRe3rr8KAalOQrg==", + "version": "8.5.0", + "resolved": "https://registry.npmjs.org/@sap/cds/-/cds-8.5.0.tgz", + "integrity": "sha512-mRiLBPcY5vC1xi21pPqzMtp1HY9FOOFajkiuSArqnwtiyp2fxqWVombuVE9lTB2UyBl9bL8XXwrpjzDftIbnBg==", "dev": true, + "license": "SEE LICENSE IN LICENSE", "dependencies": { "@sap/cds-compiler": ">=5.1", "@sap/cds-fiori": "^1", @@ -1548,9 +1550,9 @@ "integrity": "sha512-/pyBZWSLD2n0dcHE3hq8s8ZvcETHtEuF+3E7XVt0Ig2nvsVQXdghHVcEkIWjy9A0wKfTn97a/PSDYohKIlnP/w==" }, "node_modules/@types/node": { - "version": "22.10.0", - "resolved": "https://registry.npmjs.org/@types/node/-/node-22.10.0.tgz", - "integrity": "sha512-XC70cRZVElFHfIUB40FgZOBbgJYFKKMa5nb9lxcwYstFG/Mi+/Y0bGS+rs6Dmhmkpq4pnNiLiuZAbc02YCOnmA==", + "version": "22.10.1", + "resolved": "https://registry.npmjs.org/@types/node/-/node-22.10.1.tgz", + "integrity": "sha512-qKgsUwfHZV2WCWLAnVP1JqnpE6Im6h3Y0+fYgMTasNQ7V++CBX5OT1as0g0f+OyubbFqhf6XVNIsmN4IIhEgGQ==", "license": "MIT", "dependencies": { "undici-types": "~6.20.0" @@ -2848,17 +2850,18 @@ } }, "node_modules/eslint": { - "version": "9.15.0", - "resolved": "https://registry.npmjs.org/eslint/-/eslint-9.15.0.tgz", - "integrity": "sha512-7CrWySmIibCgT1Os28lUU6upBshZ+GxybLOrmRzi08kS8MBuO8QA7pXEgYgY5W8vK3e74xv0lpjo9DbaGU9Rkw==", + "version": "9.16.0", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-9.16.0.tgz", + "integrity": "sha512-whp8mSQI4C8VXd+fLgSM0lh3UlmcFtVwUQjyKCFfsp+2ItAIYhlq/hqGahGqHE6cv9unM41VlqKk2VtKYR2TaA==", "dev": true, + "license": "MIT", "dependencies": { "@eslint-community/eslint-utils": "^4.2.0", "@eslint-community/regexpp": "^4.12.1", "@eslint/config-array": "^0.19.0", "@eslint/core": "^0.9.0", "@eslint/eslintrc": "^3.2.0", - "@eslint/js": "9.15.0", + "@eslint/js": "9.16.0", "@eslint/plugin-kit": "^0.2.3", "@humanfs/node": "^0.16.6", "@humanwhocodes/module-importer": "^1.0.1", diff --git a/test/typescript/apis/project/cds-env.ts b/test/typescript/apis/project/cds-env.ts index 433e8e92..83d92aee 100644 --- a/test/typescript/apis/project/cds-env.ts +++ b/test/typescript/apis/project/cds-env.ts @@ -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 = '' diff --git a/test/typescript/apis/project/cds-ql.ts b/test/typescript/apis/project/cds-ql.ts index 4b1e8220..34bc327c 100644 --- a/test/typescript/apis/project/cds-ql.ts +++ b/test/typescript/apis/project/cds-ql.ts @@ -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 +let upd: UPDATE 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 @@ -215,6 +215,11 @@ SELECT.from(Foos).columns(f => { const number: QLExtensions = 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) @@ -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` \ No newline at end of file diff --git a/test/typescript/apis/project/dummy.ts b/test/typescript/apis/project/dummy.ts index 9f0762e4..f072d3ae 100644 --- a/test/typescript/apis/project/dummy.ts +++ b/test/typescript/apis/project/dummy.ts @@ -6,6 +6,7 @@ export class Foo { static readonly drafts: typeof Foo x: number = 42 + y?: string ref?: Foo refs?: Foo[] }