@cap-js/cds-typer 0.28.1 → 0.29.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,6 +4,15 @@ 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.30.0 - TBD
8
+
9
+ ## Version 0.29.0 - 2024-11-20
10
+ ### Added
11
+ - [breaking] cds-typer now tries to automatically detect whether it has to generate ESM or CommonJS in the emitted _index.js_ files. This behaviour can be overridden via the `--targetModuleType` option. _If you rely on these generated index.js files to be CJS despite your project being of ESM type, you need to manually tell cds-typer to generate CJS files!_
12
+
13
+ ### Fixed
14
+ - The static `.keys` property now properly reels in key types from inherited classes.
15
+
7
16
  ## Version 0.28.1 - 2024-11-07
8
17
  ### Fixed
9
18
  - `cds build` no longer fails on Windows with an `EINVAL` error.
package/lib/cli.js CHANGED
@@ -190,7 +190,12 @@ const flags = enrichFlagSchema({
190
190
  IEEE754Compatible: parameterTypes.boolean({
191
191
  desc: `If set to true, floating point properties are generated${EOL}as IEEE754 compatible '(number | string)' instead of 'number'.`,
192
192
  default: 'false'
193
- })
193
+ }),
194
+ targetModuleType: {
195
+ desc: `Output format for generated .js files.${EOL}Setting it to auto tries to derive the module type from${EOL}the package.json and falls back to CJS.`,
196
+ allowed: ['esm', 'cjs', 'auto'],
197
+ default: 'auto'
198
+ }
194
199
  })
195
200
 
196
201
  const hint = () => 'Missing or invalid parameter(s). Call with --help for more details.'
@@ -1,7 +1,7 @@
1
1
  const { configuration } = require('../config')
2
2
  const { SourceFile, Buffer } = require('../file')
3
3
  const { normalise } = require('./identifier')
4
- const { docify } = require('./wrappers')
4
+ const { docify } = require('../printers/wrappers')
5
5
 
6
6
  /** @typedef {import('../resolution/resolver').TypeResolveInfo} TypeResolveInfo */
7
7
  /** @typedef {import('../resolution/resolver').TypeResolveOptions} TypeResolverOptions */
package/lib/config.js CHANGED
@@ -1,5 +1,6 @@
1
1
  const cds = require('@sap/cds')
2
- const { camelToSnake } = require('./util')
2
+ const { camelToSnake, getProjectTargetType } = require('./util')
3
+ const { LOG } = require('./logging')
3
4
 
4
5
  /**
5
6
  * Makes properties of an object accessible in both camelCase and snake_case.
@@ -35,7 +36,8 @@ class Config {
35
36
  propertiesOptional: true,
36
37
  useEntitiesProxy: false,
37
38
  inlineDeclarations: 'flat',
38
- outputDirectory: '.'
39
+ outputDirectory: '.',
40
+ targetModuleType: 'auto'
39
41
  }
40
42
 
41
43
  values = undefined
@@ -44,6 +46,16 @@ class Config {
44
46
  init () {
45
47
  this.values = {...Config.#defaults, ...(cds.env.typer ?? {})}
46
48
  this.proxy = camelSnakeHybrid(this.values)
49
+ if (this.values.targetModuleType === 'auto') {
50
+ const type = getProjectTargetType(cds.root)
51
+ if (type) {
52
+ LOG.info(`automatically detected module type '${type}' in ${cds.root}`)
53
+ this.values.targetModuleType = type
54
+ } else {
55
+ LOG.warn(`target module type was set to 'auto', but could not detect module type in ${cds.root}. Falling back to cjs`)
56
+ this.values.targetModuleType = 'cjs'
57
+ }
58
+ }
47
59
  }
48
60
 
49
61
  constructor() {
package/lib/file.js CHANGED
@@ -7,8 +7,10 @@ const { printEnum, propertyToInlineEnumName, stringifyEnumImplementation } = req
7
7
  const { normalise } = require('./components/identifier')
8
8
  const { empty } = require('./components/typescript')
9
9
  const { proxyAccessFunction } = require('./components/javascript')
10
- const { createObjectOf } = require('./components/wrappers')
10
+ const { createObjectOf, stringIdent } = require('./printers/wrappers')
11
11
  const { configuration } = require('./config')
12
+ const { CJSPrinter, ESMPrinter } = require('./printers/javascript')
13
+ const { getProjectTargetType } = require('./util')
12
14
 
13
15
  const AUTO_GEN_NOTE = '// This is an automatically generated file. Please do not change its contents manually!'
14
16
 
@@ -144,6 +146,10 @@ class SourceFile extends File {
144
146
  this.services = { buffer: new Buffer(), names: [] }
145
147
  /** @type {Record<string,string[]>} */
