@sap/cds-compiler 6.0.12 → 6.1.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
@@ -8,6 +8,31 @@ Note: `beta` fixes, changes and features are usually not listed in this ChangeLo
8
8
  but in [doc/CHANGELOG_BETA.md](doc/CHANGELOG_BETA.md).
9
9
  The compiler behavior concerning `beta` features can change at any time without notice.
10
10
 
11
+ ## Version 6.1.0 - 2025-06-27
12
+
13
+ ### Added
14
+
15
+ - for.odata:
16
+ + Introduce a new option `addAnnotationAddressViaNavigationPath` to annotate services
17
+ containing draft-enabled entities with `@Common.AddressViaNavigationPath`.
18
+ + Introduce a new option `draftMessages` that enhances the draft generation logic.
19
+
20
+ ### Changed
21
+
22
+ - Update OData vocabularies: Capabilities, Common
23
+
24
+ ### Fixed
25
+
26
+ - compiler: The ternary condition operator `…?…:…` is now right-associative as usual
27
+ (in v5, chaining it like in `…?…:…?…:…` was not possible without parentheses).
28
+
29
+ ## Version 6.0.14 - 2025-06-18
30
+
31
+ ### Fixed
32
+
33
+ - to.sql: Fix error when calculated element refers to a localized element.
34
+ - to.edm(x): Correctly handle `PropertyPath` in a collection when using expressions as annotation values
35
+
11
36
  ## Version 6.0.12 - 2025-06-06
12
37
 
13
38
  ### Changed
