@cap-js/cds-typer
Advanced tools
Comparing version 0.4.0 to 0.5.0
@@ -7,4 +7,17 @@ # Change Log | ||
## Version 0.4.1 - TBD | ||
## Version 0.5.1 - TBD | ||
## Version 0.5.0 - 2023-07-25 | ||
### Changed | ||
- Facilitate strict property checks. Note: `checkJs: true` must be present in the project's _jsconfig.json_ or _tsconfig.json_ respectively for this feature to become effective | ||
### Added | ||
- Support for `array of` syntax | ||
### Fixes | ||
- Generate `string` type for date-related types in CDS definitions | ||
- Generate `Buffer | string` type for the CDS type `LargeBinary` | ||
## Version 0.4.0 - 2023-07-06 | ||
@@ -11,0 +24,0 @@ ### Added |
@@ -9,20 +9,18 @@ #!/usr/bin/env node | ||
const path = require('path') | ||
const { EOL } = require('node:os') | ||
const EOL2 = EOL + EOL | ||
const toolName = 'cds-typer' | ||
const flags = { | ||
// FIXME: remove asap | ||
rootDir: { | ||
desc: '[DEPRICATED] use outputDirectory instead', | ||
default: './', | ||
}, | ||
outputDirectory: { | ||
desc: 'root directory to write generated files to', | ||
desc: 'Root directory to write the generated files to.', | ||
default: './', | ||
type: 'string' | ||
}, | ||
help: { | ||
desc: 'this text', | ||
desc: 'This text.', | ||
}, | ||
logLevel: { | ||
desc: `minimum log level`, | ||
desc: `Minimum log level that is printed.`, | ||
allowed: Object.keys(Levels), | ||
@@ -32,9 +30,10 @@ default: Object.keys(Levels).at(-1), | ||
jsConfigPath: { | ||
desc: `Path to where the jsconfig.json should be written. If specified, ${toolName} will create a jsconfig.json file and set it up to restrict property usage in types entities to existing properties only.`, | ||
desc: `Path to where the jsconfig.json should be written.${EOL}If specified, ${toolName} will create a jsconfig.json file and${EOL}set it up to restrict property usage in types entities to${EOL}existing properties only.`, | ||
type: 'string' | ||
}, | ||
version: { | ||
desc: 'prints the version of this tool' | ||
desc: 'Prints the version of this tool.' | ||
}, | ||
inlineDeclarations: { | ||
desc: 'whether to resolve inline type declarations flat (x_a, x_b, ...) or structured (x: {a, b})', | ||
desc: `Whether to resolve inline type declarations${EOL}flat: (x_a, x_b, ...)${EOL}or structured: (x: {a, b}).`, | ||
allowed: ['flat', 'structured'], | ||
@@ -44,3 +43,3 @@ default: 'structured' | ||
propertiesOptional: { | ||
desc: 'if set to true, properties in entities are always generated as optional (a?: T)', | ||
desc: `If set to true, properties in entities are${EOL}always generated as optional (a?: T).`, | ||
allowed: ['true', 'false'], | ||
@@ -52,36 +51,39 @@ default: 'true' | ||
const hint = () => console.log('Missing or invalid parameter(s). Call with --help for more details.') | ||
const indent = (s, indentation) => s.split(EOL).map(line => `${indentation}${line}`).join(EOL) | ||
const help = () => | ||
console.log( | ||
'[SYNOPSIS]\n' + | ||
'Call with at least one positional parameter pointing to the (root) CDS file you want to compile.\n' + | ||
'Additionaly, you can use the following parameters:\n' + | ||
const help = () => `SYNOPSIS${EOL2}` + | ||
indent(`cds-typer [cds file | "*"]`, ' ') + EOL2 + | ||
indent(`Generates type information based on a CDS model.${EOL}Call with at least one positional parameter pointing${EOL}to the (root) CDS file you want to compile.`, ' ') + EOL2 + | ||
`OPTIONS${EOL2}` + | ||
Object.entries(flags) | ||
.sort() | ||
.map(([key, value]) => { | ||
let s = `--${key}: ${value.desc}` | ||
let s = indent(`--${key}`, ' ') | ||
if (value.allowed) { | ||
s += ` [allowed: ${value.allowed.join(' | ')}]` | ||
s += `: <${value.allowed.join(' | ')}>` | ||
} else if (value.type) { | ||
s += `: <${value.type}>` | ||
} | ||
if (value.default) { | ||
s += ` (default: ${value.default})` | ||
s += EOL | ||
s += indent(`(default: ${value.default})`, ' ') | ||
} | ||
s += `${EOL2}${indent(value.desc, ' ')}` | ||
return s | ||
} | ||
).join('\n\n') | ||
) | ||
).join(EOL2) | ||
const version = () => console.log(require('../package.json').version) | ||
const version = () => require('../package.json').version | ||
const main = async (args) => { | ||
if ('help' in args.named) { | ||
help() | ||
console.log(help()) | ||
process.exit(0) | ||
} | ||
if ('version' in args.named) { | ||
version() | ||
console.log(version()) | ||
process.exit(0) | ||
} | ||
if (args.positional.length === 0) { | ||
hint() | ||
console.log(hint()) | ||
process.exit(1) | ||
@@ -105,1 +107,5 @@ } | ||
} | ||
function helpToCapire() { | ||
} |
@@ -12,2 +12,6 @@ 'use strict' | ||
/** | ||
* @typedef {import('./visitor').CompileParameters} CompileParameters | ||
*/ | ||
/** | ||
* Writes the accompanying jsconfig.json file to the specified paths. | ||
@@ -17,2 +21,3 @@ * Tries to merge nicely if an existing file is found. | ||
* @param logger {import('./logging').Logger} logger | ||
* @private | ||
*/ | ||
@@ -19,0 +24,0 @@ const writeJsConfig = (path, logger) => { |
@@ -43,3 +43,3 @@ 'use strict' | ||
LargeString: 'string', | ||
LargeBinary: 'string', | ||
LargeBinary: 'Buffer | string', | ||
Integer: 'number', | ||
@@ -56,6 +56,7 @@ UInt8: 'number', | ||
Boolean: 'boolean', | ||
Date: 'Date', | ||
DateTime: 'Date', | ||
Time: 'Date', | ||
Timestamp: 'Date', | ||
// note: the date-related types _can_ be Date in some cases, but let's start with string | ||
Date: 'string', // yyyy-mm-dd | ||
DateTime: 'string', // yyyy-mm-dd + time + TZ (precision: seconds | ||
Time: 'string', | ||
Timestamp: 'string', // yyy-mm-dd + time + TZ (ms precision) | ||
// | ||
@@ -217,9 +218,10 @@ Composition: 'Array', | ||
Composition: [createCompositionOfOne, createCompositionOfMany], | ||
array: [createArrayOf, createArrayOf] | ||
}[element.constructor.name] ?? [] | ||
if (toOne && toMany) { | ||
const target = typeof element.target === 'string' ? { type: element.target } : element.target | ||
const target = element.items ?? (typeof element.target === 'string' ? { type: element.target } : element.target) | ||
const { singular, plural } = this.resolveAndRequire(target, file).typeInfo.inflection | ||
typeName = | ||
cardinality > 1 ? toMany(plural) : toOne(this.visitor.isSelfReference(element.target) ? 'this' : singular) | ||
cardinality > 1 ? toMany(plural) : toOne(this.visitor.isSelfReference(target) ? 'this' : singular) | ||
file.addImport(baseDefinitions.path) | ||
@@ -264,4 +266,7 @@ } | ||
if (typeInfo.isArray === true) { | ||
typeName = createArrayOf(typeName) | ||
// add fallback inflection. Mainly needed for array-of with builtin types. | ||
// (array-of relies on inflection being present, which is not the case in builtin) | ||
typeInfo.inflection ??= { | ||
singular: typeName, | ||
plural: typeName | ||
} | ||
@@ -353,4 +358,4 @@ | ||
if (element?.items) { | ||
// FIXME: builtin = true? arrays are kinda builtin | ||
result.isArray = true | ||
result.isBuiltin = true | ||
this.resolveType(element.items, file) | ||
@@ -357,0 +362,0 @@ //delete element.items |
@@ -127,3 +127,3 @@ 'use strict' | ||
// CLASS ASPECT | ||
buffer.add(`export function ${identAspect(clean)}<TBase extends new (...args: any[]) => any>(Base: TBase) {`) | ||
buffer.add(`export function ${identAspect(clean)}<TBase extends new (...args: any[]) => object>(Base: TBase) {`) | ||
buffer.indent() | ||
@@ -322,3 +322,3 @@ buffer.add(`return class ${clean} extends Base {`) | ||
* They need to be detected on CDS level, as the emitted TS types will try to | ||
* refer to refer to types via their alias that hides the aspectification. | ||
* refer to types via their alias that hides the aspectification. | ||
* If we attempt to directly refer to this alias while it has not been fully created, | ||
@@ -325,0 +325,0 @@ * that will result in a TS error. |
{ | ||
"name": "@cap-js/cds-typer", | ||
"version": "0.4.0", | ||
"version": "0.5.0", | ||
"description": "Generates .ts files for a CDS model to receive code completion in VS Code", | ||
"main": "index.js", | ||
"repository": "github:cap-js/cds-typer", | ||
"homepage": "https://cap.cloud.sap/", | ||
@@ -20,3 +21,10 @@ "keywords": [ | ||
"lint": "eslint", | ||
"cli": "node lib/cli.js" | ||
"cli": "node lib/cli.js", | ||
"doc:clean": "rm -rf ./doc", | ||
"doc:prepare": "npm run doc:clean && mkdir -p doc/types", | ||
"doc:typegen": "./node_modules/.bin/tsc ./lib/*.js --skipLibCheck --declaration --allowJs --emitDeclarationOnly --outDir doc/types && cd doc/types && tsc --init", | ||
"doc:html": "npm run doc:typegen && ./node_modules/.bin/typedoc 'doc/types/**/*.d.ts' --entryPointStrategy expand --out doc/html --tsconfig doc/types/tsconfig.json", | ||
"doc:md": "npm run doc:typegen && ./node_modules/.bin/typedoc --plugin typedoc-plugin-markdown 'doc/types/compile.d.ts' --out doc/md --tsconfig doc/types/tsconfig.json", | ||
"doc:cli": "npm run cli -- --help > ./doc/cli.txt", | ||
"doc:full": "npm run doc:prepare && npm run doc:html && npm run doc:cli" | ||
}, | ||
@@ -43,2 +51,4 @@ "files": [ | ||
"jest": "^29", | ||
"typedoc": "^0.24.8", | ||
"typedoc-plugin-markdown": "^3.15.3", | ||
"typescript": ">=4.6.4" | ||
@@ -45,0 +55,0 @@ }, |
173
README.md
# CDS type generator for JavaScript | ||
[![REUSE status](https://api.reuse.software/badge/github.com/cap-js/cds-typer)](https://api.reuse.software/info/github.com/cap-js/cds-typer) | ||
![Unit Tests passing](https://github.com/cap-js/cds-typer/actions/workflows/test.yml/badge.svg) | ||
## About this project | ||
@@ -7,168 +10,4 @@ | ||
Exhaustive documentation can be found on [CAPire](https://cap.cloud.sap/docs/tools/cds-typer). | ||
## Requirements and Setup | ||
This project is [available as `@cap-js/cds-typer`](https://www.npmjs.com/package/@cap-js/cds-typer) as NPM package. | ||
### Usage | ||
The type generator can either be used as a standalone tool, or as part of of the [CDS VSCode-Extension](https://www.npmjs.com/package/@sap/vscode-cds). | ||
#### Standalone CLI | ||
Assuming you have the following CDS project structure: | ||
``` | ||
/home/ | ||
├── mybookshop/ | ||
│ ├── db/ | ||
│ │ └── schema.cds | ||
│ ├── srv/ | ||
│ │ └── service.js | ||
``` | ||
a typical workflow to generate types for your CDS project could look something like this: | ||
```sh | ||
npx @cap-js/cds-typer /home/mybookshop/db/schema.cds --outputDirectory /home/mybookshop/@types | ||
``` | ||
You would then end up with a directory `@types`, which contains your entities and their accompanying types in a directory structure. The directory structure directly reflects the namespaces you have defined your entities in. They have to be imported in any JavaScript-based service handlers you want to have type support in and can replace calls to `cds.entities(...)`: | ||
```js | ||
// srv/service.js | ||
const { Books } = require('my.bookshop') | ||
``` | ||
becomes | ||
```js | ||
// srv/service.js | ||
const { Books } = require('../@types/mybookshop') | ||
``` | ||
From that point on you should receive code completion from the type system for `Books`. | ||
_Note:_ the above command generates types for the model contained within the mentioned `schema.cds` file. If you have multiple `.cds` files that are included via `using` statements by `schema.cds`, then those files will also be included in the type generation process. If you have `.cds` files that are _not_ in some way included in `schema.cds`, you have to explicitly pass those as positional argument as well, if you want types for them. | ||
_cds-typer_ comes with rudimentary CLI support and a few command line options: | ||
- `--help`: prints all available parameters. | ||
- `--outputDirectory`: specifies the root directory where all generated files should be put. Defaults to the CWD. | ||
- `--jsConfigPath`: specifies the path to the `jsconfig.json` file to generate. Usually your project's root directory. If specified, a config file is created that restricts the usage of types even further: | ||
```js | ||
// generated .ts file | ||
class Book { | ||
title: string; | ||
} | ||
// some hook in your service | ||
SELECT(Books, b => { | ||
b.title // 👍 no problem, property exists | ||
b.numberOfPages // ❌ property does not exist | ||
}) | ||
``` | ||
With the generated config in place, the language server will display an error, telling you that `numberOfPages` does not exist in this context. Without the config it would just infer it as `any`. | ||
- `--loglevel`: minimum log level that should be printed. Defaults to `NONE`. Available log levels roughly follow [Microsoft's dotnet log levels](https://docs.microsoft.com/en-us/dotnet/api/microsoft.extensions.logging.loglevel?view=dotnet-plat-ext-6.0): | ||
``` | ||
TRACE | ||
DEBUG | ||
INFO | ||
WARNING | ||
ERROR | ||
CRITICAL | ||
NONE | ||
``` | ||
The utility expects (at least) one path to a `.cds` file as positional parameter which serves as entry point to the model in question, e.g.: | ||
```sh | ||
npx @cap-js/cds-typer ./path/to/my/model/model.cds --outputDirectory /tmp/ | ||
``` | ||
Note that you can also pass multiple paths or `"*"` as glob pattern (with quotes to circumvent expansion by the shell). This passes the pattern on to the compiler where the [regular resolve strategy](https://cap.cloud.sap/docs/node.js/cds-compile?q=compiler#cds-resolve) is used. | ||
#### From VSCode | ||
Installing the [CDS VSCode Extension](https://www.npmjs.com/package/@sap/vscode-cds) also adds support for generating types for your model from within VSCode. Adding the appropriate facet to your project via `cds add typer` (and installing the added dependencies thereafter) allows you to simply hit save on any `.cds` file that is part of your model to trigger the generation process. | ||
#### Programmatically | ||
The main API for using _cds-typer_ within another project is contained in [`compile.js`](https://github.tools.sap/cap/cds-typer/blob/master/lib/compile.js), specifically: | ||
- `compileFromFile(…)` to parse a `.cds` file. This involves compiling it to CSN first. | ||
- `compileFromCSN(…)` to directly compile from CSN object. This is useful when you already have your CSN available as part of a tool chain. ⚠️ **WARNING**: the application of `cdstyper` may be impure, meaning that it _could_ alter the provided CSN. If you use the typer this way, you may want to apply it as last step of your tool chain. | ||
### Features | ||
#### Plural Types | ||
While CDS encourages the use of plural form for defined entities, their OOP equivalent classes are usually named in singular. _cds-typer_ automatically transforms entity names to singular and adds the plural form for arrays: | ||
```cds | ||
entity Books : cuid { | ||
… | ||
} | ||
``` | ||
becomes | ||
```ts | ||
class Book { | ||
… | ||
} | ||
class Books extends Array<Book> {} | ||
``` | ||
If you need to customise the singular or plural form, or if your entities are already in singular form, you can do so using annotations: | ||
```cds | ||
@singular: 'Mouse' | ||
entity Mice {} | ||
@plural: 'SomeListList' | ||
entity SomeList {} | ||
``` | ||
results in | ||
```ts | ||
class Mouse { … } | ||
class Mice extends Array<Mouse> { … } | ||
class SomeList { … } | ||
class SomeListList extends Array<SomeList> { … } | ||
``` | ||
### Relation to _cds2types_ | ||
This project is inspired by the existing [_cds2types_](https://github.com/mrbandler/cds2types), but differs in a few aspects: | ||
#### Reworked Imports | ||
Instead of one monolithic `.d.ts` file containing all entities in nested namespaces, multiple files are generated where each namespace is represented by a directory structure. This facilicates simpler imports in a more Java-esque style: | ||
```js | ||
const types = require('./cds2types/compiled.d.ts') | ||
console.log(types.sap.cap.bookshop.Books) // a class | ||
``` | ||
becomes | ||
```js | ||
const { Books } = require('./cds-typer/sap/cap/bookshop') | ||
console.log(Books) // the same class | ||
``` | ||
#### Usable in Javascript Projects | ||
Generated code is usable from within plain Javascript projects. The code generated by _cds2types_ would represent each cds-entity as an interface, which are not visible to Javascript projects. _cds-typer_ uses classes instead. | ||
#### Faster | ||
_cds2types_ takes a detour to create a Typescript AST first and then print out the formatted source files. _cds-typer_ directly walks the linked CSN and creates strings on the fly. Also, file operations are `async`. These two changes speed up _cds-typer_ by around one to two orders of magnitude compared to _cds2types_. | ||
#### Small Footprint | ||
_cds-typer_ tries to keep its dependency footprint as small as possible. Libraries like `typescript` are only needed as dev dependencies. | ||
## Support, Feedback, Contributing | ||
@@ -180,6 +19,6 @@ | ||
We as members, contributors, and leaders pledge to make participation in our community a harassment-free experience for everyone. By participating in this project, you agree to abide by its [Code of Conduct](CODE_OF_CONDUCT.md) at all times. | ||
We as members, contributors, and leaders pledge to make participation in our community a harassment-free experience for everyone. By participating in this project, you agree to abide by its [Code of Conduct](https://github.com/cap-js/.github/blob/main/CODE_OF_CONDUCT.md) at all times. | ||
## Licensing | ||
Copyright 2022-2022 SAP SE or an SAP affiliate company and cds-dts-generator contributors. Please see our [LICENSE](LICENSE) for copyright and license information. Detailed information including third-party components and their licensing/copyright information is available [via the REUSE tool](https://api.reuse.software/info/github.com/SAP/cds-dts-generator). | ||
Copyright 2022-2022 SAP SE or an SAP affiliate company and cds-typer contributors. Please see our [LICENSE](LICENSE) for copyright and license information. Detailed information including third-party components and their licensing/copyright information is available [via the REUSE tool](https://api.reuse.software/info/github.com/SAP/cds-dts-generator). |
No repository
Supply chain riskPackage does not have a linked source code repository. Without this field, a package will have no reference to the location of the source code use to generate the package.
Found 1 instance in 1 package
1954
95686
7
23