146
148
  this.entityProxies = {}
149
+ /** @type {import('../lib/printers/javascript').Printer} */
150
+ this.jsPrinter = configuration.targetModuleType === 'esm'
151
+ ? new ESMPrinter()
152
+ : new CJSPrinter()
147
153
  }
148
154
 
149
155
  /**
@@ -428,7 +434,7 @@ class SourceFile extends File {
428
434
  ].filter(Boolean).join('\n')
429
435
  }
430
436
  #getEntityProxyFunctionExport() {
431
- return `module.exports.createEntityProxy = ${proxyAccessFunction}`
437
+ return this.jsPrinter.printExport('createEntityProxy', proxyAccessFunction)
432
438
  }
433
439
  /**
434
440
  * Returns boilerplate code for `index.js` files
@@ -438,18 +444,23 @@ class SourceFile extends File {
438
444
  */
439
445
  #getJSExportBoilerplate() {
440
446
  const namespace = this.path.asNamespace()
441
-
447
+ const jsp = this.jsPrinter
442
448
  const boilerplate = [AUTO_GEN_NOTE]
443
449
  if (configuration.useEntitiesProxy) {
444
450
  if (namespace === '_') {
445
- boilerplate.push('const cds = require(\'@sap/cds\')', this.#getEntityProxyFunctionExport())
451
+ boilerplate.push(jsp.printImport('cds', '@sap/cds'), this.#getEntityProxyFunctionExport())
446
452
  } else {
447
- boilerplate.push(`const { createEntityProxy } = require('${new Path(['_']).asDirectory({relative: this.path.asDirectory()})}')`)
453
+ boilerplate.push(
454
+ jsp.printDeconstructedImport(
455
+ ['createEntityProxy'],
456
+ new Path(['_']).asDirectory({ relative: this.path.asDirectory() })
457
+ )
458
+ )
448
459
  }
449
460
  } else {
450
461
  boilerplate.push(
451
- 'const cds = require(\'@sap/cds\')',
452
- `const csn = cds.entities('${namespace}')`
462
+ jsp.printImport('cds', '@sap/cds'),
463
+ jsp.printConstant('csn', `cds.entities('${namespace}')`)
453
464
  )
454
465
  }
455
466
  return boilerplate
@@ -480,18 +491,32 @@ class SourceFile extends File {
480
491
  }
481
492
  }
482
493
  }
