@cap-js/cds-typer 0.11.1 → 0.13.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,12 +4,25 @@ 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.12.0 - TBD
7
+ ## Version 0.14.0 - TBD
8
+
9
+ ## Version 0.13.0 - 2023-12-06
10
+ ### Changes
11
+ - Enums are now generated ecplicitly in the respective _index.js_ files and don't have to extract their values from the model at runtime anymore
12
+
13
+ ### Added
14
+ - The `excluding` clause in projections now actually excludes the specified properties in the generated types
15
+
16
+ ## Version 0.12.0 - 2023-11-23
8
17
 
9
18
  ### Changed
19
+ - Generate `cds.LargeBinary` as string, buffer, _or readable_ in the case of media content
10
20
 
11
21
  ### Added
22
+ - Added support for the `not null` modifier
23
+
12
24
  ### Fixed
25
+ - Now using names of enum values in generated _index.js_ files if no explicit value is present
13
26
 
14
27
  ## Version 0.11.1 - 2023-10-12
15
28
 
@@ -17,7 +30,7 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/).
17
30
 
18
31
  ### Added
19
32
  ### Fixed
20
- Fixed how service names are exported as default export
33
+ - Fixed how service names are exported as default export
21
34
 
22
35
  ## Version 0.11.0 - 2023-10-10
23
36
 