package/bin/cdsc.js CHANGED
@@ -299,7 +299,7 @@ function displayUsage( error, helpText, code ) {
299
299
  */
300
300
  async function createTemporaryFileFromStdin() {
301
301
  const contents = await readStream(process.stdin);
302
- const file = tmpFilePath('cds-compiler-stdin', 'cds');
302
+ const file = tmpFilePath('cds-compiler-stdin.cds');
303
303
  await fs.promises.writeFile(file, contents);
304
304
  return file;
305
305
  }
@@ -36,6 +36,7 @@ const publicOptionsNewAPI = [
36
36
  'booleanEquality',
37
37
  'dollarNowAsTimestamp',
38
38
  // ODATA
39
+ 'addAnnotationAddressViaNavigationPath',
39
40
  'odataOpenapiHints',
40
41
  'edm4OpenAPI',
41
42
  'odataVersion',
@@ -48,6 +49,7 @@ const publicOptionsNewAPI = [
48
49
  'odataV2PartialConstr',
49
50
  'odataVocabularies',
50
51
  'odataNoCreator',
52
+ 'draftMessages',
51
53
  'service',
52
54
  'serviceNames',
53
55
  // to.cdl
@@ -193,7 +193,7 @@ class CompileMessage {
193
193
  this.validNames = null;
194
194
  this.home = home; // semantic location, e.g. 'entity:"E"/element:"x"'
195
195
  this.severity = severity;
196
- Object.defineProperty( this, 'messageId', { value: id } );
196
+ this.messageId = id;
197
197
  Object.defineProperty( this, '$module', { value: moduleName, configurable: true } );
198
198
  // Uncomment when running TypeScript linter
199
199
  // this.messageId = id;
@@ -19,9 +19,17 @@ class PromiseAllError extends Error {
19
19
  *
20
20
  * This function only works as intended if no promise in `promises` fulfill
21
21
  * with a value which is an instance of Error.
22
+ *
23
+ * @param {Promise[]} promises
24
+ * @param {(e: Error) => boolean} shouldImmediatelyReject
25
+ * Determine on a per-promise basis whether we should immediately abort all promises.
22
26
  */
23
- function promiseAllDoNotRejectImmediately( promises ) {
24
- return Promise.all( promises.map( p => p.catch(e => e) ) )
27
+ function promiseAllDoNotRejectImmediately( promises, shouldImmediatelyReject = _error => false ) {
28
+ return Promise.all( promises.map( p => p.catch((e) => {
29
+ if (shouldImmediatelyReject(e))
30
+ throw e;
31
+ return e;
32
+ }) ) )
25
33
  .then( values => (values.some(e => e instanceof Error)
26
34
  ? Promise.reject( new PromiseAllError(
27
35
  values, 'At least one promise has been rejected'
@@ -21,7 +21,9 @@ function assertNoAssocUsageOutsideOfService( parent, prop, ref, path, grandparen
21
21
  return;
22
22
 
23
23
  const { _links } = parent;
24
- if (_links?.length <= 1)
24
+ // session variables can't have assoc steps, _links of 1 can't have assoc steps
25
+ // TODO: (typeof parentProp === 'number' && path[path.length - 2] === 'on') - ignore on-conditions, as they are cut off anyway
26
+ if (parent.$scope === '$magic' || _links?.length <= 1 )
25
27
  return;
26
28
 
27
29
  for (let i = 0; i < _links.length - 1; i++) {
@@ -148,6 +148,8 @@ function parserForFile( source, ext, options ) {
148
148
  // - { realname: fs.realpath(filename) }: if filename is not canonicalized
149
149
  //
150
150
  function compileX( filenames, dir = '', options = {}, fileCache = Object.create( null ) ) {
151
+ options.abortSignal?.throwIfAborted();
152
+
151
153
  // A non-proper dictionary (i.e. with prototype) is safe if the keys are
152
154
  // absolute file names - they start with `/` or `\` or similar
153
155
  // if (Object.getPrototypeOf( fileCache ))
@@ -163,7 +165,10 @@ function compileX( filenames, dir = '', options = {}, fileCache = Object.create(
163
165
  input = processedInput;
164
166
  model.sources = input.sources;
165
167
  } )
166
- .then( () => promiseAllDoNotRejectImmediately( input.files.map( readAndParse ) ) )
168
+ .then( () => promiseAllDoNotRejectImmediately(
169
+ input.files.map( readAndParse ),
170
+ e => e?.name === 'AbortError' // reject immediately if user wants to abort
171
+ ))
167
172
  .then( testInvocation, (reason) => {
168
173
  // do not reject with PromiseAllError, use InvocationError:
169
174
  const errs = reason.valuesOrErrors?.filter( e => e instanceof Error ) || [ reason ];
@@ -176,6 +181,7 @@ function compileX( filenames, dir = '', options = {}, fileCache = Object.create(
176
181
  all = all.then( readDependencies );
177
182
 
178
183
  return all.then( () => {
184
+ options.abortSignal?.throwIfAborted();
179
185
  moduleLayers.setLayers( input.sources );
180
186
  return compileDoX( model );
181
187
  } );
@@ -193,6 +199,9 @@ function compileX( filenames, dir = '', options = {}, fileCache = Object.create(
193
199
  sources[filename] = { location: new Location( rel ) };
194
200
 
195
201
  const source = await cdsFs( fileCache, options.traceFs ).readFileAsync( filename, 'utf8' );
202
+ // before running our compute-heavy parsing, check if user aborted
203
+ options.abortSignal?.throwIfAborted();
204
+
196
205
  const ast = parseX( source, rel, options, model.$messageFunctions );
197
206
  sources[filename] = ast;
198
207
  ast.location = new Location( rel );
@@ -527,7 +527,6 @@ function fns( model ) {
527
527
  if (traverseTypedExpr( args[0], exprCtx, user, null, callback ) === traverseExpr.STOP)
528
528
  return null;
529
529
  return args.slice( 1 );
530
- // TODO: adopt if we extend this to ?:?:…
531
530
  }
532
531
 
533
532
  function traverseCaseWhen( args, exprCtx, user, type, callback ) {
@@ -1442,6 +1442,15 @@ function csn2annotationEdm( reqDefs, reqDefsUtils, csnVocabularies, serviceName,
1442
1442
  { anno: msg.anno(), '#': 'enuminCollection' });
1443
1443
  }
1444
1444
  else if (value.$edmJson) {
1445
+ if (dTypeName === 'Edm.PropertyPath') {
1446
+ const dPropType = dTypeName.replace(/^Edm\./, '');
1447
+ if (!value.$edmJson[`$${ dPropType }`]) {
1448
+ // Needs to adapt the property type as per the dictionary, because expression refs are always
1449
+ // generated as kind $Path earlier on (edmJson::transform.ref)
1450
+ value.$edmJson[`$${ dPropType }`] = value.$edmJson.$Path;
1451
+ delete value.$edmJson.$Path;
1452
+ }
1453
+ }
1445
1454
  newCollection.append(handleEdmJson(value.$edmJson, msg));
1446
1455
  }
1447
1456
  else {
@@ -1,4 +1,4 @@
1
- // Base class for generated parser, for redepage v0.2.4
1
+ // Base class for generated parser, for redepage v0.2.5
2
2
 
3
3
  'use strict';
4
4
 
@@ -53,7 +53,7 @@ class BaseParser {
53
53
  s: this.s,
54
54
  stack: this.stack,
55
55
  dynamic_: this.dynamic_,
56
- prec_: this.prec_
56
+ prec_: this.prec_ // TODO: necessary?
57
57
  };
58
58
  }
59
59
 
@@ -433,13 +433,13 @@ class BaseParser {
433
433
  this.s = cmd[1];
434
434
  if (cmd[0] !== (lk1 ? 'ck' : 'ci')) { // make the std case fast
435
435
  // TODO: also not with lean condition
436
- let match1 = this._pred_next( 'Id', lk1, 'P' ); // TODO: really P for I?
436
+ let match1 = this._pred_next( 'Id', lk1, 'P' ); // first step of `K`/`I` prediction
437
437
  if (!match1) {
438
438
  if (lk1 || match1 === false) // assert for correct code generation
439
439
  throw Error( `Cannot match first prediction token in rule at state ${ saved.s }` );
440
- if (match1 == null) {
440
+ if (match1 == null) { // TODO: just return true, rule exit prediction will do it
441
441
  this._traceSubPush( 0 ); // TODO: make _pred_next push this
442
- match1 = this._matchesInFollow( 'Id', lk1, 'I' ); // TODO: 'I'?
442
+ match1 = this._matchesInFollow( 'Id', lk1, 'I' );
443
443
  }
444
444
  else {
445
445
  this._traceSubPush( false );
@@ -452,10 +452,12 @@ class BaseParser {
452
452
 
453
453
  this._traceSubPush( '' ); // between the two tokens
454
454
  ++this.tokenIdx; // for user lookahead fns and conditions
455
- let match2 = this._pred_next( lt2, lk2, (lk1 ? 'K' : 'I') );
455
+ const mode = lk1 ? 'K' : 'I';
456
+ let match2 = this._pred_next( lt2, lk2, mode );
456
457
  if (match2 == null) {
457
458
  this._traceSubPush( 0 ); // TODO: make _pred_next push this
458
- match2 = !!this._matchesInFollow( lt2, lk2, (lk1 ? 'K' : 'I') );
459
+ match2 = !!this._matchesInFollow( lt2, lk2, mode );
460
+ // TODO: we might use mode 'E' in _matchesInFollow (depends on caching)
459
461
  }
460
462
  else {
461
463
  this._traceSubPush( match2 );
@@ -485,8 +487,6 @@ class BaseParser {
485
487
  * condition is listed in `this.leanConditions`.
486
488
  */
487
489
  _pred_next( type, keyword, mode ) { // mode = P | K | I | E | R | M
488
- // TODO mode: really distinguish between K | I | E | R ?
489
- // Probably not: would not work with caching? → P, P -> F
490
490
  const properCall = (mode === 'P');
491
491
  const lean = (mode !== 'M'); // TODO: extra method with conditions ?
492
492
  // TODO: if false, use condition in this.leanConditions
@@ -609,6 +609,7 @@ class BaseParser {
609
609
  this._traceSubPush( match == null ? 0 : match === (mode !== 'R') );
610
610
  // successfully matching a keyword in giR() means unsuccessful match as
611
611
  // reserved identifer
612
+ // TODO: this.stack ?
612
613
  }
613
614
  this.dynamic_ = dynamic_;
614
615
  this.s = savedState;
@@ -773,7 +774,6 @@ class BaseParser {
773
774
  token.keyword = keyword;
774
775
  Object.assign( this, saved );
775
776
  this.trace = trace;
776
- // TODO: also trace M(…) collection, extra line for each token, with condition
777
777
  return expecting;
778
778
  }
779
779
 
@@ -1 +1 @@
1
- eb778bab22f4006cfcf64e418dcae71c
1
+ 7cfe4871a952fbc999cd1e8b06300f7f
@@ -1,4 +1,4 @@
1
- // Parser generated by redepage v0.2.4
1
+ // Parser generated by redepage v0.2.5
2
2
  'use strict;'
3
3
  const { XsnSource, XsnArtifact, XsnName } = require( '../compiler/xsn-model' )
4
4
  const AstBuildingParser = require('../parsers/AstBuildingParser')
@@ -1164,7 +1164,7 @@ Id:[656,673],
1164
1164
  '||':['c',660,,'precLeft_',20],
1165
1165
  and:['ck',660,,'precLeft_',4],
1166
1166
  or:['ck',660,,'precLeft_',2],
1167
- '?':['c',657,,'precLeft_',0],
1167
+ '?':['c',657,,'precRight_',0],
1168
1168
  '<':['c',659,,'precNone_',10],'=':'<','>':'<','!=':'<','<=':'<','<>':'<','>=':'<',
1169
1169
  '==':['c',660,,'precNone_',10],
1170
1170
  is:['ck',661,,'precNone_',10],
@@ -1786,7 +1786,7 @@ case 85:switch(this.lk()){
1786
1786
  case'limit':case'order':this.orderByLimitOffset({query},86);continue
1787
1787
  default:this.s=86;continue
1788
1788
  }
1789
- case 86:this.s=87;{this.afterBrace('normal'); ; }continue
1789
+ case 86:this.s=87;{this.afterBrace('normal')}continue;
1790
1790
  case 87:switch(this.lk()){
1791
1791
  case'actions':this.actionsBlock({art:$.art},0);continue
1792
1792
  default:this.gr([';']);continue
@@ -3087,7 +3087,7 @@ return this.exit_()
3087
3087
  }
3088
3088
  targetCardinality($,$next,$startState){
3089
3089
  this.rule_($startState??436,$next)
3090
- { if (!$.atAlt) $.card.sourceMax = $.card.targetMax; }
3090
+ if (!$.atAlt) $.card.sourceMax = $.card.targetMax
3091
3091
  for(;;)switch(this.s){
3092
3092
  case 436:switch(this.l()){
3093
3093
  case'*':if(this.c(0)){ $.card.targetMax = this.valueWithLocation(); }continue
@@ -3232,7 +3232,7 @@ case 473:switch(this.lk()){
3232
3232
  case'excluding':this.excludingClause({query:$.query},480);continue
3233
3233
  default:this.s=480;continue
3234
3234
  }
3235
- case 474:this.s=475;{this.inSelectItem('sqlStyle'); ; }continue
3235
+ case 474:this.s=475;{this.inSelectItem('sqlStyle')}continue;
3236
3236
  case 475:switch(this.l()){
3237
3237
  case'*':if(this.c(476)){ $.query.columns = [ this.valueWithLocation() ]; }continue
3238
3238
  case'Id':case'#':case'(':case'+':case'-':case':':case'?':case'@':case'{':case'Number':case'String':case'QuotedLiteral':this.selectItemDef({columns:($.query.columns = [])},476);continue
@@ -3578,7 +3578,7 @@ case 570:switch(this.lk()){
3578
3578
  case'virtual':if(this.lP(['Id','#','(','+','-',':','?','{','key','not','case','cast','null','true','false','Number','String','exists','QuotedLiteral'])&&this.gc(571,'modifierRestriction')&&this.ck(571)){ art.virtual = this.valueWithLocation( true ); }continue
3579
3579
  default:this.s=571;continue
3580
3580
  }
3581
- case 571:this.s=572;{this.columnExpr('key'); ; }continue
3581
+ case 571:this.s=572;{this.columnExpr('key')}continue;
3582
3582
  case 572:switch(this.lk()){
3583
3583
  case'key':if(this.gc(573,'modifierRestriction')&&this.ck(573)){ art.key = this.valueWithLocation( true ); }continue
3584
3584
  default:this.s=573;continue
@@ -3860,7 +3860,7 @@ case'+':case'-':if(this.gc(0,'precLeft_',22)&&this.c(660)){ $.expr = this.applyO
3860
3860
  case'||':if(this.gc(0,'precLeft_',20)&&this.c(660)){ $.expr = this.applyOpToken( $.expr, 'nary' ); }continue
3861
3861
  case'and':if(this.gc(0,'precLeft_',4)&&this.ck(660)){ $.expr = this.applyOpToken( $.expr, 'nary' ); }continue
3862
3862
  case'or':if(this.gc(0,'precLeft_',2)&&this.ck(660)){ $.expr = this.applyOpToken( $.expr, 'nary' ); }continue
3863
- case'?':if(this.gc(0,'precLeft_',0)&&this.c(657)){ $.expr = this.applyOpToken( $.expr, '?:' ); }continue
3863
+ case'?':if(this.gc(0,'precRight_',0)&&this.c(657)){ $.expr = this.applyOpToken( $.expr, '?:' ); }continue
3864
3864
  case'<':case'=':case'>':case'!=':case'<=':case'<>':case'>=':if(this.gc(0,'precNone_',10)&&this.c(659)){ $.expr = this.applyOpToken( $.expr ); }continue
3865
3865
  case'==':if(this.gc(0,'precNone_',10)&&this.c(660)){ $.expr = this.applyOpToken( $.expr ); }continue
3866
3866
  case'is':if(this.gc(0,'precNone_',10)&&this.ck(661)){ $.expr = this.applyOpToken( $.expr ); }continue
@@ -3231,13 +3231,6 @@
3231
3231
  "ValidationFunction": "Common.QualifiedName"
3232
3232
  }
3233
3233
  },
3234
- "Common.DraftUserAccessType": {
3235
- "$kind": "ComplexType",
3236
- "Properties": {
3237
- "UserAccessRole": "Edm.String",
3238
- "UserID": "Edm.String"
3239
- }
3240
- },
3241
3234
  "Common.EffectType": {
3242
3235
  "$deprecated": true,
3243
3236
  "$deprecationText": "All side effects are essentially value changes, differentiation not needed.",
@@ -1319,11 +1319,15 @@ function flattenInternalXpr( array, xprOp ) {
1319
1319
  function ternaryOperator( node ) {
1320
1320
  const rargs = [
1321
1321
  'case',
1322
- 'when', exprInternal(node.args[0]),
1323
- 'then', exprInternal(node.args[2]),
1324
- 'else', exprInternal(node.args[4]),
1325
- 'end',
1322
+ 'when', exprInternal( node.args[0] ),
1323
+ 'then', exprInternal( node.args[2] ),
1326
1324
  ];
1325
+ let right = node.args[4];
1326
+ for (; right.op?.val === '?:' && !right.$parens?.length; right = right.args[4]) {
1327
+ rargs.push( 'when', exprInternal( right.args[0] ),
1328
+ 'then', exprInternal( right.args[2] ) );
1329
+ }
1330
+ rargs.push( 'else', exprInternal( right ), 'end' );
1327
1331
 
1328
1332
  if (node.$parens?.length)
1329
1333
  return { xpr: flattenInternalXpr( rargs, 'xpr' ) };
package/lib/main.d.ts CHANGED
@@ -158,6 +158,16 @@ declare namespace compiler {
158
158
  * @since v4.2.0
159
159
  */
160
160
  moduleLookupDirectories?: string[]
161
+ /**
162
+ * An [`AbortSignal`](https://developer.mozilla.org/en-US/docs/Web/API/AbortSignal)
163
+ * that can be used to abort the compilation.
164
+ * Used for any `async` task, i.e. at the moment for reading/parsing files.
165
+ *
166
+ * Note that this flag has no effect on _synchronous_ compilation functions.
167
+ *
168
+ * @since v6.1
169
+ */
170
+ abortSignal?: AbortSignal
161
171
  /**
162
172
  * Option for {@link compileSources}. If set, all objects inside the
163
173
  * provided sources dictionary are interpreted as XSN structures instead
@@ -1558,8 +1568,6 @@ declare namespace compiler {
1558
1568
  constructor(location: Location, msg: string, severity?: MessageSeverity, id?: string | null, home?: string | null, moduleName?: string | null);
1559
1569
  /**
1560
1570
  * Optional ID of the message. Can be used to reclassify messages.
1561
- *
1562
- * @note This property is non-enumerable as message IDs are not finalized, yet.
1563
1571
  */
1564
1572
  messageId?: string
1565
1573
 
@@ -263,6 +263,8 @@ optionProcessor.command('O, toOdata')
263
263
  .option(' --odata-v2-partial-constr')
264
264
  .option(' --odata-vocabularies <list>')
265
265
  .option(' --odata-no-creator')
266
+ .option(' --draft-messages')
267
+ .option(' --add-annotation-AddressViaNavigationPath')
266
268
  .option('-c, --csn')
267
269
  .option('-f, --odata-format <format>', { valid: [ 'flat', 'structured' ] })
268
270
  .option('-n, --sql-mapping <style>', { valid: [ 'plain', 'quoted', 'hdbcds' ], aliases: [ '--names' ] })
@@ -297,7 +299,10 @@ optionProcessor.command('O, toOdata')
297
299
  --odata-vocabularies <list> JSON array of adhoc vocabulary definitions
298
300
  { prefix: { alias, ns, uri }, ... }
299
301
  --odata-no-creator Omit creator identification in API
300
- -n, --sql-mapping <style> Annotate artifacts and elements with "@cds.persistence.name", which is
302
+ --draft-messages Add draft messages as part of the draft creation
303
+ --add-annotation-AddressViaNavigationPath Add annotation "@Common.AddressViaNavigationPath" to the services
304
+ containing draft enabled entitties
305
+ -n, --sql-mapping <style> Annotate artifacts and elements with "@cds.persistence.name", which is
301
306
  the corresponding database name (see "--sql-mapping" for "toSql")
302
307
  plain : (default) Names in uppercase and flattened with underscores
303
308
  quoted : Names in original case as in CDL. Entity names with dots,
@@ -697,8 +697,10 @@ function removeDummyValueInEntity( artifact, path, options ) {
697
697
  function dummifyInEntity( artifact, path ) {
698
698
  applyTransformationsOnDictionary(artifact.elements, {
699
699
  value: (parent, _prop, value) => {
700
- if (!value.stored)
700
+ if (!value.stored) {
701
701
  parent.value = { val: 'DUMMY' };
702
+ delete parent.localized;
703
+ }
702
704
  },
703
705
  }, {}, path);
704
706
  }
@@ -232,7 +232,7 @@ function generateDrafts( csn, options, pathDelimiter, messageFunctions ) {
232
232
  draftAdministrativeData.DraftAdministrativeData.notNull = true;
233
233
  addElement(draftAdministrativeData, draftsArtifact, artifactName);
234
234
 
235
- if (isBetaEnabled(options, 'draftMessages')) {
235
+ if (isBetaEnabled(options, 'draftMessages') || options.draftMessages) {
236
236
  const draftMessages = { DraftMessages: { '@Core.Computed': true, virtual: true, items: { type: 'DRAFT.DraftAdministrativeData_DraftMessage' } } };
237
237
  addElement(draftMessages, draftsArtifact, artifactName);
238
238
  }
@@ -59,7 +59,7 @@ function generateDrafts( csn, options, services, messageFunctions ) {
59
59
  const filterDict = Object.create(null);
60
60
 
61
61
  // validate the 'DRAFT.DraftAdministrativeData_DraftMessage' type if already present in the model
62
- if (isBetaEnabled(options, 'draftMessages')) {
62
+ if (isBetaEnabled(options, 'draftMessages') || options.draftMessages) {
63
63
  const draftAdminDataMessagesType = csn.definitions['DRAFT.DraftAdministrativeData_DraftMessage'];
64
64
  if (draftAdminDataMessagesType && !isValidDraftAdminDataMessagesType(draftAdminDataMessagesType)) {
65
65
  error(null, [ 'definitions', 'DRAFT.DraftAdministrativeData_DraftMessage' ], { name: 'DRAFT.DraftAdministrativeData_DraftMessage' },
@@ -180,7 +180,7 @@ function generateDrafts( csn, options, services, messageFunctions ) {
180
180
  // ... on SiblingEntity.IsActiveEntity != IsActiveEntity ...
181
181
  siblingEntity.SiblingEntity.on = createAssociationPathComparison('SiblingEntity', 'IsActiveEntity', '!=', 'IsActiveEntity');
182
182
 
183
- if (isBetaEnabled(options, 'draftMessages')) {
183
+ if (isBetaEnabled(options, 'draftMessages') || options.draftMessages) {
184
184
  const draftMessages = { DraftMessages: { '@Core.Computed': true, virtual: true, items: { type: 'DRAFT.DraftAdministrativeData_DraftMessage' } } };
185
185
  addElement(draftMessages, artifact, artifactName);
186
186
 
@@ -189,8 +189,11 @@ function generateDrafts( csn, options, services, messageFunctions ) {
189
189
  setAnnotation(artifact, '@Common.SideEffects#alwaysFetchMessages.TargetProperties', ['DraftMessages'] );
190
190
  }
191
191
  setAnnotation(artifact, '@Common.Messages', { '=': 'DraftMessages', ref: ['DraftMessages'] });
192
- const service = csn.definitions[getServiceOfArtifact(artifactName, services)];
193
- setAnnotation(service, '@Common.AddressViaNavigationPath', true);
192
+ setAnnotationAddressViaNavigationPath(artifactName, services);
193
+ }
194
+
195
+ if (options.addAnnotationAddressViaNavigationPath) {
196
+ setAnnotationAddressViaNavigationPath(artifactName, services);
194
197
  }
195
198
 
196
199
  // Iterate elements
@@ -291,6 +294,13 @@ function generateDrafts( csn, options, services, messageFunctions ) {
291
294
  }
292
295
  })
293
296
  }
297
+
298
+ // Set the @Common.AddressViaNavigationPath annotation to the service of
299
+ // the current artifact, if not set already
300
+ function setAnnotationAddressViaNavigationPath(artifactName, services) {
301
+ const service = csn.definitions[getServiceOfArtifact(artifactName, services)];
302
+ setAnnotation(service, '@Common.AddressViaNavigationPath', true);
303
+ }
294
304
  }
295
305
 
296
306
  module.exports = generateDrafts;
@@ -402,7 +402,7 @@ function getTransformers(model, options, msgFunctions, pathDelimiter = '_') {
402
402
  if (!draftAdminDataEntity) {
403
403
  draftAdminDataEntity = createAndAddDraftAdminDataEntity();
404
404
  model.definitions['DRAFT.DraftAdministrativeData'] = draftAdminDataEntity;
405
- if (isBetaEnabled(options, 'draftMessages')
405
+ if ((isBetaEnabled(options, 'draftMessages') || options.draftMessages)
406
406
  && options.transformation === 'odata'
407
407
  && !model.definitions['DRAFT.DraftAdministrativeData_DraftMessage']) {
408
408
  model.definitions['DRAFT.DraftAdministrativeData_DraftMessage'] = createDraftAdminDataMessagesType();
@@ -475,7 +475,7 @@ function getTransformers(model, options, msgFunctions, pathDelimiter = '_') {
475
475
  draftIsProcessedByMe.DraftIsProcessedByMe['@Common.Label'] = '{i18n>Draft_DraftIsProcessedByMe}';
476
476
  addElement(draftIsProcessedByMe, artifact, artifactName);
477
477
 
478
- if (isBetaEnabled(options, 'draftMessages')) {
478
+ if (isBetaEnabled(options, 'draftMessages') || options.draftMessages) {
479
479
  const messages = { DraftMessages: { } };
480
480
  if (options.transformation === 'odata') {
481
481
  messages.DraftMessages = { items: { type: 'DRAFT.DraftAdministrativeData_DraftMessage' } };
package/lib/utils/file.js CHANGED
@@ -32,18 +32,14 @@ function fileExtension( filename ) {
32
32
  }
33
33
 
34
34
  /**
35
- * Create a temporary file path using the system's temporary folder and a filename
36
- * consisting of the given name/extension and a random string.
35
+ * Create a temporary file path using the system's temporary folder.
37
36
  *
38
- * @param {string} name
39
- * @param {string} extension
37
+ * @param {string} filename
40
38
  * @returns {string}
41
39
  */
42
- function tmpFilePath( name, extension ) {
43
- const crypto = require('crypto');
44
- const id = crypto.randomBytes(32).toString('hex');
45
- const filename = `${ name }-${ id }.${ extension }`;
46
- return path.join(os.tmpdir(), filename);
40
+ function tmpFilePath( filename ) {
41
+ const dir = fs.mkdtempSync(fs.realpathSync(os.tmpdir()) + path.sep);
42
+ return path.join(dir, filename);
47
43
  }
48
44
 
49
45
  /**
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sap/cds-compiler",
3
- "version": "6.0.12",
3
+ "version": "6.1.0",
4
4
  "description": "CDS (Core Data Services) compiler and backends",
5
5
  "homepage": "https://cap.cloud.sap/",
6
6
  "author": "SAP SE (https://www.sap.com)",