494
+
483
495
  toJSExports() {
496
+ const jsp = this.jsPrinter
484
497
  return this.#getJSExportBoilerplate() // boilerplate
485
498
  .concat(
486
499
  // FIXME: move stringification of service into own module
487
500
  this.services.names.flatMap(name => {
488
- const nameSimple = name.split('.').pop()
489
- return [
490
- '// service',
491
- `const ${nameSimple} = { name: '${name}' }`,
492
- `module.exports = ${nameSimple}`, // there should be only one and must be the first
493
- `module.exports.${nameSimple} = ${nameSimple}`
501
+ const nameSimple = /** @type {string} */(name.split('.').pop())
502
+ const service = [
503
+ jsp.printSingleLineComment('service'),
504
+ jsp.printConstant(nameSimple, `{ name: '${name}' }`),
505
+ jsp.printDefaultExport(nameSimple), // there should be only one and must be the first
494
506
  ]
507
+ if (jsp instanceof CJSPrinter) {
508
+ // unfortunately, we have to add a tiny bit of special logic for CJS, where we get:
509
+ //
510
+ // module.exports = FooService
511
+ // module.exports.FooService = FooService
512
+ //
513
+ // which, in ESM, would amount to:
514
+ //
515
+ // export const FooService
516
+ // default export FooService = FooService // <- duplicate identifier
517
+ service.push(jsp.printExport(nameSimple, nameSimple))
518
+ }
519
+ return service
495
520
  })
496
521
  )
497
522
  .concat(this.inflections
@@ -502,10 +527,13 @@ class SourceFile extends File {
502
527
  .flatMap(([singular, plural, original]) => {
503
528
  const { singularRhs, pluralRhs } = this.#getEntityExportsRhs(singular, original)
504
529
 
505
- const exports = [`// ${original}`, `module.exports.${singular} = ${singularRhs}`]
530
+ const exports = [
531
+ jsp.printSingleLineComment(original),
532
+ jsp.printExport(singular, singularRhs)
533
+ ]
506
534
  if (!/Array<.*>/.test(plural) && plural !== original) {
507
535
  // FIXME: this is a hack to support CDS types that will produce "Array<MyType>" as plural, which we do not want as export in the index.js files
508
- exports.push(`module.exports.${plural} = ${pluralRhs}`)
536
+ exports.push(jsp.printExport(plural, pluralRhs))
509
537
  }
510
538
  // FIXME: we currently produce at most 3 entries.
511
539
  // This could be an issue when the user re-used the original name in a @singular/@plural annotation.
@@ -513,16 +541,16 @@ class SourceFile extends File {
513
541
  if (singular !== original) {
514
542
  // do not do the is_singular spiel if the original name is used for the plural
515
543
  const rhs = plural === original ? pluralRhs : singularRhs
516
- exports.push(`module.exports.${original} = ${rhs}`)
544
+ exports.push(jsp.printExport(original, rhs))
517
545
  }
518
546
  return exports
519
547
  })
520
548
  ) // singular -> plural aliases
521
- .concat(['// events'])
522
- .concat(this.events.fqs.map(({fq, name}) => `module.exports.${name} = '${fq}'`))
523
- .concat(['// actions'])
524
- .concat(this.operations.names.map(name => `module.exports.${name} = '${name}'`))
525
- .concat(['// enums'])
549
+ .concat(jsp.printSingleLineComment('events'))
550
+ .concat(this.events.fqs.map(({fq, name}) => jsp.printExport(name, stringIdent(fq))))
551
+ .concat(jsp.printSingleLineComment('actions'))
552
+ .concat(this.operations.names.map(name => jsp.printExport(name, stringIdent(name))))
553
+ .concat(jsp.printSingleLineComment('enums'))
526
554
  .concat(this.enums.data.map(({name, kvs}) => stringifyEnumImplementation(name, kvs)))
527
555
  .join('\n') + '\n'
528
556
  }
@@ -555,6 +583,15 @@ class Buffer {
555
583
  this.closed = false
556
584
  }
557
585
 
586
+ /**
587
+ * @returns {Buffer} an empty Buffer that starts at this Buffer's current indentation.
588
+ */
589
+ createSubBuffer() {
590
+ const sub = new Buffer(this.indentation)
591
+ sub.currentIndent = this.currentIndent
592
+ return sub
593
+ }
594
+
558
595
  /**
559
596
  * Indents by the predefined spacing.
560
597
  */
@@ -0,0 +1,131 @@
1
+ class JavaScriptPrinter {
2
+ /**
3
+ * @param {string} text - comment text
4
+ */
5
+ printSingleLineComment (text) {
6
+ return `// ${text}`
7
+ }
8
+
9
+ /**
10
+ * @param {string} name - name of the constant
11
+ * @param {string} value - initial assignment to the constant
12
+ */
13
+ printConstant (name, value) {
14
+ return `const ${name} = ${value}`
15
+ }
16
+
17
+ /**
18
+ * @abstract
19
+ * @param {string} alias - what the import should be known as within the importing file
20
+ * @param {string} from - the package/ location to import from
21
+ * @returns {string}
22
+ */
23
+ // eslint-disable-next-line no-unused-vars
24
+ printImport (alias, from) {
25
+ throw Error('not implemented')
26
+ }
27
+
28
+ /**
29
+ * @abstract
30
+ * @param {string[]} imports - the deconstructed elements
31
+ * @param {string} from - the package/ location to import from
32
+ * @returns {string}
33
+ */
34
+ // eslint-disable-next-line no-unused-vars
35
+ printDeconstructedImport (imports, from) {
36
+ throw Error('not implemented')
37
+ }
38
+
39
+ /**
40
+ * @abstract
41
+ * @param {string} name - name the export should be known as
42
+ * @param {string} value - the value of the export
43
+ * @returns {string}
44
+ */
45
+ // eslint-disable-next-line no-unused-vars
46
+ printExport (name, value) {
47
+ throw Error('not implemented')
48
+ }
49
+
50
+ /**
51
+ * @abstract
52
+ * @param {string} value - the value of the default export
53
+ * @returns {string}
54
+ */
55
+ // eslint-disable-next-line no-unused-vars
56
+ printDefaultExport (value) {
57
+ throw Error('not implemented')
58
+ }
59
+
60
+ /**
61
+ * @abstract
62
+ * @param {string} file - the file name without extension
63
+ * @returns {string}
64
+ */
65
+ // eslint-disable-next-line no-unused-vars
66
+ nameFile (file) {
67
+ throw Error('not implemented')
68
+ }
69
+ }
70
+
71
+ class ESMPrinter extends JavaScriptPrinter {
72
+ /** @type {JavaScriptPrinter['printImport']} */
73
+ printImport (alias, from) {
74
+ return `import * as ${alias} from '${from}'`
75
+ }
76
+
77
+ /** @type {JavaScriptPrinter['printDeconstructedImport']} */
78
+ printDeconstructedImport (imports, from) {
79
+ return `import { ${imports.join(', ')} } from '${from}'`
80
+ }
81
+
82
+ /** @type {JavaScriptPrinter['printExport']} */
83
+ printExport (name, value) {
84
+ return `export const ${name} = ${value}`
85
+ }
86
+
87
+ /** @type {JavaScriptPrinter['printDefaultExport']} */
88
+ printDefaultExport (value) {
89
+ return `export default ${value}`
90
+ }
91
+
92
+ /** @type {JavaScriptPrinter['nameFile']} */
93
+ nameFile (file) {
94
+ return `${file}.mjs`
95
+ }
96
+ }
97
+
98
+ class CJSPrinter extends JavaScriptPrinter {
99
+ /** @type {JavaScriptPrinter['printImport']} */
100
+ printImport (alias, from) {
101
+ return `const ${alias} = require('${from}')`
102
+ }
103
+
104
+ /** @type {JavaScriptPrinter['printDeconstructedImport']} */
105
+ printDeconstructedImport (imports, from) {
106
+ return `const { ${imports.join(', ')} } = require('${from}')`
107
+ }
108
+
109
+ /** @type {JavaScriptPrinter['printExport']} */
110
+ printExport (name, value) {
111
+ return `module.exports.${name} = ${value}`
112
+ }
113
+
114
+ /** @type {JavaScriptPrinter['printDefaultExport']} */
115
+ printDefaultExport (value) {
116
+ // not exactly "default", but you catch the drift...
117
+ return `module.exports = ${value}`
118
+ }
119
+
120
+ /** @type {JavaScriptPrinter['nameFile']} */
121
+ nameFile (file) {
122
+ return `${file}.js`
123
+ }
124
+ }
125
+
126
+ /** @typedef {JavaScriptPrinter} Printer */
127
+
128
+ module.exports = {
129
+ CJSPrinter,
130
+ ESMPrinter
131
+ }
@@ -87,6 +87,13 @@ const createObjectOf = t => `{${t}}`
87
87
  */
88
88
  const createUnionOf = (...types) => types.join(' | ')
89
89
 
90
+ /**
91
+ * Wraps types into a intersection type string
92
+ * @param {string[]} types - an array of types
93
+ * @returns {string}
94
+ */
95
+ const createIntersectionOf = (...types) => types.join(' & ')
96
+
90
97
  /**
91
98
  * Wraps type into a promise
92
99
  * @param {string} t - the type to wrap.
@@ -136,6 +143,7 @@ module.exports = {
136
143
  createObjectOf,
137
144
  createPromiseOf,
138
145
  createUnionOf,
146
+ createIntersectionOf,
139
147
  createToOneAssociation,
140
148
  createToManyAssociation,
141
149
  createCompositionOfOne,
@@ -3,7 +3,7 @@
3
3
  const util = require('../util')
4
4
  // eslint-disable-next-line no-unused-vars
5
5
  const { Buffer, SourceFile, Path, Library } = require('../file')
6
- const { deepRequire, createToManyAssociation, createToOneAssociation, createArrayOf, createCompositionOfMany, createCompositionOfOne, createKey } = require('../components/wrappers')
6
+ const { deepRequire, createToManyAssociation, createToOneAssociation, createArrayOf, createCompositionOfMany, createCompositionOfOne, createKey } = require('../printers/wrappers')
7
7
  const { StructuredInlineDeclarationResolver } = require('../components/inline')
8
8
  const { isInlineEnumType, propertyToInlineEnumName } = require('../components/enum')
9
9
  const { isReferenceType } = require('../components/reference')
package/lib/typedefs.d.ts CHANGED
@@ -202,6 +202,7 @@ export module config {
202
202
  * `IEEE754Compatible = true` -> any cds.Decimal will become `number | string`
203
203
  */
204
204
  IEEE754Compatible: boolean
205
+ targetModuleType: 'cjs' | 'esm' | 'auto'
205
206
  }
206
207
  }
207
208
 
package/lib/util.js CHANGED
@@ -1,8 +1,9 @@
1
1
  /** @typedef { import('./typedefs').util.Annotations} Annotations */
2
- /** @typedef { import('./typedefs').util.CommandLineFlags } CommandlineFlag */
3
- /** @typedef { import('./typedefs').util.ParsedFlag } ParsedFlags */
4
2
  /** @typedef { import('./typedefs').resolver.EntityCSN } EntityCSN */
5
3
 
4
+ const fs = require('node:fs')
5
+ const path = require('node:path')
6
+
6
7
  // inflection functions are stolen from github/cap/dev/blob/main/etc/inflect.js
7
8
 
8
9
  // MONKEY PATCH to support Node v14
@@ -108,7 +109,7 @@ const singular4 = (dn, stripped = false) => {
108
109
  const plural4 = (dn, stripped) => {
109
110
  let n = dn.name ?? dn
110
111
  if (stripped) {
111
- n = n.match(last)?.[0]
112
+ n = /** @type {string} */(n.match(last)?.[0])
112
113
  }
113
114
  return (
114
115
  getPluralAnnotation(dn) ??
@@ -125,8 +126,8 @@ const plural4 = (dn, stripped) => {
125
126
  /**
126
127
  * Performs a deep merge of the passed objects into the first object.
127
128
  * See Object.assign(target, source).
128
- * @param {object} target - object to assign into.
129
- * @param {object} source - object to assign from.
129
+ * @param {{[key: string]: any}} target - object to assign into.
130
+ * @param {{[key: string]: any}} source - object to assign from.
130
131
  */
131
132
  const deepMerge = (target, source) => {
132
133
  Object.keys(target)
@@ -135,11 +136,40 @@ const deepMerge = (target, source) => {
135
136
  Object.assign(target, source)
136
137
  }
137
138
 
139
+ /**
140
+ * Tries to determine the project's target type (ESM or CJS).
141
+ * @param {string} root - the root of the project
142
+ * @returns {'esm' | 'cjs' | undefined}
143
+ */
144
+ const getProjectTargetType = root => {
145
+ const isFsRoot = (/** @type {string} **/ dir) => dir === path.dirname(dir)
146
+ let dir = root
147
+ let packageJson
148
+
149
+ do {
150
+ packageJson = fs.readdirSync(dir).find(f => f.endsWith('package.json'))
151
+ if (packageJson) {
152
+ packageJson = path.join(dir, packageJson)
153
+ }
154
+ dir = path.dirname(dir)
155
+ } while (!packageJson && !isFsRoot(dir))
156
+
157
+ if (packageJson) {
158
+ const json = JSON.parse(fs.readFileSync(packageJson, 'utf-8'))
159
+ return json.type === 'module'
160
+ ? 'esm'
161
+ : 'cjs'
162
+ }
163
+ // no package.json found -> inconclusive
164
+ return undefined
165
+ }
166
+
138
167
  module.exports = {
139
168
  annotations,
140
169
  camelToSnake,
141
170
  getSingularAnnotation,
142
171
  getPluralAnnotation,
172
+ getProjectTargetType,
143
173
  unlocalize,
144
174
  singular4,
145
175
  plural4,
package/lib/visitor.js CHANGED
@@ -8,7 +8,7 @@ const { SourceFile, FileRepository, Buffer, Path } = require('./file')
8
8
  const { FlatInlineDeclarationResolver, StructuredInlineDeclarationResolver } = require('./components/inline')
9
9
  const { Resolver } = require('./resolution/resolver')
10
10
  const { LOG } = require('./logging')
11
- const { docify, createPromiseOf, createUnionOf, createKeysOf, createElementsOf, stringIdent, createDraftsOf, createDraftOf } = require('./components/wrappers')
11
+ const { docify, createPromiseOf, createUnionOf, createKeysOf, createElementsOf, stringIdent, createDraftsOf, createDraftOf, createIntersectionOf, createReturnTypeOf } = require('./printers/wrappers')
12
12
  const { csnToEnumPairs, propertyToInlineEnumName, isInlineEnumType, stringifyEnumType } = require('./components/enum')
13
13
  const { isReferenceType } = require('./components/reference')
14
14
  const { empty } = require('./components/typescript')
@@ -138,11 +138,11 @@ class Visitor {
138
138
  #printStaticActions(entity, buffer, ancestors, file) {
139
139
  // TODO: refactor away! All these printing functionalities need to go
140
140
  const actions = Object.entries(entity.actions ?? {})
141
- const inherited = ancestors.length
142
- ? ancestors.map(a => `typeof ${asIdentifier({info: a, relative: file.path})}.actions`).join(' & ') + ' & '
143
- : ''
141
+ const inherited = ancestors.map(a => `typeof ${asIdentifier({info: a, relative: file.path})}.actions`)
142
+
143
+ const typeBuffer = buffer.createSubBuffer()
144
144
  if (actions.length) {
145
- buffer.addIndentedBlock(`declare static readonly actions: ${inherited}{`,
145
+ typeBuffer.addIndentedBlock(createIntersectionOf(...inherited, '{'),
146
146
  () => {
147
147
  for (const [aname, action] of actions) {
148
148
  const [opener, content, closer] = SourceFile.stringifyLambda({
@@ -153,28 +153,34 @@ class Visitor {
153
153
  : 'any',
154
154
  kind: action.kind,
155
155
  doc: docify(action.doc)})
156
- buffer.addIndentedBlock(opener, content, closer)
156
+ typeBuffer.addIndentedBlock(opener, content, closer)
157
157
  }
158
- }, '}'
159
- ) // end of actions
158
+ }, '}')
160
159
  } else {
161
- buffer.add(createMember({
162
- name: 'actions',
163
- type: `${inherited}${empty}`,
164
- isStatic: true,
165
- isReadonly: true
166
- }))
160
+ typeBuffer.add(createIntersectionOf(...inherited, empty))
167
161
  }
162
+ buffer.add(createMember({
163
+ name: 'actions',
164
+ type: typeBuffer.join().trimStart(), // remove leading whitespace from indentation, as type is printed in line
165
+ isStatic: true,
166
+ isReadonly: true,
167
+ isDeclare: true,
168
+ }))
168
169
  }
169
170
 
170
171
  /**
171
172
  * @param {Buffer} buffer - the buffer to write the keys into
172
173
  * @param {string} clean - the clean name of the entity
174
+ * @param {import('./typedefs').resolver.EntityInfo[]} ancestors - ancestors infos to include in they type
175
+ * @param {SourceFile} file - the file the entity is being printed into
173
176
  */
174
- #printStaticKeys(buffer, clean) {
177
+ #printStaticKeys(buffer, clean, ancestors, file) {
178
+ const ancestorKeys = ancestors
179
+ .filter(a => Object.entries(a.csn.keys ?? {}).length)
180
+ .map(a => `typeof ${asIdentifier({info: a, relative: file.path})}.keys`)
175
181
  buffer.add(createMember({
176
182
  name: 'keys',
177
- type: createKeysOf(clean),
183
+ type: createIntersectionOf(createKeysOf(clean), ...ancestorKeys),
178
184
  isDeclare: true,
179
185
  isStatic: true,
180
186
  isReadonly: true,
@@ -312,15 +318,15 @@ class Visitor {
312
318
  if ('kind' in entity) {
313
319
  buffer.add(createMember({
314
320
  name: 'kind',
315
- type: '"entity" | "type" | "aspect"',
321
+ type: createUnionOf(...['entity', 'type', 'aspect'].map(stringIdent)),
316
322
  isStatic: true,
317
323
  isReadonly: true,
318
324
  isDeclare: false,
319
- isOverride: ancestorInfos.some(ancestor => ancestor.csn.kind),
325
+ isOverride: ancestorInfos.some(({csn}) => csn.kind),
320
326
  initialiser: stringIdent(entity.kind)
321
327
  }))
322
328
  }
323
- this.#printStaticKeys(buffer, clean)
329
+ this.#printStaticKeys(buffer, clean, ancestorInfos, file)
324
330
  this.#printStaticElements(buffer, clean)
325
331
  this.#printStaticActions(entity, buffer, ancestorInfos, file)
326
332
  }, '};') // end of generated class
@@ -657,4 +663,3 @@ class Visitor {
657
663
  module.exports = {
658
664
  Visitor
659
665
  }
660
-
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@cap-js/cds-typer",
3
- "version": "0.28.1",
3
+ "version": "0.29.0",
4
4
  "description": "Generates .ts files for a CDS model to receive code completion in VS Code",
5
5
  "main": "index.js",
6
6
  "repository": "github:cap-js/cds-typer",
@@ -123,6 +123,16 @@
123
123
  "type": "boolean",
124
124
  "description": "If set to true, floating point properties are generated\nas IEEE754 compatible '(number | string)' instead of 'number'.",
125
125
  "default": false
126
+ },
127
+ "target_module_type": {
128
+ "type": "string",
129
+ "description": "Output format for generated .js files.\nSetting it to auto tries to derive the module type from\nthe package.json and falls back to CJS.",
130
+ "enum": [
131
+ "esm",
132
+ "cjs",
133
+ "auto"
134
+ ],
135
+ "default": "auto"
126
136
  }
127
137
  }
128
138
  }