package/lib/cli.js CHANGED
@@ -99,8 +99,4 @@ const main = async (args) => {
99
99
 
100
100
  if (require.main === module) {
101
101
  main(parseCommandlineArgs(process.argv.slice(2), flags))
102
- }
103
-
104
- function helpToCapire() {
105
-
106
102
  }
@@ -31,8 +31,8 @@ function printEnum(buffer, name, kvs, options = {}) {
31
31
  buffer.indent()
32
32
  const vals = new Set()
33
33
  for (const [k, v] of kvs) {
34
- buffer.add(`${k}: ${JSON.stringify(v)},`)
35
- vals.add(JSON.stringify(v.val ?? v)) // in case of wrapped vals we need to unwrap here for the type
34
+ buffer.add(`${k}: ${v},`)
35
+ vals.add(v?.val ?? v) // in case of wrapped vals we need to unwrap here for the type
36
36
  }
37
37
  buffer.outdent()
38
38
  buffer.add('} as const;')
@@ -41,27 +41,32 @@ function printEnum(buffer, name, kvs, options = {}) {
41
41
  }
42
42
 
43
43
  // in case of strings, wrap in quotes and fallback to key to make sure values are attached for every key
44
- const enumVal = (key, value, enumType) => enumType === 'cds.String' ? `${value ?? key}` : value
44
+ const enumVal = (key, value, enumType) => enumType === 'cds.String' ? JSON.stringify(`${value ?? key}`) : value
45
45
 
46
46
  /**
47
+ * Converts a CSN type describing an enum into a list of kv-pairs.
48
+ * Values from CSN are unwrapped from their `.val` structure and
49
+ * will fall back to the key if no value is provided.
50
+ *
47
51
  * @param {{enum: {[key: name]: string}, type: string}} enumCsn
48
52
  * @param {{unwrapVals: boolean}} options if `unwrapVals` is passed,
49
53
  * then the CSN structure `{val:x}` is flattened to just `x`.
50
54
  * Retaining `val` is closer to the actual CSN structure and should be used where we want
51
- * to mimic the runtime as closely as possible (anoymous enum types).
55
+ * to mimic the runtime as closely as possible (inline enum types).
52
56
  * Stripping that additional wrapper would be more readable for users.
57
+ *
53
58
  * @example
54
59
  * ```ts
55
- * const csn = {enum: {x: {val: 42}, y: {val: -42}}}
56
- * csnToEnum(csn) // -> [['x', 42], ['y': -42]]
57
- * csnToEnum(csn, {unwrapVals: false}) // -> [['x', {val:42}], ['y': {val:-42}]]
60
+ * const csn = {enum: {X: {val: 'a'}, Y: {val: 'b'}, Z: {}}}
61
+ * csnToEnumPairs(csn) // -> [['X', 'a'], ['Y': 'b'], ['Z': 'Z']]
62
+ * csnToEnumPairs(csn, {unwrapVals: false}) // -> [['X', {val:'a'}], ['Y': {val:'b'}], ['Z':'Z']]
58
63
  * ```
59
64
  */
60
- const csnToEnum = ({enum: enm, type}, options = {}) => {
65
+ const csnToEnumPairs = ({enum: enm, type}, options = {}) => {
61
66
  options = {...{unwrapVals: true}, ...options}
62
67
  return Object.entries(enm).map(([k, v]) => {
63
68
  const val = enumVal(k, v.val, type)
64
- return [k, options.unwrapVals ? val : { val }]
69
+ return [k, (options.unwrapVals ? val : { val })]
65
70
  })
66
71
  }
67
72
 
@@ -69,7 +74,7 @@ const csnToEnum = ({enum: enm, type}, options = {}) => {
69
74
  * @param {string} entity
70
75
  * @param {string} property
71
76
  */
72
- const propertyToAnonymousEnumName = (entity, property) => `${entity}_${property}`
77
+ const propertyToInlineEnumName = (entity, property) => `${entity}_${property}`
73
78
 
74
79
  /**
75
80
  * A type is considered to be an inline enum, iff it has a `.enum` property
@@ -82,27 +87,30 @@ const propertyToAnonymousEnumName = (entity, property) => `${entity}_${property}
82
87
  */
83
88
  const isInlineEnumType = (element, csn) => element.enum && !(element.type in csn.definitions)
84
89
 
85
- const stringifyEnumImplementation = (name, enm) => `module.exports.${name} = Object.fromEntries(Object.entries(${enm}).map(([k,v]) => [k,v.val]))`
86
-
87
- /**
88
- * @param {string} name
89
- * @param {string} fq
90
- * @returns {string}
91
- */
92
- const stringifyNamedEnum = (name, fq) => stringifyEnumImplementation(name, `cds.model.definitions['${fq}'].enum`)
93
90
  /**
91
+ * Stringifies an enum into a runtime artifact.
92
+ * ```cds
93
+ * type Language: String enum {
94
+ * DE = "German";
95
+ * EN = "English";
96
+ * FR;
97
+ * }
98
+ * ```
99
+ * becomes
100
+ *
101
+ * ```js
102
+ * module.exports.Language = { DE: "German", EN: "English", FR: "FR" }
103
+ * ```
94
104
  * @param {string} name
95
- * @param {string} fq
96
- * @param {string} property
97
- * @returns {string}
105
+ * @param {[string, string][]} kvs a list of key-value pairs. Values that are falsey are replaced by
98
106
  */
99
- const stringifyAnonymousEnum = (name, fq, property) => stringifyEnumImplementation(fq, `cds.model.definitions['${name}'].elements.${property}.enum`)
107
+ const stringifyEnumImplementation = (name, kvs) => `module.exports.${name} = { ${kvs.map(([k,v]) => `${k}: ${v}`).join(', ')} }`
108
+
100
109
 
101
110
  module.exports = {
102
111
  printEnum,
103
- csnToEnum,
104
- propertyToAnonymousEnumName,
112
+ csnToEnumPairs,
113
+ propertyToInlineEnumName,
105
114
  isInlineEnumType,
106
- stringifyNamedEnum,
107
- stringifyAnonymousEnum
115
+ stringifyEnumImplementation
108
116
  }
@@ -17,6 +17,7 @@ class InlineDeclarationResolver {
17
17
  * @protected
18
18
  * @abstract
19
19
  */
20
+ // eslint-disable-next-line no-unused-vars
20
21
  printInlineType(name, type, buffer, statementEnd) { /* abstract */ }
21
22
 
22
23
  /**
@@ -84,6 +85,17 @@ class InlineDeclarationResolver {
84
85
  return this.visitor.options.propertiesOptional ? '?:' : ':'
85
86
  }
86
87
 
88
+ /**
89
+ * It returns TypeScript datatype for provided TS property
90
+ * @param {{typeName: string, typeInfo: TypeResolveInfo & { inflection: Inflection } }} type
91
+ * @param {string} typeName name of the TypeScript property
92
+ * @return {string} the datatype to be presented on TypeScript layer
93
+ * @public
94
+ */
95
+ getPropertyDatatype(type, typeName = type.typeName) {
96
+ return type.typeInfo.isNotNull ? typeName : `${typeName} | null`
97
+ }
98
+
87
99
  /** @param {import('../visitor').Visitor} visitor */
88
100
  constructor(visitor) {
89
101
  this.visitor = visitor
@@ -111,6 +123,7 @@ class InlineDeclarationResolver {
111
123
  * @public
112
124
  * @abstract
113
125
  */
126
+ // eslint-disable-next-line no-unused-vars
114
127
  getTypeLookup(members) { /* abstract */ return '' }
115
128
  }
116
129
 
@@ -142,7 +155,7 @@ class FlatInlineDeclarationResolver extends InlineDeclarationResolver {
142
155
  flatten(prefix, type) {
143
156
  return type.typeInfo.structuredType
144
157
  ? Object.entries(type.typeInfo.structuredType).map(([k,v]) => this.flatten(`${this.prefix(prefix)}${k}`, v))
145
- : [`${prefix}${this.getPropertyTypeSeparator()} ${type.typeName}`]
158
+ : [`${prefix}${this.getPropertyTypeSeparator()} ${this.getPropertyDatatype(type)}`]
146
159
  }
147
160
 
148
161
  printInlineType(name, type, buffer) {
@@ -192,9 +205,9 @@ class StructuredInlineDeclarationResolver extends InlineDeclarationResolver {
192
205
  this.flatten(n, t, buffer)
193
206
  }
194
207
  buffer.outdent()
195
- buffer.add(`}${lineEnding}`)
208
+ buffer.add(`}${this.getPropertyDatatype(type, '')}${lineEnding}`)
196
209
  } else {
197
- buffer.add(`${name}${this.getPropertyTypeSeparator()} ${type.typeName}${lineEnding}`)
210
+ buffer.add(`${name}${this.getPropertyTypeSeparator()} ${this.getPropertyDatatype(type)}${lineEnding}`)
198
211
  }
199
212
  this.printDepth--
200
213
  return buffer
@@ -1,10 +1,11 @@
1
1
  'use strict'
2
2
 
3
3
  const util = require('../util')
4
- const { Buffer, SourceFile, Path, Library, baseDefinitions } = require("../file")
4
+ // eslint-disable-next-line no-unused-vars
5
+ const { Buffer, SourceFile, Path, Library, baseDefinitions } = require('../file')
5
6
  const { deepRequire, createToManyAssociation, createToOneAssociation, createArrayOf, createCompositionOfMany, createCompositionOfOne } = require('./wrappers')
6
- const { StructuredInlineDeclarationResolver } = require("./inline")
7
- const { isInlineEnumType, propertyToInlineEnumName, propertyToAnonymousEnumName } = require('./enum')
7
+ const { StructuredInlineDeclarationResolver } = require('./inline')
8
+ const { isInlineEnumType, propertyToInlineEnumName } = require('./enum')
8
9
 
9
10
  /** @typedef {{ cardinality?: { max?: '*' | number } }} EntityCSN */
10
11
  /** @typedef {{ definitions?: Object<string, EntityCSN> }} CSN */
@@ -22,6 +23,7 @@ const { isInlineEnumType, propertyToInlineEnumName, propertyToAnonymousEnumName
22
23
  * ```
23
24
  * @typedef {{
24
25
  * isBuiltin: boolean,
26
+ * isNotNull: boolean,
25
27
  * isInlineDeclaration: boolean,
26
28
  * isForeignKeyReference: boolean,
27
29
  * isArray: boolean,
@@ -41,7 +43,7 @@ const Builtins = {
41
43
  String: 'string',
42
44
  Binary: 'string',
43
45
  LargeString: 'string',
44
- LargeBinary: 'Buffer | string',
46
+ LargeBinary: 'Buffer | string | {value: import("stream").Readable, $mediaContentType: string, $mediaContentDispositionFilename?: string, $mediaContentDispositionType?: string}',
45
47
  Integer: 'number',
46
48
  UInt8: 'number',
47
49
  Int16: 'number',
@@ -231,6 +233,8 @@ class Resolver {
231
233
 
232
234
  if (toOne && toMany) {
233
235
  const target = element.items ?? (typeof element.target === 'string' ? { type: element.target } : element.target)
236
+ /** set `notNull = true` to avoid repeated `| not null` TS construction */
237
+ target.notNull = true
234
238
  const { singular, plural } = this.resolveAndRequire(target, file).typeInfo.inflection
235
239
  typeName =
236
240
  cardinality > 1 ? toMany(plural) : toOne(this.visitor.isSelfReference(target) ? 'this' : singular)
@@ -254,7 +258,7 @@ class Resolver {
254
258
  }
255
259
 
256
260
  if (element.type.ref?.length > 1) {
257
- const [entity, ...members] = element.type.ref
261
+ const [, ...members] = element.type.ref
258
262
  const lookup = this.visitor.inlineDeclarationResolver.getTypeLookup(members)
259
263
  typeName = deepRequire(typeInfo.inflection.singular, lookup)
260
264
  file.addImport(baseDefinitions.path)
@@ -348,11 +352,16 @@ class Resolver {
348
352
  return element
349
353
  }
350
354
 
355
+ const cardinality = this.getMaxCardinality(element)
356
+
351
357
  const result = {
352
358
  isBuiltin: false, // will be rectified in the corresponding handlers, if needed
353
359
  isInlineDeclaration: false,
354
360
  isForeignKeyReference: false,
355
361
  isArray: false,
362
+ isNotNull: element?.isRefNotNull !== undefined
363
+ ? element?.isRefNotNull
364
+ : element?.key || element?.notNull || cardinality > 1,
356
365
  }
357
366
 
358
367
  if (element?.type === undefined) {
@@ -365,7 +374,7 @@ class Resolver {
365
374
  // we use the singular as the initial declaration of these enums takes place
366
375
  // while defining the singular class. Which therefore uses the singular over the plural name.
367
376
  const cleanEntityName = util.singular4(element.parent, true)
368
- const enumName = propertyToAnonymousEnumName(cleanEntityName, element.name)
377
+ const enumName = propertyToInlineEnumName(cleanEntityName, element.name)
369
378
  result.type = enumName
370
379
  result.plainName = enumName
371
380
  result.isInlineDeclaration = true
@@ -377,6 +386,8 @@ class Resolver {
377
386
  // objects and arrays
378
387
  if (element?.items) {
379
388
  result.isArray = true
389
+ // TODO: re-implement this line once {element.notNull} will be provided for array-like elements
390
+ result.isNotNull = true
380
391
  result.isBuiltin = true
381
392
  this.resolveType(element.items, file)
382
393
  //delete element.items
package/lib/csn.js CHANGED
@@ -221,49 +221,6 @@ function propagateForeignKeys(csn) {
221
221
  }
222
222
  }
223
223
 
224
- /**
225
- * Entities inherit their ancestors annotations:
226
- * https://cap.cloud.sap/docs/cds/cdl#annotation-propagation
227
- * This is a problem if we annotate @singular/ @plural to an entity A,
228
- * as we don't want all descendents B, C, ... to share the ancestor's
229
- * annotated inflexion
230
- * -> remove all such annotations that appear in a parent as well.
231
- * BUT: we can't just delete the attributes. Imagine three classes
232
- * A <- B <- C
233
- * where A contains a @singular annotation.
234
- * If we erase the annotation from B, C will still contain it and
235
- * can not detect that its own annotation was inherited without
236
- * travelling up the entire inheritance chain up to A.
237
- * So instead, we monkey patch and maintain a dictionary "erased"
238
- * when removing an annotation which we also check.
239
- * @deprecated since we use the xtended flavour for CSN, we don't need to fix this anymore
240
- */
241
- function propagateInflectionAnnotations(csn) {
242
- const erase = (entity, parent, attr) => {
243
- if (attr in entity) {
244
- const ea = entity[attr]
245
- if (parent[attr] === ea || (parent.erased && parent.erased[attr] === ea)) {
246
- entity.erased ??= {}
247
- entity.erased[attr] = ea
248
- delete entity[attr]
249
- //this.logger.info(`Removing inherited attribute ${attr} from ${entity.name}.`)
250
- }
251
- }
252
- }
253
-
254
- for (const entity of Object.values(csn.definitions)) {
255
- let i = 0
256
- while (
257
- (getSingularAnnotation(entity) || getPluralAnnotation(entity)) &&
258
- i < (entity.includes ?? []).length
259
- ) {
260
- const parent = csn.definitions[entity.includes[i]]
261
- Object.values(annotations).flat().forEach(an => erase(entity, parent, an))
262
- i++
263
- }
264
- }
265
- }
266
-
267
224
  function amendCSN(csn) {
268
225
  unrollDraftability(csn)
269
226
  propagateForeignKeys(csn)
package/lib/file.js CHANGED
@@ -2,7 +2,7 @@
2
2
 
3
3
  const fs = require('fs').promises
4
4
  const { readFileSync } = require('fs')
5
- const { printEnum, stringifyNamedEnum, stringifyAnonymousEnum, propertyToAnonymousEnumName } = require('./components/enum')
5
+ const { printEnum, propertyToInlineEnumName, stringifyEnumImplementation } = require('./components/enum')
6
6
  const path = require('path')
7
7
 
8
8
  const AUTO_GEN_NOTE = "// This is an automatically generated file. Please do not change its contents manually!"
@@ -104,8 +104,8 @@ class SourceFile extends File {
104
104
  this.events = { buffer: new Buffer(), fqs: []}
105
105
  /** @type {Buffer} */
106
106
  this.types = new Buffer()
107
- /** @type {{ buffer: Buffer, fqs: {name: string, fq: string, property?: string}[]}} */
108
- this.enums = { buffer: new Buffer(), fqs: [] }
107
+ /** @type {{ buffer: Buffer, data: {kvs: [string[]], name: string, fq: string, property?: string}[]}} */
108
+ this.enums = { buffer: new Buffer(), data: [] }
109
109
  /** @type {{ buffer: Buffer }} */
110
110
  this.inlineEnums = { buffer: new Buffer() }
111
111
  /** @type {Buffer} */
@@ -226,28 +226,28 @@ class SourceFile extends File {
226
226
  * @param {string} fq fully qualified name of the enum (entity name within CSN)
227
227
  * @param {string} name local name of the enum
228
228
  * @param {[string, string][]} kvs list of key-value pairs
229
- * @param {string} [property] property to which the enum is attached.
230
- * If given, the enum is considered to be an anonymous inline definition of an enum.
229
+ * @param {string} [property] property to which the enum is attached.
230
+ * If given, the enum is considered to be an inline definition of an enum.
231
231
  * If not, it is considered to be regular, named enum.
232
232
  */
233
233
  addEnum(fq, name, kvs) {
234
- this.enums.fqs.push({ name, fq })
234
+ this.enums.data.push({ name, fq, kvs })
235
235
  printEnum(this.enums.buffer, name, kvs)
236
236
  }
237
237
 
238
238
  /**
239
- * Adds an anonymous enum to this file.
239
+ * Adds an inline enum to this file.
240
240
  * @param {string} entityCleanName name of the entity the enum is attached to without namespace
241
241
  * @param {string} entityFqName name of the entity the enum is attached to with namespace
242
242
  *
243
243
  * @param {string} propertyName property to which the enum is attached.
244
244
  * @param {[string, string][]} kvs list of key-value pairs
245
- * If given, the enum is considered to be an anonymous inline definition of an enum.
245
+ * If given, the enum is considered to be an inline definition of an enum.
246
246
  * If not, it is considered to be regular, named enum.
247
247
  *
248
248
  * @example
249
249
  * ```js
250
- * addAnonymousEnum('Books.genre', 'Books', 'genre', [['horror','horror']])
250
+ * addInlineEnum('Books.genre', 'Books', 'genre', [['horror','horror']])
251
251
  * ```
252
252
  * generates
253
253
  * ```js
@@ -266,13 +266,14 @@ class SourceFile extends File {
266
266
  * }
267
267
  * ```
268
268
  */
269
- addAnonymousEnum(entityCleanName, entityFqName, propertyName, kvs) {
270
- this.enums.fqs.push({
269
+ addInlineEnum(entityCleanName, entityFqName, propertyName, kvs) {
270
+ this.enums.data.push({
271
271
  name: entityFqName,
272
272
  property: propertyName,
273
+ kvs,
273
274
  fq: `${entityCleanName}.${propertyName}`
274
275
  })
275
- printEnum(this.inlineEnums.buffer, propertyToAnonymousEnumName(entityCleanName, propertyName), kvs, {export: false})
276
+ printEnum(this.inlineEnums.buffer, propertyToInlineEnumName(entityCleanName, propertyName), kvs, {export: false})
276
277
  }
277
278
 
278
279
  /**
@@ -410,9 +411,7 @@ class SourceFile extends File {
410
411
  .concat(['// actions'])
411
412
  .concat(this.actions.names.map(name => `module.exports.${name} = '${name}'`))
412
413
  .concat(['// enums'])
413
- .concat(this.enums.fqs.map(({name, fq, property}) => property
414
- ? stringifyAnonymousEnum(name, fq, property)
415
- : stringifyNamedEnum(name, fq)))
414
+ .concat(this.enums.data.map(({name, kvs}) => stringifyEnumImplementation(name, kvs)))
416
415
  .join('\n') + '\n'
417
416
  }
418
417
  }
@@ -608,6 +607,7 @@ const writeout = async (root, sources) =>
608
607
  } catch (err) {
609
608
  // eslint-disable-next-line no-console
610
609
  console.error(`Could not create parent directory ${dir}: ${err}.`)
610
+ console.error(err.stack)
611
611
  }
612
612
  return dir
613
613
  })
package/lib/visitor.js CHANGED
@@ -3,12 +3,13 @@
3
3
  const util = require('./util')
4
4
 
5
5
  const { amendCSN } = require('./csn')
6
+ // eslint-disable-next-line no-unused-vars
6
7
  const { SourceFile, baseDefinitions, Buffer } = require('./file')
7
8
  const { FlatInlineDeclarationResolver, StructuredInlineDeclarationResolver } = require('./components/inline')
8
9
  const { Resolver } = require('./components/resolver')
9
10
  const { Logger } = require('./logging')
10
11
  const { docify } = require('./components/wrappers')
11
- const { csnToEnum, propertyToAnonymousEnumName, isInlineEnumType } = require('./components/enum')
12
+ const { csnToEnumPairs, propertyToInlineEnumName, isInlineEnumType } = require('./components/enum')
12
13
 
13
14
  /** @typedef {import('./file').File} File */
14
15
  /** @typedef {{ entity: String }} Context */
@@ -116,7 +117,11 @@ class Visitor {
116
117
  // instead of using the definition from inferred CSN, we refer to the projected entity from xtended CSN instead.
117
118
  // The latter contains the CSN fixes (propagated foreign keys, etc) and none of the localised fields we don't handle yet.
118
119
  if (entity.projection) {
119
- this.visitEntity(name, this.csn.xtended.definitions[entity.projection.from.ref[0]])
120
+ const targetName = entity.projection.from.ref[0]
121
+ // FIXME: references to types of entity properties may be missing from xtendend flavour (see #103)
122
+ // this should be revisted once we settle on a single flavour.
123
+ const target = this.csn.xtended.definitions[targetName] ?? this.csn.inferred.definitions[targetName]
124
+ this.visitEntity(name, target)
120
125
  } else {
121
126
  this.logger.error(`Expecting an autoexposed projection within a service. Skipping ${name}`)
122
127
  }
@@ -151,7 +156,9 @@ class Visitor {
151
156
  buffer.indent()
152
157
 
153
158
  const enums = []
154
- for (const [ename, element] of Object.entries(entity.elements ?? {})) {
159
+ const exclusions = new Set(entity.projection?.excluding ?? [])
160
+ const elements = Object.entries(entity.elements ?? {}).filter(([ename]) => !exclusions.has(ename))
161
+ for (const [ename, element] of elements) {
155
162
  this.visitElement(ename, element, file, buffer)
156
163
 
157
164
  // make foreign keys explicit
@@ -160,7 +167,10 @@ class Visitor {
160
167
  // We don't really have to care for this case, as keys from such structs are _not_ propagated to
161
168
  // the containing entity.
162
169
  for (const [kname, kelement] of Object.entries(this.csn.xtended.definitions[element.target]?.keys ?? {})) {
163
- this.visitElement(`${ename}_${kname}`, kelement, file, buffer)
170
+ if (this.resolver.getMaxCardinality(element) === 1) {
171
+ kelement.isRefNotNull = !!element.notNull || !!element.key
172
+ this.visitElement(`${ename}_${kname}`, kelement, file, buffer)
173
+ }
164
174
  }
165
175
  }
166
176
 
@@ -172,8 +182,8 @@ class Visitor {
172
182
 
173
183
  buffer.indent()
174
184
  for (const e of enums) {
175
- buffer.add(`static ${e.name} = ${propertyToAnonymousEnumName(clean, e.name)}`)
176
- file.addAnonymousEnum(clean, name, e.name, csnToEnum(e, {unwrapVals: true}))
185
+ buffer.add(`static ${e.name} = ${propertyToInlineEnumName(clean, e.name)}`)
186
+ file.addInlineEnum(clean, name, e.name, csnToEnumPairs(e, {unwrapVals: true}))
177
187
  }
178
188
  buffer.add('static actions: {')
179
189
  buffer.indent()
@@ -299,7 +309,7 @@ class Visitor {
299
309
  .filter(([, type]) => type?.type !== '$self' && !(type.items?.type === '$self'))
300
310
  .map(([name, type]) => [
301
311
  name,
302
- this.resolver.resolveAndRequire(type, file).typeName,
312
+ this.resolver.visitor.inlineDeclarationResolver.getPropertyDatatype(this.resolver.resolveAndRequire(type, file)),
303
313
  ])
304
314
  : []
305
315
  }
@@ -310,7 +320,9 @@ class Visitor {
310
320
  const ns = this.resolver.resolveNamespace(name.split('.'))
311
321
  const file = this.getNamespaceFile(ns)
312
322
  const params = this.#stringifyFunctionParams(func.params, file)
313
- const returns = this.resolver.resolveAndRequire(func.returns, file).typeName
323
+ const returns = this.resolver.visitor.inlineDeclarationResolver.getPropertyDatatype(
324
+ this.resolver.resolveAndRequire(func.returns, file)
325
+ )
314
326
  file.addFunction(name.split('.').at(-1), params, returns)
315
327
  }
316
328
 
@@ -319,7 +331,9 @@ class Visitor {
319
331
  const ns = this.resolver.resolveNamespace(name.split('.'))
320
332
  const file = this.getNamespaceFile(ns)
321
333
  const params = this.#stringifyFunctionParams(action.params, file)
322
- const returns = this.resolver.resolveAndRequire(action.returns, file).typeName
334
+ const returns = this.resolver.visitor.inlineDeclarationResolver.getPropertyDatatype(
335
+ this.resolver.resolveAndRequire(action.returns, file)
336
+ )
323
337
  file.addAction(name.split('.').at(-1), params, returns)
324
338
  }
325
339
 
@@ -329,7 +343,7 @@ class Visitor {
329
343
  const ns = this.resolver.resolveNamespace(name.split('.'))
330
344
  const file = this.getNamespaceFile(ns)
331
345
  if ('enum' in type) {
332
- file.addEnum(name, clean, csnToEnum(type))
346
+ file.addEnum(name, clean, csnToEnumPairs(type))
333
347
  } else {
334
348
  // alias
335
349
  file.addType(name, clean, this.resolver.resolveAndRequire(type, file).typeName)
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@cap-js/cds-typer",
3
- "version": "0.11.1",
3
+ "version": "0.13.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",
@@ -17,7 +17,7 @@
17
17
  "test:integration": "jest --projects test/int.jest.config.js",
18
18
  "test:all": "jest",
19
19
  "test": "npm run test:unit",
20
- "lint": "eslint",
20
+ "lint": "npx eslint .",
21
21
  "cli": "node lib/cli.js",
22
22
  "doc:clean": "rm -rf ./doc",
23
23
  "doc:prepare": "npm run doc:clean && mkdir -p doc/types",
@@ -40,10 +40,9 @@
40
40
  "@sap/cds": ">=6"
41
41
  },
42
42
  "devDependencies": {
43
+ "@babel/eslint-parser": "^7.23.3",
43
44
  "acorn": "^8.10.0",
44
45
  "eslint": "^8.15.0",
45
- "eslint-config-prettier": "^8.5.0",
46
- "eslint-plugin-prettier": "^4.0.0",
47
46
  "jest": "^29",
48
47
  "typescript": ">=4.6.4"
49
48
  },