@cap-js/cds-typer 0.4.0 → 0.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -4,7 +4,20 @@ All notable changes to this project will be documented in this file.
4
4
  This project adheres to [Semantic Versioning](http://semver.org/).
5
5
  The format is based on [Keep a Changelog](http://keepachangelog.com/).
6
6
 
7
- ## Version 0.4.1 - TBD
7
+ ## Version 0.5.1 - TBD
8
+
9
+ ## Version 0.5.0 - 2023-07-25
10
+
11
+ ### Changed
12
+ - 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
13
+
14
+ ### Added
15
+ - Support for `array of` syntax
16
+
17
+ ### Fixes
18
+ - Generate `string` type for date-related types in CDS definitions
19
+ - Generate `Buffer | string` type for the CDS type `LargeBinary`
20
+
8
21
 
9
22
  ## Version 0.4.0 - 2023-07-06
10
23
  ### Added
package/README.md CHANGED
@@ -1,174 +1,13 @@
1
1
  # CDS type generator for JavaScript
2
2
 
3
+ [![REUSE status](https://api.reuse.software/badge/github.com/cap-js/cds-typer)](https://api.reuse.software/info/github.com/cap-js/cds-typer)
4
+ ![Unit Tests passing](https://github.com/cap-js/cds-typer/actions/workflows/test.yml/badge.svg)
5
+
3
6
  ## About this project
4
7
 
5
8
  Generates `.ts` files for a CDS model to receive code completion in VS Code.
6
9
 
7
-
8
- ## Requirements and Setup
9
- This project is [available as `@cap-js/cds-typer`](https://www.npmjs.com/package/@cap-js/cds-typer) as NPM package.
10
-
11
- ### Usage
12
- 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).
13
-
14
- #### Standalone CLI
15
- Assuming you have the following CDS project structure:
16
-
17
- ```
18
- /home/
19
- ├── mybookshop/
20
- │ ├── db/
21
- │ │ └── schema.cds
22
- │ ├── srv/
23
- │ │ └── service.js
24
- ```
25
-
26
- a typical workflow to generate types for your CDS project could look something like this:
27
-
28
- ```sh
29
- npx @cap-js/cds-typer /home/mybookshop/db/schema.cds --outputDirectory /home/mybookshop/@types
30
- ```
31
-
32
- 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(...)`:
33
-
34
- ```js
35
- // srv/service.js
36
- const { Books } = require('my.bookshop')
37
- ```
38
-
39
- becomes
40
-
41
- ```js
42
- // srv/service.js
43
- const { Books } = require('../@types/mybookshop')
44
- ```
45
-
46
- From that point on you should receive code completion from the type system for `Books`.
47
-
48
- _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.
49
-
50
- _cds-typer_ comes with rudimentary CLI support and a few command line options:
51
-
52
- - `--help`: prints all available parameters.
53
- - `--outputDirectory`: specifies the root directory where all generated files should be put. Defaults to the CWD.
54
- - `--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:
55
-
56
- ```js
57
- // generated .ts file
58
- class Book {
59
- title: string;
60
- }
61
-
62
- // some hook in your service
63
- SELECT(Books, b => {
64
- b.title // 👍 no problem, property exists
65
- b.numberOfPages // ❌ property does not exist
66
- })
67
- ```
68
- 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`.
69
-
70
- - `--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):
71
-
72
- ```
73
- TRACE
74
- DEBUG
75
- INFO
76
- WARNING
77
- ERROR
78
- CRITICAL
79
- NONE
80
- ```
81
-
82
- 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.:
83
-
84
- ```sh
85
- npx @cap-js/cds-typer ./path/to/my/model/model.cds --outputDirectory /tmp/
86
- ```
87
-
88
- 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.
89
-
90
- #### From VSCode
91
- 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.
92
- #### Programmatically
93
- 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:
94
-
95
- - `compileFromFile(…)` to parse a `.cds` file. This involves compiling it to CSN first.
96
- - `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.
97
-
98
-
99
- ### Features
100
- #### Plural Types
101
- 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:
102
-
103
- ```cds
104
- entity Books : cuid {
105
-
106
- }
107
- ```
108
-
109
- becomes
110
-
111
- ```ts
112
- class Book {
113
-
114
- }
115
-
116
- class Books extends Array<Book> {}
117
- ```
118
-
119
- 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:
120
-
121
- ```cds
122
- @singular: 'Mouse'
123
- entity Mice {}
124
-
125
- @plural: 'SomeListList'
126
- entity SomeList {}
127
- ```
128
-
129
- results in
130
-
131
- ```ts
132
- class Mouse { … }
133
- class Mice extends Array<Mouse> { … }
134
-
135
- class SomeList { … }
136
- class SomeListList extends Array<SomeList> { … }
137
- ```
138
-
139
- ### Relation to _cds2types_
140
- This project is inspired by the existing [_cds2types_](https://github.com/mrbandler/cds2types), but differs in a few aspects:
141
-
142
- #### Reworked Imports
143
- 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:
144
-
145
- ```js
146
- const types = require('./cds2types/compiled.d.ts')
147
-
148
- console.log(types.sap.cap.bookshop.Books) // a class
149
- ```
150
-
151
- becomes
152
-
153
- ```js
154
- const { Books } = require('./cds-typer/sap/cap/bookshop')
155
-
156
- console.log(Books) // the same class
157
- ```
158
-
159
- #### Usable in Javascript Projects
160
- 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.
161
-
162
- #### Faster
163
- _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_.
164
-
165
- #### Small Footprint
166
- _cds-typer_ tries to keep its dependency footprint as small as possible. Libraries like `typescript` are only needed as dev dependencies.
167
-
168
-
169
-
170
-
171
-
10
+ Exhaustive documentation can be found on [CAPire](https://cap.cloud.sap/docs/tools/cds-typer).
172
11
 
173
12
  ## Support, Feedback, Contributing
174
13
 
@@ -176,8 +15,8 @@ This project is open to feature requests/suggestions, bug reports etc. via [GitH
176
15
 
177
16
  ## Code of Conduct
178
17
 
179
- 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.
18
+ 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.
180
19
 
181
20
  ## Licensing
182
21
 
183
- 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).
22
+ 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).
package/lib/cli.js CHANGED
@@ -6,80 +6,82 @@ const { compileFromFile } = require('./compile')
6
6
  const { parseCommandlineArgs } = require('./util')
7
7
  const { Levels } = require('./logging')
8
8
  const path = require('path')
9
+ const { EOL } = require('node:os')
9
10
 
11
+ const EOL2 = EOL + EOL
10
12
  const toolName = 'cds-typer'
11
13
 
12
14
  const flags = {
13
- // FIXME: remove asap
14
- rootDir: {
15
- desc: '[DEPRICATED] use outputDirectory instead',
16
- default: './',
17
- },
18
15
  outputDirectory: {
19
- desc: 'root directory to write generated files to',
16
+ desc: 'Root directory to write the generated files to.',
20
17
  default: './',
18
+ type: 'string'
21
19
  },
22
20
  help: {
23
- desc: 'this text',
21
+ desc: 'This text.',
24
22
  },
25
23
  logLevel: {
26
- desc: `minimum log level`,
24
+ desc: `Minimum log level that is printed.`,
27
25
  allowed: Object.keys(Levels),
28
26
  default: Object.keys(Levels).at(-1),
29
27
  },
30
28
  jsConfigPath: {
31
- 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.`,
29
+ 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.`,
30
+ type: 'string'
32
31
  },
33
32
  version: {
34
- desc: 'prints the version of this tool'
33
+ desc: 'Prints the version of this tool.'
35
34
  },
36
35
  inlineDeclarations: {
37
- desc: 'whether to resolve inline type declarations flat (x_a, x_b, ...) or structured (x: {a, b})',
36
+ desc: `Whether to resolve inline type declarations${EOL}flat: (x_a, x_b, ...)${EOL}or structured: (x: {a, b}).`,
38
37
  allowed: ['flat', 'structured'],
39
38
  default: 'structured'
40
39
  },
41
40
  propertiesOptional: {
42
- desc: 'if set to true, properties in entities are always generated as optional (a?: T)',
41
+ desc: `If set to true, properties in entities are${EOL}always generated as optional (a?: T).`,
43
42
  allowed: ['true', 'false'],
44
43
  default: 'true'
45
44
  }
46
45
  }
47
46
 
48
47
  const hint = () => console.log('Missing or invalid parameter(s). Call with --help for more details.')
48
+ const indent = (s, indentation) => s.split(EOL).map(line => `${indentation}${line}`).join(EOL)
49
49
 
50
- const help = () =>
51
- console.log(
52
- '[SYNOPSIS]\n' +
53
- 'Call with at least one positional parameter pointing to the (root) CDS file you want to compile.\n' +
54
- 'Additionaly, you can use the following parameters:\n' +
50
+ const help = () => `SYNOPSIS${EOL2}` +
51
+ indent(`cds-typer [cds file | "*"]`, ' ') + EOL2 +
52
+ 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 +
53
+ `OPTIONS${EOL2}` +
55
54
  Object.entries(flags)
56
55
  .sort()
57
56
  .map(([key, value]) => {
58
- let s = `--${key}: ${value.desc}`
57
+ let s = indent(`--${key}`, ' ')
59
58
  if (value.allowed) {
60
- s += ` [allowed: ${value.allowed.join(' | ')}]`
59
+ s += `: <${value.allowed.join(' | ')}>`
60
+ } else if (value.type) {
61
+ s += `: <${value.type}>`
61
62
  }
62
63
  if (value.default) {
63
- s += ` (default: ${value.default})`
64
+ s += EOL
65
+ s += indent(`(default: ${value.default})`, ' ')
64
66
  }
67
+ s += `${EOL2}${indent(value.desc, ' ')}`
65
68
  return s
66
69
  }
67
- ).join('\n\n')
68
- )
70
+ ).join(EOL2)
69
71
 
70
- const version = () => console.log(require('../package.json').version)
72
+ const version = () => require('../package.json').version
71
73
 
72
74
  const main = async (args) => {
73
75
  if ('help' in args.named) {
74
- help()
76
+ console.log(help())
75
77
  process.exit(0)
76
78
  }
77
79
  if ('version' in args.named) {
78
- version()
80
+ console.log(version())
79
81
  process.exit(0)
80
82
  }
81
83
  if (args.positional.length === 0) {
82
- hint()
84
+ console.log(hint())
83
85
  process.exit(1)
84
86
  }
85
87
  if (args.named.jsConfigPath && !args.named.jsConfigPath.endsWith('jsconfig.json')) {
@@ -98,3 +100,7 @@ const main = async (args) => {
98
100
  if (require.main === module) {
99
101
  main(parseCommandlineArgs(process.argv.slice(2), flags))
100
102
  }
103
+
104
+ function helpToCapire() {
105
+
106
+ }
package/lib/compile.js CHANGED
@@ -8,11 +8,16 @@ const { writeout } = require('./file')
8
8
  const { Logger } = require('./logging')
9
9
  const { Visitor } = require('./visitor')
10
10
 
11
+ /**
12
+ * @typedef {import('./visitor').CompileParameters} CompileParameters
13
+ */
14
+
11
15
  /**
12
16
  * Writes the accompanying jsconfig.json file to the specified paths.
13
17
  * Tries to merge nicely if an existing file is found.
14
18
  * @param path {string} filepath to jsconfig.json.
15
19
  * @param logger {import('./logging').Logger} logger
20
+ * @private
16
21
  */
17
22
  const writeJsConfig = (path, logger) => {
18
23
  let values = {
@@ -40,7 +40,7 @@ const Builtins = {
40
40
  String: 'string',
41
41
  Binary: 'string',
42
42
  LargeString: 'string',
43
- LargeBinary: 'string',
43
+ LargeBinary: 'Buffer | string',
44
44
  Integer: 'number',
45
45
  UInt8: 'number',
46
46
  Int16: 'number',
@@ -52,10 +52,11 @@ const Builtins = {
52
52
  Float: 'number',
53
53
  Double: 'number',
54
54
  Boolean: 'boolean',
55
- Date: 'Date',
56
- DateTime: 'Date',
57
- Time: 'Date',
58
- Timestamp: 'Date',
55
+ // note: the date-related types _can_ be Date in some cases, but let's start with string
56
+ Date: 'string', // yyyy-mm-dd
57
+ DateTime: 'string', // yyyy-mm-dd + time + TZ (precision: seconds
58
+ Time: 'string',
59
+ Timestamp: 'string', // yyy-mm-dd + time + TZ (ms precision)
59
60
  //
60
61
  Composition: 'Array',
61
62
  Association: 'Array'
@@ -212,13 +213,14 @@ class Resolver {
212
213
  {
213
214
  Association: [createToOneAssociation, createToManyAssociation],
214
215
  Composition: [createCompositionOfOne, createCompositionOfMany],
216
+ array: [createArrayOf, createArrayOf]
215
217
  }[element.constructor.name] ?? []
216
218
 
217
219
  if (toOne && toMany) {
218
- const target = typeof element.target === 'string' ? { type: element.target } : element.target
220
+ const target = element.items ?? (typeof element.target === 'string' ? { type: element.target } : element.target)
219
221
  const { singular, plural } = this.resolveAndRequire(target, file).typeInfo.inflection
220
222
  typeName =
221
- cardinality > 1 ? toMany(plural) : toOne(this.visitor.isSelfReference(element.target) ? 'this' : singular)
223
+ cardinality > 1 ? toMany(plural) : toOne(this.visitor.isSelfReference(target) ? 'this' : singular)
222
224
  file.addImport(baseDefinitions.path)
223
225
  }
224
226
  } else {
@@ -258,8 +260,11 @@ class Resolver {
258
260
  typeInfo.inflection = this.inflect(typeInfo)
259
261
  }
260
262
 
261
- if (typeInfo.isArray === true) {
262
- typeName = createArrayOf(typeName)
263
+ // add fallback inflection. Mainly needed for array-of with builtin types.
264
+ // (array-of relies on inflection being present, which is not the case in builtin)
265
+ typeInfo.inflection ??= {
266
+ singular: typeName,
267
+ plural: typeName
263
268
  }
264
269
 
265
270
  // FIXME: typeName could probably just become part of typeInfo
@@ -346,8 +351,8 @@ class Resolver {
346
351
 
347
352
  // objects and arrays
348
353
  if (element?.items) {
349
- // FIXME: builtin = true? arrays are kinda builtin
350
354
  result.isArray = true
355
+ result.isBuiltin = true
351
356
  this.resolveType(element.items, file)
352
357
  //delete element.items
353
358
  } else if (element?.elements && !element?.type) {
package/lib/visitor.js CHANGED
@@ -124,7 +124,7 @@ class Visitor {
124
124
  })
125
125
 
126
126
  // CLASS ASPECT
127
- buffer.add(`export function ${identAspect(clean)}<TBase extends new (...args: any[]) => any>(Base: TBase) {`)
127
+ buffer.add(`export function ${identAspect(clean)}<TBase extends new (...args: any[]) => object>(Base: TBase) {`)
128
128
  buffer.indent()
129
129
  buffer.add(`return class ${clean} extends Base {`)
130
130
  buffer.indent()
@@ -318,7 +318,7 @@ class Visitor {
318
318
  /**
319
319
  * A self reference is a property that references the class it appears in.
320
320
  * They need to be detected on CDS level, as the emitted TS types will try to
321
- * refer to refer to types via their alias that hides the aspectification.
321
+ * refer to types via their alias that hides the aspectification.
322
322
  * If we attempt to directly refer to this alias while it has not been fully created,
323
323
  * that will result in a TS error.
324
324
  * @param {String} entityName
package/package.json CHANGED
@@ -1,8 +1,9 @@
1
1
  {
2
2
  "name": "@cap-js/cds-typer",
3
- "version": "0.4.0",
3
+ "version": "0.5.0",
4
4
  "description": "Generates .ts files for a CDS model to receive code completion in VS Code",
5
5
  "main": "index.js",
6
+ "repository": "github:cap-js/cds-typer",
6
7
  "homepage": "https://cap.cloud.sap/",
7
8
  "keywords": [
8
9
  "CAP",
@@ -17,7 +18,14 @@
17
18
  "test:all": "jest",
18
19
  "test": "npm run test:unit",
19
20
  "lint": "eslint",
20
- "cli": "node lib/cli.js"
21
+ "cli": "node lib/cli.js",
22
+ "doc:clean": "rm -rf ./doc",
23
+ "doc:prepare": "npm run doc:clean && mkdir -p doc/types",
24
+ "doc:typegen": "./node_modules/.bin/tsc ./lib/*.js --skipLibCheck --declaration --allowJs --emitDeclarationOnly --outDir doc/types && cd doc/types && tsc --init",
25
+ "doc:html": "npm run doc:typegen && ./node_modules/.bin/typedoc 'doc/types/**/*.d.ts' --entryPointStrategy expand --out doc/html --tsconfig doc/types/tsconfig.json",
26
+ "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",
27
+ "doc:cli": "npm run cli -- --help > ./doc/cli.txt",
28
+ "doc:full": "npm run doc:prepare && npm run doc:html && npm run doc:cli"
21
29
  },
22
30
  "files": [
23
31
  "lib/",
@@ -39,6 +47,8 @@
39
47
  "eslint-config-prettier": "^8.5.0",
40
48
  "eslint-plugin-prettier": "^4.0.0",
41
49
  "jest": "^29",
50
+ "typedoc": "^0.24.8",
51
+ "typedoc-plugin-markdown": "^3.15.3",
42
52
  "typescript": ">=4.6.4"
43
53
  },
44
54
  "jest": {