@sap/cds-dk 9.9.2 → 9.9.3

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
@@ -6,6 +6,13 @@ This project adheres to [Semantic Versioning](https://semver.org/).
6
6
 
7
7
  The format is based on [Keep a Changelog](https://keepachangelog.com/).
8
8
 
9
+ ## Version 9.9.3 - 2026-06-29
10
+
11
+ ### Fixed
12
+
13
+ - `cds import` for OData v2 now can import annotations.
14
+ - `cds watch` on Windows no longer restarts the server for read-only file opens (antivirus, Windows Search indexer, TypeScript loaders like tsx). A mtime guard now confirms a file was actually written before emitting a `change` event.
15
+
9
16
  ## Version 9.9.2 - 2026-05-28
10
17
 
11
18
  ### Fixed
package/bin/import.js CHANGED
@@ -2,7 +2,7 @@ module.exports = Object.assign(_import, {
2
2
  _name: 'import',
3
3
  options: [ '--services', '--for', '--out', '--as', '--name', '--include-namespaces', '--dependencies', '--from', '--config', '--destination', '--group', '--into-namespace' ],
4
4
  shortcuts: [ '-s', '-4', '-o', '-as', '-n', '-ns', '-d',undefined, '-c', undefined, undefined, '-2', '-', '-f', '-b'],
5
- flags: [ '--dry', '--force', '--resolve-bindings', '--no-copy', '--no-save', '--keep-namespace', '--data-product', '--edmx', '--rfc', '--odata', '--openapi', '--asyncapi' ],
5
+ flags: [ '--dry', '--force', '--resolve-bindings', '--no-copy', '--no-save', '--keep-namespace', '--data-product', '--edmx', '--rfc', '--odata', '--openapi', '--asyncapi', '--odata-v2-annotations' ],
6
6
  help: `
7
7
  # SYNOPSIS
8
8
 
@@ -79,6 +79,11 @@ module.exports = Object.assign(_import, {
79
79
  Keeps the original namespace from the edmx content instead of using
80
80
  the filename by default.
81
81
 
82
+ *--odata-v2-annotations*
83
+ Imports OData annotations into the CSN output. For OData V2, annotations
84
+ are not imported by default and must be explicitly enabled with this flag.
85
+ For OData V4, annotations are always imported.
86
+
82
87
  *-ns | --include-namespaces <string_of_namespaces> or* "\x2A"
83
88
  Imports only entities matching to the given list of namespaces.
84
89
  For OData V2 attributes with the namespace "sap" & "m" are captured by
@@ -15,10 +15,11 @@ let optionsMapping = {
15
15
  'keep-namespace': 'keepNamespace',
16
16
  'include-namespaces': 'includeNamespaces',
17
17
  'no-copy': 'no_copy',
18
- 'no-save': 'no_save'
18
+ 'no-save': 'no_save',
19
+ 'odata-v2-annotations': 'odataV2Annotations'
19
20
  };
20
21
 
21
- let apiOptions = ['includeNamespaces', 'keepNamespace'];
22
+ let apiOptions = ['includeNamespaces', 'keepNamespace', 'odataV2Annotations'];
22
23
  let commonOptions = ['for', 'no_copy', 'no_save', 'out', 'dry', 'as', 'from', 'force', 'config', 'destination', 'group', 'name'];
23
24
  let usingStatements = [];
24
25
 
@@ -64,7 +64,7 @@ async function cds_import_from_edmx(filepath, options = { keepNamespace: false }
64
64
  filepath = await common.isValidInputFile(path.resolve(process.cwd(), filepath));
65
65
  const src = await common.getContentFromFile(filepath);
66
66
  // takes the api specific options and reads it from env
67
- let odataOptions = { include_namespaces: options.includeNamespaces, keep_namespace: options.keepNamespace};
67
+ let odataOptions = { include_namespaces: options.includeNamespaces, keep_namespace: options.keepNamespace, odataV2Annotations: options.odataV2Annotations};
68
68
  if (cds.env.import) {
69
69
  common.readEnvVariables(odataOptions, false);
70
70
  }
@@ -0,0 +1,316 @@
1
+ // annotation parsing funtions common for v2parser.js and v4parser.js
2
+
3
+ "use strict";
4
+
5
+ const { warn } = require("../../util/term");
6
+
7
+ const known_vocabularies = {
8
+ 'Org.OData.Authorization.V1': 'Authorization',
9
+ 'Org.OData.Aggregation.V1': 'Aggregation',
10
+ 'Org.OData.Core.V1': 'Core',
11
+ 'Org.OData.Capabilities.V1': 'Capabilities',
12
+ 'Org.OData.Validation.V1': 'Validation',
13
+ 'Org.OData.Measures.V1': 'Measures',
14
+ 'Org.OData.JSON.V1': 'JSON',
15
+ 'Org.OData.Repeatability.V1': 'Repeatability',
16
+
17
+ 'com.sap.vocabularies.Analytics.v1': 'Analytics',
18
+ 'com.sap.vocabularies.CodeList.v1': 'CodeList',
19
+ 'com.sap.vocabularies.Common.v1': 'Common',
20
+ 'com.sap.vocabularies.Communication.v1': 'Communication',
21
+ 'com.sap.vocabularies.DataIntegration.v1': 'DataIntegration',
22
+ 'com.sap.vocabularies.Graph.v1': 'Graph',
23
+ 'com.sap.vocabularies.Hierarchy.v1': 'Hierarchy',
24
+ 'com.sap.vocabularies.HTML5.v1': 'HTML5',
25
+ 'com.sap.vocabularies.ODM.v1': 'ODM',
26
+ 'com.sap.vocabularies.Offline.v1': 'Offline',
27
+ 'com.sap.vocabularies.PDF.v1': 'PDF',
28
+ 'com.sap.vocabularies.PersonalData.v1': 'PersonalData',
29
+ 'com.sap.vocabularies.Session.v1': 'Session',
30
+ 'com.sap.vocabularies.UI.v1': 'UI'
31
+ };
32
+
33
+ function _replaceSpecialCharacters(text) {
34
+ return text.replace(/(?:\\[rn]|[\r\n]+)+/gm, "\n").replace(/\s+/g, ' ').replace(/"/g, "&quot;").trim();
35
+ }
36
+
37
+ function captureAnnotationInCSN(targetString, annotationObject, csnDefsTargetObject) {
38
+ /**
39
+ * For scenarios like:
40
+ * <Annotation Term="Core.Computed" />
41
+ * Here, 'annotationObject' would be null but in CSN we need to treat it as:
42
+ * @Core.Computed : true
43
+ */
44
+ if (annotationObject === null) {
45
+ annotationObject = true;
46
+ }
47
+ /**
48
+ * if annotationObject is empty list or empty object, we won't capture it.
49
+ * eg., <Annotation Term="Core.OptimisticConcurrency"> <Collection /> </Annotation>
50
+ * TODO: we might have to revisit it later for nested scenario.
51
+ */
52
+ if ((annotationObject instanceof Array && annotationObject.length === 0) ||
53
+ (annotationObject instanceof Object && Object.keys(annotationObject).length === 0)
54
+ ) {
55
+ return;
56
+ }
57
+ /**
58
+ * <Annotation Term="Common.Text" Path="name"/> --> annotationTermObject = { "=": annotationTermObject.$Path };
59
+ */
60
+ if (annotationObject.$Path) {
61
+ annotationObject = { "=": annotationObject.$Path };
62
+ }
63
+
64
+ /**
65
+ * Annotations need to be flattened in CSN. E.g.
66
+ *
67
+ * @Core.ContentDisposition : {
68
+ * Type : 'inline',
69
+ * Filename: typedImageFileName
70
+ * }
71
+ *
72
+ * is represented as:
73
+ *
74
+ * @Core.ContentDisposition.Type : 'inline',
75
+ * @Core.ContentDisposition.Filename : {'=': 'typedImageFileName'},
76
+ */
77
+ if (annotationObject instanceof Object && !(annotationObject instanceof Array) && !(Object.keys(annotationObject).length === 1 && (Object.keys(annotationObject)[0] === '=' || Object.keys(annotationObject)[0] === 'xpr' || Object.keys(annotationObject)[0] === '#'))) {
78
+ Object.keys(annotationObject).forEach(innerTargetString => {
79
+ captureAnnotationInCSN(targetString + '.' + innerTargetString, annotationObject[innerTargetString], csnDefsTargetObject);
80
+ });
81
+ } else {
82
+ csnDefsTargetObject[targetString] = annotationObject;
83
+ }
84
+ }
85
+
86
+ function flattenAnnotationObject(annotationPrefix, annotationObject) {
87
+ let flattenedResult = [];
88
+
89
+ Object.entries(annotationObject).forEach(annotation => {
90
+ let currentResult = [];
91
+ /**
92
+ * if annotation looks like:
93
+ * { "#" : <value> } or { "=" : <value> }
94
+ * don't flatten because it is for the `EnumMember` & `Path` use cases respectively
95
+ */
96
+ if (annotation[0] === '#' || annotation[0] === '=') {
97
+ currentResult[0] = annotationPrefix;
98
+ currentResult[1] = annotationObject;
99
+ } else {
100
+ currentResult[0] = annotationPrefix + '.' + annotation[0];
101
+ currentResult[1] = annotation[1];
102
+ }
103
+ flattenedResult.push(currentResult);
104
+ });
105
+
106
+ return flattenedResult;
107
+ }
108
+
109
+ function removeTypeInAnnotation(annotationObject) {
110
+ Object.keys(annotationObject).forEach(key => {
111
+ if (key === "$Type") delete annotationObject[key];
112
+ else if (annotationObject[key] instanceof Object) {
113
+ removeTypeInAnnotation(annotationObject[key]);
114
+ }
115
+ });
116
+ }
117
+
118
+ function parseCoreTerm(annotationTerm, annotationTermObject, csnDefsTargetObject) {
119
+ if (annotationTerm === "@Core.Description" || annotationTerm === "@Core.LongDescription") {
120
+ // we don't capture `null` value for doc
121
+ if (annotationTermObject === null) return;
122
+
123
+ annotationTermObject = _replaceSpecialCharacters(annotationTermObject);
124
+
125
+ if (csnDefsTargetObject.doc) {
126
+ if (annotationTerm === "@Core.Description") {
127
+ annotationTermObject = annotationTermObject + '\n\n' + csnDefsTargetObject.doc;
128
+ } else {
129
+ annotationTermObject = csnDefsTargetObject.doc + '\n\n' + annotationTermObject;
130
+ }
131
+ }
132
+ return captureAnnotationInCSN('doc', annotationTermObject, csnDefsTargetObject);
133
+ }
134
+
135
+ /**
136
+ * <Annotation Term="Core.XYZ"/>
137
+ * For such scenario, mark the `annotationTermObject` as `true`
138
+ */
139
+ if (annotationTermObject === null) annotationTermObject = true;
140
+
141
+ if (annotationTerm === "@Core.OptionalParameter") {
142
+ if (annotationTermObject && annotationTermObject.DefaultValue !== undefined) {
143
+ csnDefsTargetObject.default = { val: annotationTermObject.DefaultValue };
144
+ }
145
+ } else {
146
+ captureAnnotationInCSN(annotationTerm, annotationTermObject, csnDefsTargetObject);
147
+ }
148
+ }
149
+
150
+ function parseCapabilitiesTerm(annotationTerm, annotationTermObject, csnDefsTargetObject) {
151
+ let targetString;
152
+ /**
153
+ * <Annotation Term="Capabilities.KeyAsSegmentSupported" />
154
+ * For such scenario, mark the `annotationTermObject` as `true`
155
+ */
156
+ if (annotationTermObject === null) annotationTermObject = true;
157
+
158
+ removeTypeInAnnotation(annotationTermObject);
159
+ /**
160
+ * For entries like:
161
+ * <Annotation Term="Capabilities.FilterFunctions"> <Collection> <String>eq</String> <String>ne</String> </Collection> </Annotation>
162
+ * <Annotation Term="Capabilities.DeleteRestrictions.Deletable" Bool="true"/>
163
+ *
164
+ * simply preserve the value as it is
165
+ */
166
+ if (annotationTermObject instanceof Array || !(annotationTermObject instanceof Object)) {
167
+ captureAnnotationInCSN(annotationTerm, annotationTermObject, csnDefsTargetObject);
168
+ }
169
+ else {
170
+ Object.keys(annotationTermObject).forEach(annoKey => {
171
+ let currentAnnoValue = annotationTermObject[annoKey];
172
+ // if the currentAnnoValue is an Object, we need to flatten it
173
+ if (currentAnnoValue instanceof Object && !(currentAnnoValue instanceof Array)) {
174
+ targetString = annotationTerm + '.' + annoKey;
175
+ let flattenedResultList = flattenAnnotationObject(targetString, currentAnnoValue);
176
+
177
+ for (let idx = 0; idx < flattenedResultList.length; idx++) {
178
+ captureAnnotationInCSN(flattenedResultList[idx][0], flattenedResultList[idx][1], csnDefsTargetObject);
179
+ }
180
+ }
181
+ // if the currentAnnoValue is a list or direct value, we preserve as it is
182
+ else {
183
+ targetString = annotationTerm + '.' + annoKey;
184
+ captureAnnotationInCSN(targetString, currentAnnoValue, csnDefsTargetObject);
185
+ }
186
+ });
187
+ }
188
+ }
189
+
190
+ function parseValidationTerm(annotationTerm, annotationTermObject, csnDefsTargetObject) {
191
+ const resolveCastFor = ["Minimum", "Maximum", "MaxItems"];
192
+ const term = annotationTerm.split('.')[1];
193
+
194
+ if (resolveCastFor.includes(term) && annotationTermObject.$Cast) {
195
+ annotationTermObject = Number(annotationTermObject.$Cast);
196
+ }
197
+
198
+ captureAnnotationInCSN(annotationTerm, annotationTermObject, csnDefsTargetObject);
199
+ }
200
+
201
+ /**
202
+ * Capture all the annotation cases within an Element
203
+ *
204
+ * UnKnown term: <Annotation Term="Org.OData.ASDF.V1.qwe"/>
205
+ * Known term: <Annotation Term="Org.OData.Measures.V1.Unit" String="Centimeters"/>
206
+ *
207
+ * if "--include-namespaces *" then we also capture for ABS and QWE
208
+ * <TypeDefinition Name="TD_2" UnderlyingType="Edm.Int32" ABS:asdf="testing" QWE:qwe="testing"/>
209
+ */
210
+ function parseAnnotationTerms(annotationTerm, annotationTermObject, csnDefsTargetObject) {
211
+ const term = annotationTerm.split('.');
212
+ const vocabAlias = term[0];
213
+
214
+ switch (vocabAlias) {
215
+ case "@Core":
216
+ parseCoreTerm(annotationTerm, annotationTermObject, csnDefsTargetObject);
217
+ break;
218
+ case "@Capabilities":
219
+ parseCapabilitiesTerm(annotationTerm, annotationTermObject, csnDefsTargetObject);
220
+ break;
221
+ case "@Validation":
222
+ parseValidationTerm(annotationTerm, annotationTermObject, csnDefsTargetObject);
223
+ break;
224
+ default:
225
+ captureAnnotationInCSN(annotationTerm, annotationTermObject, csnDefsTargetObject);
226
+ break;
227
+ }
228
+ }
229
+
230
+ function validateAnnotationNamespace(annotationTerm, vocabNamespaceToAlias) {
231
+ let aliasOrNamespace = annotationTerm.substring(1, annotationTerm.lastIndexOf('.'));
232
+
233
+ // This check is for ignoring the @cds annotations given for special cases, e.g. @cds.validate = false for collided action/function
234
+ if (aliasOrNamespace === "cds") {
235
+ return;
236
+ }
237
+
238
+ for (let alias of Object.values(known_vocabularies)) {
239
+ if (alias === aliasOrNamespace) {
240
+ return;
241
+ }
242
+ }
243
+
244
+ for (let [namespace, alias] of Object.entries(vocabNamespaceToAlias || {})) {
245
+ if (alias === aliasOrNamespace || namespace === aliasOrNamespace) {
246
+ console.log(warn(annotationTerm + " AnnotationTerm belongs to an unknown namespace: " + namespace));
247
+ return;
248
+ }
249
+ }
250
+
251
+ console.log(warn(annotationTerm + ": Reference of this AnnotationTerm is not present in the edmx file."));
252
+ }
253
+
254
+ function resolveAnnotationTerm(schemaDataAnnotation, parserContext) {
255
+ for (const [namespace, alias] of Object.entries(parserContext.vocabNamespaceToAlias || {})) {
256
+ if (known_vocabularies[namespace]) {
257
+ // Case 1: Namespace belongs to known vocabularies. Replacing the namespace/alias of the doc with standard alias
258
+ if (schemaDataAnnotation.includes('@' + alias + '.')) {
259
+ schemaDataAnnotation = schemaDataAnnotation.split('@' + alias + '.').join('@' + known_vocabularies[namespace] + '.');
260
+ }
261
+ if (schemaDataAnnotation.includes('@' + namespace + '.')) {
262
+ schemaDataAnnotation = schemaDataAnnotation.split('@' + namespace + '.').join('@' + known_vocabularies[namespace] + '.');
263
+ }
264
+ } else {
265
+ // Case 2: Namespace doesn't belong to known vocabularies. Replacing the namespace with alias(if provided) in the doc.
266
+ if (schemaDataAnnotation.includes('@' + namespace + '.') && alias) {
267
+ schemaDataAnnotation = schemaDataAnnotation.split('@' + namespace + '.').join('@' + alias + '.');
268
+ }
269
+ }
270
+ }
271
+ /**
272
+ * Case 3: Namespace doesn't have edmx:Reference. But it is present in known_vocabularies.
273
+ * Replacing it with standard alias.
274
+ */
275
+ for (const [namespace, alias] of Object.entries(known_vocabularies)) {
276
+ if (schemaDataAnnotation.includes(namespace)) {
277
+ schemaDataAnnotation = schemaDataAnnotation.split(namespace).join(alias);
278
+ }
279
+ }
280
+
281
+ let index = schemaDataAnnotation.indexOf('"@');
282
+ while (index != -1) {
283
+ let i = schemaDataAnnotation.indexOf('"', index + 1);
284
+ let annotationTerm = schemaDataAnnotation.substring(index + 1, i);
285
+ validateAnnotationNamespace(annotationTerm, parserContext.vocabNamespaceToAlias);
286
+ index = schemaDataAnnotation.indexOf('"@', i + 1);
287
+ }
288
+
289
+ return JSON.parse(schemaDataAnnotation);
290
+ }
291
+
292
+ function parseAnnotations(schemaAnnotations, csnDefs, parserContext, resolveTarget) {
293
+ Object.keys(schemaAnnotations || {}).forEach(annotationTargetPath => {
294
+ let csnDefsTargetObject = resolveTarget(annotationTargetPath, csnDefs, parserContext);
295
+ if (csnDefsTargetObject) {
296
+ let annotationTerms = schemaAnnotations[annotationTargetPath];
297
+ Object.keys(annotationTerms).forEach(annotationTerm => {
298
+ let annotationTermObject = schemaAnnotations[annotationTargetPath][annotationTerm];
299
+ parseAnnotationTerms(annotationTerm, annotationTermObject, csnDefsTargetObject);
300
+ });
301
+ }
302
+ });
303
+ }
304
+
305
+ module.exports = {
306
+ captureAnnotationInCSN,
307
+ flattenAnnotationObject,
308
+ removeTypeInAnnotation,
309
+ parseCoreTerm,
310
+ parseCapabilitiesTerm,
311
+ parseValidationTerm,
312
+ parseAnnotationTerms,
313
+ validateAnnotationNamespace,
314
+ resolveAnnotationTerm,
315
+ parseAnnotations
316
+ };
@@ -8,6 +8,7 @@ let messages = require("../message").getMessages();
8
8
  let common = require("../common");
9
9
  let versionInfo = require('../../../package.json').version;
10
10
  const { warn } = require('../../util/term');
11
+ const annotationParser = require("./annotationParser");
11
12
 
12
13
  const edmxncdsdatatype = {
13
14
  "Edm.String": "cds.String",
@@ -78,6 +79,7 @@ function _initialize(parserContext) {
78
79
  parserContext.allowedNamespaces = [];
79
80
  parserContext.allowAllNamespaces = false;
80
81
  parserContext.mockServerUc = true;
82
+ parserContext.vocabNamespaceToAlias = {};
81
83
  }
82
84
 
83
85
  // Function to extract allowed namespace attributes from given set of attributes
@@ -1673,6 +1675,25 @@ function _generateEDMXJSON2CSN(jsonObj, ignorePersistenceSkip, parserContext) {
1673
1675
  csn.$version = '2.0';
1674
1676
  csn.definitions = {};
1675
1677
 
1678
+ // Parse edmx:Reference elements to build vocabNamespaceToAlias mapping
1679
+ const references = jsonObj["edmx:Edmx"]["edmx:Reference"];
1680
+ if (references) {
1681
+ const refArray = Array.isArray(references) ? references : [references];
1682
+ refArray.forEach(ref => {
1683
+ const include = ref["edmx:Include"];
1684
+ if (include) {
1685
+ const includeArray = Array.isArray(include) ? include : [include];
1686
+ includeArray.forEach(inc => {
1687
+ const namespace = inc._attributes?.Namespace;
1688
+ const alias = inc._attributes?.Alias;
1689
+ if (namespace && alias) {
1690
+ parserContext.vocabNamespaceToAlias[namespace] = alias;
1691
+ }
1692
+ });
1693
+ }
1694
+ });
1695
+ }
1696
+
1676
1697
  // Extract associations
1677
1698
  const associations = jsonObj["edmx:Edmx"]["edmx:DataServices"].Schema.Association;
1678
1699
  if (associations) {
@@ -1742,12 +1763,204 @@ function _generateEDMXJSON2CSN(jsonObj, ignorePersistenceSkip, parserContext) {
1742
1763
 
1743
1764
  try {
1744
1765
  csn.definitions = JSON.parse(`{${definitions.join(",")}}`);
1766
+
1767
+ // Parse annotations after definitions are populated
1768
+ // Note: V2 annotations are opt-in (disabled by default for backward compatibility)
1769
+ // and require --odata-v2-annotations flag. V4 always imports annotations.
1770
+ const schemaAnnotations = jsonObj["edmx:Edmx"]["edmx:DataServices"].Schema.Annotations;
1771
+ if (schemaAnnotations && parserContext.annotations) {
1772
+ _parseV2SchemaAnnotations(schemaAnnotations, csn.definitions, parserContext);
1773
+ }
1774
+
1745
1775
  return JSON.stringify(csn, null, 4);
1746
1776
  } catch {
1747
1777
  throw new Error("Unable to convert to csn file.");
1748
1778
  }
1749
1779
  }
1750
1780
 
1781
+ function _toArray(node) {
1782
+ if (!node) return [];
1783
+ return Array.isArray(node) ? node : [node];
1784
+ }
1785
+
1786
+ function _readAnnotationPrimitive(attributes) {
1787
+ if (attributes == null) return null;
1788
+
1789
+ if (attributes.Bool !== undefined) return attributes.Bool === 'true';
1790
+ if (attributes.String !== undefined) return attributes.String;
1791
+ if (attributes.Int !== undefined) return Number(attributes.Int);
1792
+ if (attributes.Decimal !== undefined) return Number(attributes.Decimal);
1793
+ if (attributes.Path !== undefined) return { '=': attributes.Path };
1794
+ if (attributes.PropertyPath !== undefined) return { '=': attributes.PropertyPath };
1795
+ if (attributes.NavigationPropertyPath !== undefined) return { '=': attributes.NavigationPropertyPath };
1796
+ if (attributes.AnnotationPath !== undefined) return { '=': attributes.AnnotationPath };
1797
+
1798
+ return null;
1799
+ }
1800
+
1801
+ function _parseV2AnnotationNode(node) {
1802
+ if (node === null || node === undefined) return null;
1803
+ if (Array.isArray(node)) return node.map(item => _parseV2AnnotationNode(item));
1804
+ if (typeof node !== 'object') return node;
1805
+
1806
+ if (node._attributes) {
1807
+ const primitiveValue = _readAnnotationPrimitive(node._attributes);
1808
+ const childKeys = Object.keys(node).filter(key => key !== '_attributes');
1809
+ if (primitiveValue !== null && childKeys.length === 0) {
1810
+ return primitiveValue;
1811
+ }
1812
+ }
1813
+
1814
+ if (node.Collection) return _parseV2Collection(node.Collection);
1815
+ if (node.Record) return _parseV2Record(node.Record);
1816
+
1817
+ const result = {};
1818
+ Object.keys(node).forEach(key => {
1819
+ if (key === '_attributes') return;
1820
+ if (key === 'PropertyValue') {
1821
+ _toArray(node[key]).forEach(propertyValue => {
1822
+ Object.assign(result, _parseV2PropertyValue(propertyValue));
1823
+ });
1824
+ } else if (key === 'Collection') {
1825
+ return _parseV2Collection(node[key]);
1826
+ } else if (key === 'Record') {
1827
+ Object.assign(result, _parseV2Record(node[key]));
1828
+ } else {
1829
+ const parsedValue = _parseV2AnnotationNode(node[key]);
1830
+ if (parsedValue !== null && parsedValue !== undefined) {
1831
+ result[key] = parsedValue;
1832
+ }
1833
+ }
1834
+ });
1835
+
1836
+ return Object.keys(result).length ? result : _readAnnotationPrimitive(node._attributes);
1837
+ }
1838
+
1839
+ function _parseV2PropertyValue(propertyValueNode) {
1840
+ const attributes = propertyValueNode._attributes || {};
1841
+ const propertyName = attributes.Property;
1842
+ const childKeys = Object.keys(propertyValueNode).filter(key => key !== '_attributes');
1843
+ let propertyValue = null;
1844
+
1845
+ if (childKeys.length === 0) {
1846
+ propertyValue = _readAnnotationPrimitive(attributes);
1847
+ } else if (childKeys.length === 1) {
1848
+ const childKey = childKeys[0];
1849
+ if (childKey === 'Collection') {
1850
+ propertyValue = _parseV2Collection(propertyValueNode[childKey]);
1851
+ } else if (childKey === 'Record') {
1852
+ propertyValue = _parseV2Record(propertyValueNode[childKey]);
1853
+ } else {
1854
+ propertyValue = _parseV2AnnotationNode(propertyValueNode[childKey]);
1855
+ }
1856
+ } else {
1857
+ propertyValue = {};
1858
+ childKeys.forEach(childKey => {
1859
+ if (childKey === 'PropertyValue') {
1860
+ _toArray(propertyValueNode[childKey]).forEach(item => {
1861
+ Object.assign(propertyValue, _parseV2PropertyValue(item));
1862
+ });
1863
+ } else {
1864
+ propertyValue[childKey] = _parseV2AnnotationNode(propertyValueNode[childKey]);
1865
+ }
1866
+ });
1867
+ }
1868
+
1869
+ return { [propertyName]: propertyValue };
1870
+ }
1871
+
1872
+ function _parseV2Record(recordNode) {
1873
+ if (recordNode === null || recordNode === undefined) return null;
1874
+ if (Array.isArray(recordNode)) return recordNode.map(item => _parseV2Record(item));
1875
+
1876
+ const attributes = recordNode._attributes || {};
1877
+ const result = {};
1878
+
1879
+ if (attributes.Type) {
1880
+ result.$Type = attributes.Type;
1881
+ }
1882
+
1883
+ Object.keys(recordNode).forEach(key => {
1884
+ if (key === '_attributes') return;
1885
+ if (key === 'PropertyValue') {
1886
+ _toArray(recordNode[key]).forEach(propertyValueNode => {
1887
+ Object.assign(result, _parseV2PropertyValue(propertyValueNode));
1888
+ });
1889
+ } else {
1890
+ result[key] = _parseV2AnnotationNode(recordNode[key]);
1891
+ }
1892
+ });
1893
+
1894
+ return result;
1895
+ }
1896
+
1897
+ function _parseV2Collection(collectionNode) {
1898
+ if (collectionNode === null || collectionNode === undefined) return null;
1899
+ if (Array.isArray(collectionNode)) return collectionNode.map(item => _parseV2AnnotationNode(item));
1900
+
1901
+ const values = [];
1902
+ Object.keys(collectionNode).forEach(key => {
1903
+ if (key === '_attributes') return;
1904
+ _toArray(collectionNode[key]).forEach(item => {
1905
+ values.push(_parseV2AnnotationNode(item));
1906
+ });
1907
+ });
1908
+
1909
+ return values;
1910
+ }
1911
+
1912
+ function _parseV2AnnotationTerm(annotationNode) {
1913
+ const attributes = annotationNode._attributes || {};
1914
+ const annotationTerm = '@' + attributes.Term;
1915
+ let annotationTermObject = null;
1916
+
1917
+ Object.keys(annotationNode).forEach(key => {
1918
+ if (key === '_attributes') return;
1919
+ annotationTermObject = _parseV2AnnotationNode(annotationNode[key]);
1920
+ });
1921
+
1922
+ return [annotationTerm, annotationTermObject];
1923
+ }
1924
+
1925
+ function _resolveV2AnnotationTarget(targetPath, csnDefs, parserContext) {
1926
+ const normalizedTargetPath = targetPath.replace(/\.$/, '');
1927
+ const targetSegments = normalizedTargetPath.split('/');
1928
+ const entityContainerPrefix = parserContext.primarySchema + '.EntityContainer';
1929
+
1930
+ if (targetSegments.length > 1 && targetSegments[0] === entityContainerPrefix) {
1931
+ return csnDefs[parserContext.primarySchema + '.' + targetSegments[1]];
1932
+ }
1933
+
1934
+ if (csnDefs[normalizedTargetPath]) {
1935
+ return csnDefs[normalizedTargetPath];
1936
+ }
1937
+
1938
+ const directTarget = parserContext.primarySchema + '.' + targetSegments[targetSegments.length - 1];
1939
+ if (csnDefs[directTarget]) {
1940
+ return csnDefs[directTarget];
1941
+ }
1942
+ }
1943
+
1944
+ function _parseV2SchemaAnnotations(schemaAnnotations, csnDefs, parserContext) {
1945
+ const parsedAnnotations = {};
1946
+
1947
+ _toArray(schemaAnnotations).forEach(annotationsNode => {
1948
+ const targetPath = annotationsNode._attributes && annotationsNode._attributes.Target;
1949
+ if (!targetPath) return;
1950
+
1951
+ if (!parsedAnnotations[targetPath]) {
1952
+ parsedAnnotations[targetPath] = {};
1953
+ }
1954
+
1955
+ _toArray(annotationsNode.Annotation).forEach(annotationNode => {
1956
+ const [annotationTerm, annotationTermObject] = _parseV2AnnotationTerm(annotationNode);
1957
+ parsedAnnotations[targetPath][annotationTerm] = annotationTermObject;
1958
+ });
1959
+ });
1960
+
1961
+ annotationParser.parseAnnotations(parsedAnnotations, csnDefs, parserContext, _resolveV2AnnotationTarget);
1962
+ }
1963
+
1751
1964
  function getEdmxv2CSN(edmx2jsonModel, ignorePersistenceSkip, mockServerUc, context) {
1752
1965
  let csnDataModel;
1753
1966
  let isValid;
@@ -1762,11 +1975,15 @@ function getEdmxv2CSN(edmx2jsonModel, ignorePersistenceSkip, mockServerUc, conte
1762
1975
  else if (context.include_all_namespaces) {
1763
1976
  parserContext.allowAllNamespaces = true;
1764
1977
  }
1978
+ // V2 annotations are opt-in (disabled by default for backward compatibility)
1979
+ // Controlled via --odata-v2-annotations CLI flag
1980
+ parserContext.annotations = !!context.odataV2Annotations;
1765
1981
 
1766
1982
  isValid = _isValidEDMXProvided(edmx2jsonModel);
1767
1983
  _settingMockServerUc(edmx2jsonModel, parserContext)
1768
1984
  if (isValid) {
1769
1985
  parserContext.serviceNamespace = _getServiceNameSpace(edmx2jsonModel);
1986
+ parserContext.primarySchema = parserContext.serviceNamespace;
1770
1987
  context.schemaNamespace = parserContext.serviceNamespace; // required for `--keep-namespace` scenario
1771
1988
  let namespaceAlias = edmx2jsonModel["edmx:Edmx"]["edmx:DataServices"].Schema._attributes.Alias;
1772
1989
  // replace the namespace alias value with original namespace value
@@ -11,6 +11,7 @@ const ParserContext = require("./ParserContext");
11
11
  const ignoreObjects = ["$Reference"]; // add all the objects that needs to be ignored for parsing.
12
12
  const { warn, info } = require("../../util/term");
13
13
  const MetadataConverterFactory = require("./metadataconverter/MetadataConverterFactory");
14
+ const annotationParser = require("./annotationParser");
14
15
 
15
16
  const edmPrimitiveTypes = [
16
17
  'Edm.Binary', 'Edm.Boolean', 'Edm.Byte', 'Edm.Date', 'Edm.DateTimeOffset',
@@ -56,33 +57,6 @@ const edmToCdsTypeSpecialHandling = {
56
57
  'Edm.Stream': 'cds.LargeBinary'
57
58
  };
58
59
 
59
- const known_vocabularies = {
60
- 'Org.OData.Authorization.V1': 'Authorization',
61
- 'Org.OData.Aggregation.V1': 'Aggregation',
62
- 'Org.OData.Core.V1': 'Core',
63
- 'Org.OData.Capabilities.V1': 'Capabilities',
64
- 'Org.OData.Validation.V1': 'Validation',
65
- 'Org.OData.Measures.V1': 'Measures',
66
- 'Org.OData.JSON.V1': 'JSON',
67
- 'Org.OData.Repeatability.V1': 'Repeatability',
68
-
69
- 'com.sap.vocabularies.Analytics.v1': 'Analytics',
70
- 'com.sap.vocabularies.CodeList.v1': 'CodeList',
71
- 'com.sap.vocabularies.Common.v1': 'Common',
72
- 'com.sap.vocabularies.Communication.v1': 'Communication',
73
- 'com.sap.vocabularies.DataIntegration.v1': 'DataIntegration',
74
- 'com.sap.vocabularies.Graph.v1': 'Graph',
75
- 'com.sap.vocabularies.Hierarchy.v1': 'Hierarchy',
76
- 'com.sap.vocabularies.HTML5.v1': 'HTML5',
77
- 'com.sap.vocabularies.ODM.v1': 'ODM',
78
- 'com.sap.vocabularies.Offline.v1': 'Offline',
79
- 'com.sap.vocabularies.PDF.v1': 'PDF',
80
- 'com.sap.vocabularies.PersonalData.v1': 'PersonalData',
81
- 'com.sap.vocabularies.Session.v1': 'Session',
82
- 'com.sap.vocabularies.UI.v1': 'UI',
83
- };
84
-
85
-
86
60
  function _isJson(edmx) {
87
61
  let isJson = false;
88
62
  try {
@@ -235,52 +209,7 @@ function _updateDataTypeObjectForSpecialHandling(dataTypeObj, propertyDataType,
235
209
  }
236
210
 
237
211
  function _captureAnnotationInCSN(targetString, annotationObject, csnDefsTargetObject) {
238
- /**
239
- * For scenarios like:
240
- * <Annotation Term="Core.Computed" />
241
- * Here, 'annotationObject' would be null but in CSN we need to treat it as:
242
- * @Core.Computed : true
243
- */
244
- if (annotationObject === null) {
245
- annotationObject = true;
246
- }
247
- /**
248
- * if anntationObject is empty list or empty object, we won't capture it.
249
- * eg., <Annotation Term="Core.OptimisticConcurrency"> <Collection /> </Annotation>
250
- * TODO: we might have to revisit it later for nested scenario.
251
- */
252
- if ((annotationObject instanceof Array && annotationObject.length === 0) ||
253
- (annotationObject instanceof Object && Object.keys(annotationObject).length === 0)
254
- ) {
255
- return;
256
- }
257
- /**
258
- * <Annotation Term="Common.Text" Path="name"/> --> annotationTermObject = { "=": annotationTermObject.$Path };
259
- */
260
- if (annotationObject.$Path) {
261
- annotationObject = { "=": annotationObject.$Path };
262
- }
263
-
264
- /**
265
- * Annotations need to be flattened in CSN. E.g.
266
- *
267
- * @Core.ContentDisposition : {
268
- * Type : 'inline',
269
- * Filename: typedImageFileName
270
- * }
271
- *
272
- * is represented as:
273
- *
274
- * @Core.ContentDisposition.Type : 'inline',
275
- * @Core.ContentDisposition.Filename : {'=': 'typedImageFileName'},
276
- */
277
- if (annotationObject instanceof Object && !(annotationObject instanceof Array) && !(Object.keys(annotationObject).length === 1 && (Object.keys(annotationObject)[0] === '=' || Object.keys(annotationObject)[0] === 'xpr' || Object.keys(annotationObject)[0] === '#'))) {
278
- Object.keys(annotationObject).forEach(innerTargetString => {
279
- _captureAnnotationInCSN(targetString + '.' + innerTargetString, annotationObject[innerTargetString], csnDefsTargetObject);
280
- })
281
- } else {
282
- csnDefsTargetObject[targetString] = annotationObject;
283
- }
212
+ annotationParser.captureAnnotationInCSN(targetString, annotationObject, csnDefsTargetObject);
284
213
  }
285
214
 
286
215
  function _replaceSpecialCharacters(text) {
@@ -316,170 +245,17 @@ function _resolveCoreTerm(annotationTerm, annotationTermObject, csnDefsTargetObj
316
245
  }
317
246
  }
318
247
 
319
- function _flattenAnnotationObject(annotationPrefix, annotationObject) {
320
- let flattenedResult = [];
321
-
322
- Object.entries(annotationObject).forEach(annotation => {
323
- let currentResult = [];
324
- /**
325
- * if annotation looks like:
326
- * { "#" : <value> } or { "=" : <value> }
327
- * don't flatten because it is for the `EnumMember` & `Path` use cases respectively
328
- */
329
- if (annotation[0] === '#' || annotation[0] === '=') {
330
- currentResult[0] = annotationPrefix;
331
- currentResult[1] = annotationObject;
332
- } else {
333
- currentResult[0] = annotationPrefix + '.' + annotation[0];
334
- currentResult[1] = annotation[1];
335
- }
336
- flattenedResult.push(currentResult);
337
- });
338
-
339
- return flattenedResult;
340
- }
341
-
342
- function _removeTypeInAnnotation(annotationObject) {
343
- Object.keys(annotationObject).forEach(key => {
344
- if (key === "$Type") delete annotationObject[key];
345
- else if (annotationObject[key] instanceof Object) {
346
- _removeTypeInAnnotation(annotationObject[key]);
347
- }
348
- });
349
- }
350
-
351
- function _parseCapabilitiesTerm(annotationTerm, annotationTermObject, csnDefsTargetObject) {
352
- let targetString;
353
- /**
354
- * <Annotation Term="Capabilities.KeyAsSegmentSupported" />
355
- * For such scenario, mark the `anntationTermObject` as `true`
356
- */
357
- if (annotationTermObject === null) annotationTermObject = true;
358
-
359
- _removeTypeInAnnotation(annotationTermObject);
360
- /**
361
- * For entries like:
362
- * <Annotation Term="Capabilities.FilterFunctions"> <Collection> <String>eq</String> <String>ne</String> </Collection> </Annotation>
363
- * <Annotation Term="Capabilities.DeleteRestrictions.Deletable" Bool="true"/>
364
- *
365
- * simply preserve the value as it is
366
- */
367
- if (annotationTermObject instanceof Array || !(annotationTermObject instanceof Object)) {
368
- _captureAnnotationInCSN(annotationTerm, annotationTermObject, csnDefsTargetObject);
369
- }
370
- else {
371
- Object.keys(annotationTermObject).forEach(annoKey => {
372
- let currentAnnoValue = annotationTermObject[annoKey];
373
- // if the currentAnnoValue is an Object, we need to flatten it
374
- if (currentAnnoValue instanceof Object && !(currentAnnoValue instanceof Array)) {
375
- targetString = annotationTerm + '.' + annoKey;
376
- let flattenedResultList = _flattenAnnotationObject(targetString, currentAnnoValue);
377
-
378
- for (let idx = 0; idx < flattenedResultList.length; idx++) {
379
- _captureAnnotationInCSN(flattenedResultList[idx][0], flattenedResultList[idx][1], csnDefsTargetObject);
380
- }
381
- }
382
- // if the currentAnnoValue is a list or direct value, we preserve as it is
383
- else {
384
- targetString = annotationTerm + '.' + annoKey;
385
- _captureAnnotationInCSN(targetString, currentAnnoValue, csnDefsTargetObject);
386
- }
387
- });
388
- }
389
- }
390
-
391
- function _parseValidationTerm(annotationTerm, annotationTermObject, csnDefsTargetObject) {
392
- const resolveCastFor = ["Minimum", "Maximum", "MaxItems"];
393
- const term = annotationTerm.split('.')[1];
394
-
395
- if (resolveCastFor.includes(term) && annotationTermObject.$Cast) {
396
- annotationTermObject = Number(annotationTermObject.$Cast);
397
- }
398
-
399
- _captureAnnotationInCSN(annotationTerm, annotationTermObject, csnDefsTargetObject);
400
- }
401
-
402
248
  function _parseAnnotationTerms(annotationTerm, annotationTermObject, csnDefsTargetObject) {
403
- const term = annotationTerm.split('.');
404
- const vocabAlias = term[0];
405
-
406
- switch (vocabAlias) {
407
- case "@Core":
408
- _resolveCoreTerm(annotationTerm, annotationTermObject, csnDefsTargetObject);
409
- break;
410
- case "@Capabilities":
411
- _parseCapabilitiesTerm(annotationTerm, annotationTermObject, csnDefsTargetObject);
412
- break;
413
- case "@Validation":
414
- _parseValidationTerm(annotationTerm, annotationTermObject, csnDefsTargetObject);
415
- break;
416
- default:
417
- _captureAnnotationInCSN(annotationTerm, annotationTermObject, csnDefsTargetObject);
418
- break;
419
- }
420
- }
421
-
422
- function _validateAnnotationNamespace(annotationTerm, vocabNamespaceToAlias) {
423
- let aliasOrNamespace = annotationTerm.substring(1, annotationTerm.lastIndexOf('.'));
424
-
425
- // This check is for ignoring the @cds annotations given for special cases, e.g. @cds.validate = false for collided action/function
426
- if (aliasOrNamespace === "cds") {
427
- return
428
- }
429
-
430
- for (let alias of Object.values(known_vocabularies)) {
431
- if (alias === aliasOrNamespace) {
432
- return;
433
- }
434
- }
435
-
436
- for (let [namespace, alias] of Object.entries(vocabNamespaceToAlias)) {
437
- if (alias === aliasOrNamespace || namespace === aliasOrNamespace) {
438
- console.log(warn(annotationTerm + " AnnotationTerm belongs to an unknown namespace: " + namespace));
439
- return;
440
- }
249
+ // @Core needs special v4 handling for @Core.OptionalParameter (uses _updateElementObjectForDefaultValue)
250
+ if (annotationTerm.startsWith('@Core.')) {
251
+ _resolveCoreTerm(annotationTerm, annotationTermObject, csnDefsTargetObject);
252
+ } else {
253
+ annotationParser.parseAnnotationTerms(annotationTerm, annotationTermObject, csnDefsTargetObject);
441
254
  }
442
-
443
- console.log(warn(annotationTerm + ": Reference of this AnnotationTerm is not present in the edmx file."));
444
255
  }
445
256
 
446
257
  function _resolveAnnotationTerm(schemaDataAnnotation, parserContext) {
447
-
448
- for (const [namespace, alias] of Object.entries(parserContext.vocabNamespaceToAlias)) {
449
- if (known_vocabularies[namespace]) {
450
- // Case 1: Namespace belongs to known vocabularies. Replacing the namespace/alias of the doc with standard alias
451
- if (schemaDataAnnotation.includes('@' + alias + '.')) {
452
- schemaDataAnnotation = schemaDataAnnotation.split('@' + alias + '.').join('@' + known_vocabularies[namespace] + '.');
453
- }
454
- if (schemaDataAnnotation.includes('@' + namespace + '.')) {
455
- schemaDataAnnotation = schemaDataAnnotation.split('@' + namespace + '.').join('@' + known_vocabularies[namespace] + '.');
456
- }
457
- } else {
458
- // Case 2: Namespace doesn't belong to known vocabularies. Replacing the namespace with alias(if provided) in the doc.
459
- if (schemaDataAnnotation.includes('@' + namespace + '.') && alias) {
460
- schemaDataAnnotation = schemaDataAnnotation.split('@' + namespace + '.').join('@' + alias + '.');
461
- }
462
- }
463
- }
464
- /**
465
- * Case 3: Namespace doesn't have edmx:Reference. But it is present in known_vocabularies.
466
- * Replacing it with standard alias.
467
- */
468
- for (const [namespace, alias] of Object.entries(known_vocabularies)) {
469
- if (schemaDataAnnotation.includes(namespace)) {
470
- schemaDataAnnotation = schemaDataAnnotation.split(namespace).join(alias);
471
- }
472
- }
473
-
474
- let index = schemaDataAnnotation.indexOf('"@');
475
- while (index != -1) {
476
- let i = schemaDataAnnotation.indexOf('"', index + 1);
477
- let annotationTerm = schemaDataAnnotation.substring(index + 1, i);
478
- _validateAnnotationNamespace(annotationTerm, parserContext.vocabNamespaceToAlias);
479
- index = schemaDataAnnotation.indexOf('"@', i + 1);
480
- }
481
-
482
- return JSON.parse(schemaDataAnnotation);
258
+ return annotationParser.resolveAnnotationTerm(schemaDataAnnotation, parserContext);
483
259
  }
484
260
 
485
261
  /**
@@ -46,6 +46,32 @@ module.exports = function watch(paths, { filter, ignore, hotReload } = {}) {
46
46
 
47
47
  const useNativeRecursive = process.platform === 'darwin' || process.platform === 'win32'
48
48
  if (useNativeRecursive) {
49
+ // On Windows, ReadDirectoryChangesW fires FILE_NOTIFY_CHANGE events for mere
50
+ // file opens (antivirus, search indexer, TypeScript loaders) without a write
51
+ // having occurred, causing spurious server restarts. Guard by confirming
52
+ // mtime actually changed before emitting 'change'. The cache is seeded on
53
+ // the first event for each path so buffered pre-startup events are suppressed.
54
+ // Mac uses kqueue/FSEvents which only fires on real writes — no guard needed.
55
+ const _mtimes = process.platform === 'win32' ? new Map() : null
56
+ const _watcherStart = _mtimes && Date.now()
57
+ const _changedOnDisk = _mtimes
58
+ ? async fullPath => {
59
+ try {
60
+ const { mtimeMs } = await fs.promises.stat(fullPath)
61
+ const prev = _mtimes.get(fullPath)
62
+ _mtimes.set(fullPath, mtimeMs)
63
+ if (prev === undefined) return false // first event: seed cache, suppress
64
+ if (prev === mtimeMs) return false // mtime unchanged: read-only open
65
+ if (mtimeMs < _watcherStart) return false // older than watcher: buffered OS event
66
+ return true
67
+ } catch (err) {
68
+ _mtimes.delete(fullPath)
69
+ if (err.code !== 'ENOENT' && err.code !== 'ENOTDIR') throw err
70
+ return true // stat failed (deleted file): let it through
71
+ }
72
+ }
73
+ : () => true // non-Windows: always emit
74
+
49
75
  const _watch = async root => {
50
76
  try {
51
77
  if (!fs.existsSync(root)) return
@@ -56,7 +82,7 @@ module.exports = function watch(paths, { filter, ignore, hotReload } = {}) {
56
82
  const fullPath = resolve(root, name)
57
83
  if (_hotReload(fullPath)) {
58
84
  ee.emit('refreshModel', fs.existsSync(fullPath) ? 'update' : 'remove', fullPath)
59
- } else if (_allow(fullPath) && _narrowJSONreloads(fullPath)) {
85
+ } else if (_allow(fullPath) && _narrowJSONreloads(fullPath) && await _changedOnDisk(fullPath)) {
60
86
  ee.emit('change', fs.existsSync(fullPath) ? 'update' : 'remove', fullPath)
61
87
  }
62
88
  }
@@ -1,18 +1,18 @@
1
1
  {
2
2
  "name": "@sap/cds-dk",
3
- "version": "9.9.2",
3
+ "version": "9.9.3",
4
4
  "lockfileVersion": 3,
5
5
  "requires": true,
6
6
  "packages": {
7
7
  "": {
8
8
  "name": "@sap/cds-dk",
9
- "version": "9.9.2",
9
+ "version": "9.9.3",
10
10
  "license": "SEE LICENSE IN LICENSE",
11
11
  "dependencies": {
12
12
  "@cap-js/asyncapi": "^1.0.0",
13
13
  "@cap-js/openapi": "^1.0.0",
14
- "@sap/cds": "^8.3 || ^9",
15
- "@sap/cds-mtxs": "^2 || ^3",
14
+ "@sap/cds": "^9",
15
+ "@sap/cds-mtxs": "^3",
16
16
  "@sap/hdi-deploy": "^5",
17
17
  "express": "^4.22.1 || ^5",
18
18
  "hdb": "^2.0.0",
@@ -28,13 +28,13 @@
28
28
  "cds-tsx": "bin/cds-tsx.js"
29
29
  },
30
30
  "optionalDependencies": {
31
- "@cap-js/sqlite": ">=1"
31
+ "@cap-js/sqlite": "^1 || ^2"
32
32
  }
33
33
  },
34
34
  "node_modules/@cap-js/asyncapi": {
35
- "version": "1.0.3",
36
- "integrity": "sha512-vZSWKAe+3qfvZDXV5SSFiObGWmqyS9MDyEADb5PLVT8kzO39qGaSDPv/GzI/gwvRfCayGAjU4ThiBKrFA7Gclg==",
37
- "license": "SEE LICENSE IN LICENSE",
35
+ "version": "1.1.0",
36
+ "integrity": "sha512-ywO5NfGwoeEx6/jneOMpNMs4PFmuueet8HOEg13VAiXpu4W5juAZQ/Jv2xE0QcW9m1qiCmI1ymypmz7UI+YGbw==",
37
+ "license": "Apache-2.0",
38
38
  "peerDependencies": {
39
39
  "@sap/cds": ">=7.6"
40
40
  }
@@ -52,8 +52,8 @@
52
52
  }
53
53
  },
54
54
  "node_modules/@cap-js/openapi": {
55
- "version": "1.4.2",
56
- "integrity": "sha512-Mi02XsSlLy+h9tBLPX1/re2Obsmb1rnIj18AUb1jD5dLvbfedfDojtsuZIQHQImdYNeD6nMgaBQw0g1JRHTdow==",
55
+ "version": "1.5.0",
56
+ "integrity": "sha512-JDzENNC80vnP9W31Skz6PfgPnpaxNTgIP8CtDK3bJ0FCbr5sXshRqes1pc17kNMXDbJAtEshS0dN7GYBW2THWA==",
57
57
  "license": "Apache-2.0",
58
58
  "dependencies": {
59
59
  "pluralize": "^8.0.0"
@@ -129,8 +129,8 @@
129
129
  }
130
130
  },
131
131
  "node_modules/@sap/cds-compiler": {
132
- "version": "6.9.2",
133
- "integrity": "sha512-Qv7Zb3RhG92WVm1AjHEJaYbOi3tNT051/EWPYTsYdUe5epYXbR4dJfGpD1eEgo82ThrKCFx0BZfT0b28t0/vqg==",
132
+ "version": "6.9.3",
133
+ "integrity": "sha512-8fEeM6D059ruqLWoJqzFSNLkGoUCMnycx7Zv4OP4nA9zZeHzFC3nAe6s3ak2kASSiub1xAMMPzDE/9EnSXQKLw==",
134
134
  "license": "SEE LICENSE IN LICENSE",
135
135
  "bin": {
136
136
  "cdsc": "bin/cdsc.js",
@@ -150,8 +150,8 @@
150
150
  }
151
151
  },
152
152
  "node_modules/@sap/cds-mtxs": {
153
- "version": "3.9.3",
154
- "integrity": "sha512-NdL+ctu1YmxRMFFk8ip3PZHXxIrDv93gK+ZUE+e5B+9MNDvalQcav4QllqwiGJ/CJcfJROWGTt+a554A81dxow==",
153
+ "version": "3.9.4",
154
+ "integrity": "sha512-A3FRCGCMNhml8MLpGv+OUHwDhAUgvAiE38qdTL87/KQ932CTNZ1frohQtT24iszoamB7NI8lp9RR3BFD6HR/FQ==",
155
155
  "license": "SEE LICENSE IN LICENSE",
156
156
  "dependencies": {
157
157
  "@sap/hdi-deploy": "^5"
@@ -188,15 +188,15 @@
188
188
  }
189
189
  },
190
190
  "node_modules/@sap/hdi-deploy": {
191
- "version": "5.6.1",
192
- "integrity": "sha512-+qQ7qwG8lko303L5yRj2dud/nDAVuVblV/mmzJT44oPbF0Nry18eD2LUS23hFeuxjRa7rYK5YKQ8ffGgWxVrYQ==",
193
- "license": "See LICENSE file",
191
+ "version": "5.7.0",
192
+ "integrity": "sha512-ENBCtCBnJEKBlvC5mQT/G7OAteOENo7ygSeRJgVdz7AHikcHUxegtavXkJtFCJ/PSjpjytFKfGRmG8peUVAmNQ==",
193
+ "license": "SEE LICENSE IN LICENSE",
194
194
  "dependencies": {
195
- "@sap/hdi": "^4.8.0",
195
+ "@sap/hdi": "^4.8.1",
196
196
  "@sap/xsenv": "^6.0.0",
197
197
  "async": "^3.2.6",
198
198
  "dotenv": "^16.4.5",
199
- "handlebars": "^4.7.8",
199
+ "handlebars": "^4.7.9",
200
200
  "micromatch": "^4.0.8"
201
201
  },
202
202
  "engines": {
@@ -274,8 +274,8 @@
274
274
  "optional": true
275
275
  },
276
276
  "node_modules/better-sqlite3": {
277
- "version": "12.10.0",
278
- "integrity": "sha512-CyzaZRQKyHkB2ZInfTTl2nvT33EbDpjkLEbE8/Zck3Ll6O0qqvuGdrJ45HgtH+HykRg88ITY3AdreBGN70aBSQ==",
277
+ "version": "12.11.1",
278
+ "integrity": "sha512-dq9AtApgg5PGFtBzPFSBl3HZQjHok5gaQCM6zh2Yk0aSmDCs1CbnVI8/HgASQkNKsWFpseIO9beg5xxpYhbIfA==",
279
279
  "hasInstallScript": true,
280
280
  "license": "MIT",
281
281
  "optional": true,
@@ -308,20 +308,32 @@
308
308
  }
309
309
  },
310
310
  "node_modules/body-parser": {
311
- "version": "2.2.2",
312
- "integrity": "sha512-oP5VkATKlNwcgvxi0vM0p/D3n2C3EReYVX+DNYs5TjZFn/oQt2j+4sVJtSMr18pdRr8wjTcBl6LoV+FUwzPmNA==",
311
+ "version": "2.3.0",
312
+ "integrity": "sha512-2cGmJupaNgg+QUwVLAucDuWuoMZ6EX9iHDRswZ5lsNYEmwPaRknMPCLZz07yTzVq/83p4o/wzbDZbBrTvGGTIw==",
313
313
  "license": "MIT",
314
314
  "dependencies": {
315
315
  "bytes": "^3.1.2",
316
- "content-type": "^1.0.5",
316
+ "content-type": "^2.0.0",
317
317
  "debug": "^4.4.3",
318
- "http-errors": "^2.0.0",
319
- "iconv-lite": "^0.7.0",
318
+ "http-errors": "^2.0.1",
319
+ "iconv-lite": "^0.7.2",
320
320
  "on-finished": "^2.4.1",
321
- "qs": "^6.14.1",
322
- "raw-body": "^3.0.1",
323
- "type-is": "^2.0.1"
321
+ "qs": "^6.15.2",
322
+ "raw-body": "^3.0.2",
323
+ "type-is": "^2.1.0"
324
+ },
325
+ "engines": {
326
+ "node": ">=18"
324
327
  },
328
+ "funding": {
329
+ "type": "opencollective",
330
+ "url": "https://opencollective.com/express"
331
+ }
332
+ },
333
+ "node_modules/body-parser/node_modules/content-type": {
334
+ "version": "2.0.0",
335
+ "integrity": "sha512-j/O/d7GcZCyNl7/hwZAb606rzqkyvaDctLmckbxLzHvFBzTJHuGEdodATcP3yIRoDrLHkIATJuvzbFlp/ki2cQ==",
336
+ "license": "MIT",
325
337
  "engines": {
326
338
  "node": ">=18"
327
339
  },
@@ -817,8 +829,8 @@
817
829
  }
818
830
  },
819
831
  "node_modules/hasown": {
820
- "version": "2.0.3",
821
- "integrity": "sha512-ej4AhfhfL2Q2zpMmLo7U1Uv9+PyhIZpgQLGT1F9miIGmiCJIoCgSmczFdrc97mWT4kVY72KA+WnnhJ5pghSvSg==",
832
+ "version": "2.0.4",
833
+ "integrity": "sha512-T2UbfbBEF32wiepXIsMlTW9+dDYC6wMh/t/vYA4tuOMKqWz/n3vr1NFSxQiyP+zk2mXsoMA/i/7qV6LKut1t1A==",
822
834
  "license": "MIT",
823
835
  "dependencies": {
824
836
  "function-bind": "^1.1.2"
@@ -828,8 +840,8 @@
828
840
  }
829
841
  },
830
842
  "node_modules/hdb": {
831
- "version": "2.27.1",
832
- "integrity": "sha512-xYL/W+fq2TyGHyzm8muolQnw8tdh4+2NQ8mQP2FpLSuhfJ8l0jQNSUZoAXic7NfMEan1Jvf8V1L4blwkgTc6+A==",
843
+ "version": "2.29.4",
844
+ "integrity": "sha512-9L7/xjx4NU2YWatqLSHXhuEIuvNlV/4eRFg7u/L63U1Dc5LocP4yWeH8UGKTLBCJkt548OQ+A7PqqimZwEuqdw==",
833
845
  "license": "Apache-2.0",
834
846
  "dependencies": {
835
847
  "iconv-lite": "0.7.0"
@@ -1212,11 +1224,12 @@
1212
1224
  }
1213
1225
  },
1214
1226
  "node_modules/qs": {
1215
- "version": "6.15.2",
1216
- "integrity": "sha512-Rzq0KEyX/w/tEybncDgdkZrJgVUsUMk3xjh3t5bv3S1HTAtg+uOYt72+ZfwiQwKdysThkTBdL/rTi6HDmX9Ddw==",
1227
+ "version": "6.15.3",
1228
+ "integrity": "sha512-O9gl3zCl5h5blw1KGUzQKhA5oUXSl8rwUIM5o0S3nCXMliSvy5Dzx7/DJcI+SwgICv+IneSZwhBh1oSyEHA71A==",
1217
1229
  "license": "BSD-3-Clause",
1218
1230
  "dependencies": {
1219
- "side-channel": "^1.1.0"
1231
+ "es-define-property": "^1.0.1",
1232
+ "side-channel": "^1.1.1"
1220
1233
  },
1221
1234
  "engines": {
1222
1235
  "node": ">=0.6"
@@ -1325,8 +1338,8 @@
1325
1338
  }
1326
1339
  },
1327
1340
  "node_modules/semver": {
1328
- "version": "7.8.1",
1329
- "integrity": "sha512-rkVq3IXh+4FDGch+KwzX3aV9W3kO54GyEgpvBzSyctDA6Xtd7RJQV1xmXbeQp5v7+VzLOfVqiutSE6GICgPFvg==",
1341
+ "version": "7.8.5",
1342
+ "integrity": "sha512-Y7/KDsb8LjooZpwaqGyulO6DQlksgCncchHGk+sZIY4SBvUocMBEFH5Ur1fI4dV+Jvl0w6cjvucaIi40puRioA==",
1330
1343
  "devOptional": true,
1331
1344
  "license": "ISC",
1332
1345
  "bin": {
@@ -1385,13 +1398,13 @@
1385
1398
  "license": "ISC"
1386
1399
  },
1387
1400
  "node_modules/side-channel": {
1388
- "version": "1.1.0",
1389
- "integrity": "sha512-ZX99e6tRweoUXqR+VBrslhda51Nh5MTQwou5tnUDgbtyM0dBgmhEDtWGP/xbKn6hqfPRHujUNwz5fy/wbbhnpw==",
1401
+ "version": "1.1.1",
1402
+ "integrity": "sha512-6x6dK6zJdpTzF4sQeNYxwtvBzf6Eg4GtlesS94HOvTudUeyK2WXAaIfmDgsyslYrRBeFIlsi54AYsFGUuhmvrQ==",
1390
1403
  "license": "MIT",
1391
1404
  "dependencies": {
1392
1405
  "es-errors": "^1.3.0",
1393
- "object-inspect": "^1.13.3",
1394
- "side-channel-list": "^1.0.0",
1406
+ "object-inspect": "^1.13.4",
1407
+ "side-channel-list": "^1.0.1",
1395
1408
  "side-channel-map": "^1.0.1",
1396
1409
  "side-channel-weakmap": "^1.0.2"
1397
1410
  },
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sap/cds-dk",
3
- "version": "9.9.2",
3
+ "version": "9.9.3",
4
4
  "description": "Command line client and development toolkit for the SAP Cloud Application Programming Model",
5
5
  "homepage": "https://cap.cloud.sap/",
6
6
  "keywords": [
@@ -19,8 +19,8 @@
19
19
  "dependencies": {
20
20
  "@cap-js/asyncapi": "^1.0.0",
21
21
  "@cap-js/openapi": "^1.0.0",
22
- "@sap/cds": "^8.3 || ^9",
23
- "@sap/cds-mtxs": "^2 || ^3",
22
+ "@sap/cds": "^9",
23
+ "@sap/cds-mtxs": "^3",
24
24
  "@sap/hdi-deploy": "^5",
25
25
  "express": "^4.22.1 || ^5",
26
26
  "hdb": "^2.0.0",
@@ -31,7 +31,7 @@
31
31
  "yaml": "^2"
32
32
  },
33
33
  "optionalDependencies": {
34
- "@cap-js/sqlite": ">=1"
34
+ "@cap-js/sqlite": "^1 || ^2"
35
35
  },
36
36
  "files": [
37
37
  "bin/",