@sap/cds-dk 10.2.0 → 10.2.1

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.
Files changed (90) hide show
  1. package/CHANGELOG.md +13 -0
  2. package/bin/bind.js +5 -4
  3. package/bin/completion/scripts/cds.fish +4 -3
  4. package/bin/completion/scripts/cds.ps1 +4 -4
  5. package/bin/completion/scripts/cds.sh +4 -4
  6. package/bin/completion/scripts/cds.zsh +4 -4
  7. package/bin/debug.js +3 -3
  8. package/bin/deploy/index.js +2 -2
  9. package/bin/deploy/to-hana/hana.js +1 -1
  10. package/bin/deploy/to-hana/hdiDeployUtil.js +25 -14
  11. package/bin/up.js +3 -3
  12. package/bin/version.js +1 -1
  13. package/bin/watch/vite-watch.js +12 -6
  14. package/lib/bind/shared.js +1 -1
  15. package/lib/init/index.js +1 -0
  16. package/lib/init/projectReader.js +3 -2
  17. package/lib/init/template/approuter/files/values.yaml.hbs +0 -9
  18. package/lib/init/template/approuter/files/xs-app.json.hbs +0 -22
  19. package/lib/init/template/approuter/index.js +8 -9
  20. package/lib/init/template/html5-repo/index.js +12 -0
  21. package/lib/init/template/react/files/vite.config.{{extension}}.hbs +2 -3
  22. package/lib/init/template/react/index.js +1 -3
  23. package/lib/init/template/sample/files/app/index.html.hbs +18 -0
  24. package/lib/init/template/sample/index.js +1 -0
  25. package/lib/init/template/vue/files/vite.config.{{extension}}.hbs +2 -3
  26. package/lib/init/template/vue/index.js +1 -3
  27. package/lib/mtx/push.js +7 -1
  28. package/node_modules/@cap-js/asyncapi/README.md +4 -14
  29. package/node_modules/@cap-js/asyncapi/cds-plugin.js +12 -0
  30. package/node_modules/@cap-js/asyncapi/index.js +3 -2
  31. package/node_modules/@cap-js/asyncapi/lib/compile/components/messages/index.js +4 -2
  32. package/node_modules/@cap-js/asyncapi/lib/compile/components/schemas/definition.js +11 -1
  33. package/node_modules/@cap-js/asyncapi/lib/compile/components/schemas/utils.js +16 -0
  34. package/node_modules/@cap-js/asyncapi/lib/compile/utils.js +4 -3
  35. package/node_modules/@cap-js/asyncapi/lib/import/importAsyncAPI.js +34 -0
  36. package/node_modules/@cap-js/asyncapi/lib/import/index.js +18 -0
  37. package/node_modules/@cap-js/asyncapi/lib/import/message.js +9 -0
  38. package/node_modules/@cap-js/asyncapi/lib/import/parserContext.js +368 -0
  39. package/node_modules/@cap-js/asyncapi/lib/import/utils.js +85 -0
  40. package/node_modules/@cap-js/asyncapi/package.json +89 -1
  41. package/node_modules/@sap/cds/CHANGELOG.md +10 -0
  42. package/node_modules/@sap/cds/app/index.js +1 -1
  43. package/node_modules/@sap/cds/lib/env/schemas/cds-package.js +0 -2
  44. package/node_modules/@sap/cds/lib/env/schemas/cds-rc.js +3 -3
  45. package/node_modules/@sap/cds/lib/env/schemas/index.js +4 -4
  46. package/node_modules/@sap/cds/lib/utils/cds-utils.js +21 -8
  47. package/node_modules/@sap/cds/libx/_runtime/messaging/common-utils/connections.js +45 -39
  48. package/node_modules/@sap/cds/libx/odata/ODataAdapter.js +1 -1
  49. package/node_modules/@sap/cds/libx/queue/TaskRunner.js +22 -5
  50. package/node_modules/@sap/cds/package.json +1 -1
  51. package/node_modules/@sap/cds-mtxs/CHANGELOG.md +8 -0
  52. package/node_modules/@sap/cds-mtxs/package.json +1 -1
  53. package/node_modules/@sap/cds-mtxs/srv/extensibility-service.js +57 -41
  54. package/node_modules/@sap/cds-mtxs/srv/jobs-service.js +27 -12
  55. package/node_modules/@sap/cds-mtxs/srv/plugins/hana.js +31 -25
  56. package/node_modules/body-parser/README.md +17 -13
  57. package/node_modules/body-parser/lib/read.js +74 -57
  58. package/node_modules/body-parser/lib/types/json.js +11 -11
  59. package/node_modules/body-parser/lib/types/urlencoded.js +5 -5
  60. package/node_modules/body-parser/lib/utils.js +0 -15
  61. package/node_modules/body-parser/package.json +7 -12
  62. package/node_modules/encodeurl/README.md +14 -0
  63. package/node_modules/encodeurl/index.js +5 -10
  64. package/node_modules/encodeurl/package.json +10 -6
  65. package/node_modules/on-finished/index.js +14 -2
  66. package/node_modules/on-finished/package.json +11 -7
  67. package/node_modules/qs/.editorconfig +1 -1
  68. package/node_modules/qs/CHANGELOG.md +87 -22
  69. package/node_modules/qs/README.md +63 -10
  70. package/node_modules/qs/dist/qs.js +17 -17
  71. package/node_modules/qs/eslint.config.mjs +61 -50
  72. package/node_modules/qs/lib/formats.js +7 -2
  73. package/node_modules/qs/lib/parse.js +29 -10
  74. package/node_modules/qs/lib/stringify.js +5 -3
  75. package/node_modules/qs/lib/utils.js +28 -9
  76. package/node_modules/qs/package.json +6 -6
  77. package/node_modules/qs/test/parse.js +335 -2
  78. package/node_modules/qs/test/stringify.js +222 -0
  79. package/node_modules/qs/test/utils.js +61 -3
  80. package/node_modules/semver/classes/range.js +2 -2
  81. package/node_modules/semver/internal/constants.js +30 -0
  82. package/node_modules/semver/internal/trim-comparator-whitespace.js +438 -0
  83. package/node_modules/semver/package.json +1 -1
  84. package/node_modules/ws/README.md +21 -29
  85. package/node_modules/ws/lib/buffer-util.js +13 -2
  86. package/node_modules/ws/lib/sender.js +4 -1
  87. package/node_modules/ws/lib/websocket.js +8 -3
  88. package/node_modules/ws/package.json +1 -1
  89. package/package.json +1 -1
  90. package/node_modules/on-finished/HISTORY.md +0 -98
package/lib/mtx/push.js CHANGED
@@ -8,6 +8,7 @@ const buildSystem = require('../build')
8
8
 
9
9
  const { buildResponseError, buildNetworkError, mtxFetch } = require('./util/request');
10
10
  const { getMessage } = require('./util/logging');
11
+ const { bold } = require('../util/term');
11
12
  const { schemaRegex } = require('./util/urls');
12
13
 
13
14
  const JOB_STATUS = {
@@ -58,11 +59,16 @@ module.exports = class Push {
58
59
  }
59
60
 
60
61
  static getExtensionName(projectFolder) {
62
+ let name;
61
63
  try {
62
- return require(join(projectFolder, 'package.json')).name;
64
+ name = require(join(projectFolder, 'package.json')).name;
63
65
  } catch (error) {
64
66
  throw getMessage(`Extension project at ${projectFolder} is missing package.json file`, { error, command: 'push' });
65
67
  }
68
+ if (typeof name !== 'string' || !name.trim()) {
69
+ throw getMessage(`Extension project at ${projectFolder} requires a non-empty ${bold('name')} in package.json`, { command: 'push' });
70
+ }
71
+ return name;
66
72
  }
67
73
 
68
74
  static async pushTgz(params, tgz, extensionName) {
@@ -1,6 +1,6 @@
1
1
  [![REUSE status](https://api.reuse.software/badge/github.com/cap-js/asyncapi)](https://api.reuse.software/info/github.com/cap-js/asyncapi)
2
2
 
3
- # AsyncAPI
3
+ # AsyncAPI
4
4
 
5
5
  ## About this project
6
6
 
@@ -18,20 +18,10 @@ The `@cap-js/asyncapi` is a package that provides support for AsyncAPI document
18
18
  ### Installation
19
19
 
20
20
  ```sh
21
- $ npm install @cap-js/asyncapi
21
+ npm add -D @cap-js/asyncapi
22
22
  ```
23
23
 
24
- ### Usage
25
-
26
- ```js
27
- const cds = require('@sap/cds')
28
- const { compile } = require('@cap-js/asyncapi')
29
- ```
30
-
31
- ```js
32
- const csn = await cds.load(cds.env.folders.srv)
33
- const asyncapiDocument = compile(csn)
34
- ```
24
+ See the [AsyncAPI guide](https://cap.cloud.sap/docs/guides/protocols/asyncapi) for the full documentation.
35
25
 
36
26
  ## Contributing
37
27
 
@@ -43,4 +33,4 @@ We as members, contributors, and leaders pledge to make participation in our com
43
33
 
44
34
  ## Licensing
45
35
 
46
- Copyright 2024 SAP SE or an SAP affiliate company and contributors. Please see our [LICENSE](LICENSE) for copyright and license information. Detailed information including third-party components and their licensing/copyright information is available [via the REUSE tool](https://api.reuse.software/info/github.com/cap-js/asyncapi).
36
+ Copyright 2026 SAP SE or an SAP affiliate company and contributors. Please see our [LICENSE](LICENSE) for copyright and license information. Detailed information including third-party components and their licensing/copyright information is available [via the REUSE tool](https://api.reuse.software/info/github.com/cap-js/asyncapi).
@@ -0,0 +1,12 @@
1
+ const cds = require('@sap/cds')
2
+ const { compile, import: asyncapi } = require('./index')
3
+
4
+ // monkey-patch cds.compile.to and cds.import.from directly, same pattern as cds-dk's lazy getters
5
+ if (cds.compile?.to) cds.compile.to.asyncapi = compile
6
+
7
+ if (cds.import?.from) cds.import.from.asyncapi = async function (filepath, options = {}) {
8
+ const src = await cds.utils.read(filepath, 'utf-8')
9
+ const csn = asyncapi.asyncapi2csn(src)
10
+ options.inputFileKind = 'odata'
11
+ return csn
12
+ }
@@ -1,4 +1,5 @@
1
1
 
2
2
  module.exports = {
3
- compile: require('./lib/compile')
4
- }
3
+ compile: require('./lib/compile'),
4
+ import: require('./lib/import')
5
+ }
@@ -125,6 +125,7 @@ function eventToMessage(definition, name, presets, i18nBundle) {
125
125
 
126
126
  let messageObj = {
127
127
  name,
128
+ 'x-service-name': definition._service.name,
128
129
  headers: {
129
130
  type: 'object',
130
131
  properties: getHeaderProperties(name)
@@ -157,8 +158,9 @@ function entriesToMessages(definitions, events, presets, i18nBundle) {
157
158
  const messages = {}
158
159
 
159
160
  events.forEach(event => {
160
- let name = getEventName(event, definitions[event])
161
- messages[name] = eventToMessage(definitions[event], name, presets, i18nBundle)
161
+ const definition = definitions[event]
162
+ let name = getEventName(event, definition)
163
+ messages[name] = eventToMessage(definition, name, presets, i18nBundle)
162
164
  })
163
165
 
164
166
  return messages
@@ -1,5 +1,6 @@
1
1
  const { addEnum } = require('./enum')
2
2
  const schemaUtils = require('./utils')
3
+ const { makeNullable } = require('./utils')
3
4
  const { cacheAndReturn, getCache } = require('./cache')
4
5
  const { getOwnValue } = require('../../utils')
5
6
  const { toLocalizedArray } = require('./localized')
@@ -30,6 +31,7 @@ function elementsToSchema(schemaCache, definitions, entry, nullable, i18nBundle)
30
31
  const object = schemaUtils.getBaseTypeStructure('object')
31
32
  for (const [name, element] of Object.entries(entry.elements)) {
32
33
  if (getOwnValue(element, 'key')
34
+ || getOwnValue(element, 'notNull')
33
35
  || getOwnValue(element, '@mandatory')
34
36
  || (getOwnValue(element, '@Common.FieldControl') && getOwnValue(element['@Common.FieldControl'], '#') === 'Mandatory')
35
37
  ) {
@@ -44,7 +46,15 @@ function elementsToSchema(schemaCache, definitions, entry, nullable, i18nBundle)
44
46
  object.properties = {}
45
47
  }
46
48
 
47
- object.properties[name] = addAnnotations(element, elementToSchema(schemaCache, definitions, element, i18nBundle), i18nBundle)
49
+ let propSchema = addAnnotations(element, elementToSchema(schemaCache, definitions, element, i18nBundle), i18nBundle)
50
+ if (!getOwnValue(element, 'notNull')
51
+ && !getOwnValue(element, 'key')
52
+ && !getOwnValue(element, '@mandatory')
53
+ && !(getOwnValue(element, '@Common.FieldControl') && getOwnValue(element['@Common.FieldControl'], '#') === 'Mandatory')
54
+ ) {
55
+ propSchema = makeNullable(propSchema)
56
+ }
57
+ object.properties[name] = propSchema
48
58
  }
49
59
 
50
60
  if (required.length) {
@@ -88,10 +88,26 @@ function isRefEntry(entry) {
88
88
  return typeof type === 'object' && !!type && 'ref' in type && Array.isArray(type.ref)
89
89
  }
90
90
 
91
+ function makeNullable(schema) {
92
+ const type = schema.type
93
+ const nullableSchema = { ...schema }
94
+
95
+ if (type && !Array.isArray(type)) {
96
+ nullableSchema.type = [type, 'null']
97
+ }
98
+
99
+ if (Array.isArray(nullableSchema.enum) && !nullableSchema.enum.includes(null)) {
100
+ nullableSchema.enum = [...nullableSchema.enum, null]
101
+ }
102
+
103
+ return nullableSchema
104
+ }
105
+
91
106
  module.exports = {
92
107
  toString,
93
108
  getBaseTypeStructure,
94
109
  getFormatStructure,
110
+ makeNullable,
95
111
  isAssociation,
96
112
  isComposition,
97
113
  isStructured,
@@ -5,11 +5,12 @@
5
5
  * @return {string} The event type name
6
6
  */
7
7
  function getEventName(eventName, eventDefinition) {
8
-
9
8
  if (eventDefinition['@AsyncAPI.EventType']) {
10
- eventName = eventDefinition['@AsyncAPI.EventType']
9
+ return eventDefinition['@AsyncAPI.EventType']
10
+ }
11
+ if (eventDefinition['@topic']) {
12
+ return eventDefinition['@topic']
11
13
  }
12
-
13
14
  return eventName
14
15
  }
15
16
 
@@ -0,0 +1,34 @@
1
+ const ParserContext = require('./parserContext')
2
+ const { join } = require('path')
3
+ const version = require(join(__dirname, '..', '..', 'package.json')).version
4
+
5
+ function importAsyncAPI(input) {
6
+ const context = new ParserContext(input)
7
+ return convertToCSN(context)
8
+ }
9
+
10
+ function convertToCSN(context) {
11
+ const csn = {
12
+ meta: {
13
+ creator: `@cap-js/asyncapi ${version}`
14
+ },
15
+ $version: '2.0',
16
+ definitions: {}
17
+ }
18
+
19
+ Object.entries(context.services).forEach(([key, value]) => {
20
+ const events = value.events
21
+ const types = value.types
22
+ delete value.events
23
+ delete value.types
24
+
25
+ csn.definitions[key] = value
26
+ Object.assign(csn.definitions, events, types)
27
+ })
28
+
29
+ return csn
30
+ }
31
+
32
+ module.exports = {
33
+ importAsyncAPI
34
+ }
@@ -0,0 +1,18 @@
1
+ const { importAsyncAPI } = require('./importAsyncAPI')
2
+ const { validateAsyncAPI } = require('./utils')
3
+
4
+ /**
5
+ * The starting function for conversion of AsyncAPI document to csn
6
+ * @returns AsyncAPI document
7
+ */
8
+ function asyncapi2csn(source) {
9
+ // validate asyncapi document
10
+ const content = JSON.parse(source)
11
+ validateAsyncAPI(content)
12
+
13
+ return importAsyncAPI(content)
14
+ }
15
+
16
+ module.exports = {
17
+ asyncapi2csn
18
+ }
@@ -0,0 +1,9 @@
1
+ 'use strict'
2
+
3
+ const errorMessages = {
4
+ INVALID_ASYNCAPI_FILE: 'The AsyncAPI file is not valid. Specify the correct AsyncAPI file.',
5
+ INVALID_MESSAGE_NAME: 'The message name is not valid.',
6
+ UNRESOLVED_TYPE: 'Could not resolve the type: ',
7
+ }
8
+
9
+ module.exports = errorMessages
@@ -0,0 +1,368 @@
1
+ const { parseMessageName } = require('./utils')
2
+ const messages = require('./message')
3
+
4
+ module.exports = class ParserContext {
5
+ /**
6
+ * @constructor
7
+ */
8
+ constructor(content) {
9
+ this.services = this.getServicesFromContent(content)
10
+ }
11
+
12
+ getTopicName(eventName) {
13
+ return eventName.startsWith('ce/') ? eventName.substring(3) : eventName
14
+ }
15
+
16
+ /**
17
+ * Returns services from the asyncapi json object along with the events and annotations.
18
+ * @returns an object with all the services.
19
+ */
20
+ getServicesFromContent(content) {
21
+ const services = {}
22
+ const serviceAnnotations = this.getServiceAnnotations(content)
23
+
24
+ Object.entries(content.components.messages).forEach(([, value]) => {
25
+ let eventName
26
+ // Message name should be used to capture the event name
27
+ if (value.name) {
28
+ eventName = value.name
29
+ } else {
30
+ throw new Error(messages.INVALID_ASYNCAPI_FILE)
31
+ }
32
+ // uses regex to break event name in different components
33
+ // x-sap-cds-service-name is set by the CAP exporter and is the authoritative
34
+ // service key — use it directly to avoid parsing the message name string.
35
+ const service = value['x-service-name'] ?? parseMessageName(eventName, content['x-sap-application-namespace'])[1]
36
+ const event = eventName
37
+
38
+ // add service to services entry if not present
39
+ if (!(service in services)) {
40
+ services[service] = { 'kind': 'service', '@cds.external': 'true', ...serviceAnnotations, 'events': {}, 'types': {} }
41
+ }
42
+
43
+ // add event entry to service
44
+ const eventAnnotations = this.getEventAnnotations(value)
45
+
46
+ // add topic annotation
47
+ eventAnnotations['@topic'] = this.getTopicName(eventName)
48
+ let properties = {}
49
+ let types = {}
50
+
51
+ if (value.payload) {
52
+ // gets the schema being referred to in the event
53
+ const eventSchema = this.resolveRef(content, value.payload)
54
+ ;[properties, types] = this.getPropertiesAndTypes(event, eventSchema)
55
+ }
56
+ services[service].types = types
57
+ services[service].events[event] = { 'kind': 'event', '@cds.external': 'true', ...eventAnnotations, elements: properties }
58
+ })
59
+
60
+ return services
61
+ }
62
+
63
+ /**
64
+ * Returns annotations of a particular service.
65
+ * @returns an object with all the annotations as key value pairs.
66
+ */
67
+ getServiceAnnotations(content) {
68
+ const serviceAnnotationsMapping = {
69
+ 'x-sap-stateInfo': 'StateInfo', // expected: object
70
+ 'x-sap-shortText': 'ShortText' // expected: string
71
+ }
72
+
73
+ const infoAnnotationsMapping = {
74
+ 'title': 'Title', // expected: string
75
+ 'version': 'SchemaVersion', // expected: string
76
+ 'description': 'Description' // expected: string
77
+ }
78
+
79
+ const annotations = this.getAnnotations(content, serviceAnnotationsMapping)
80
+
81
+ if (content.info) {
82
+ for (const [key, value] of Object.entries(infoAnnotationsMapping)) {
83
+ if (content.info[key] && !(content.info[key] instanceof Object)) {
84
+ // info values will always be in string format
85
+ annotations['@AsyncAPI.' + value] = content.info[key]
86
+ }
87
+ }
88
+ }
89
+
90
+ return annotations
91
+ }
92
+
93
+ /**
94
+ * Returns annotations of a particular event. Here the parameter content is event content.
95
+ * @returns an object with all the annotations as key value pairs.
96
+ */
97
+ getEventAnnotations(content) {
98
+ const eventLevelAnnotationsMapping = {
99
+ 'x-sap-event-spec-version': 'EventSpecVersion', // expected: string
100
+ 'x-sap-event-source': 'EventSource', // expected: string
101
+ 'x-sap-event-version': 'EventSchemaVersion', // expected: string
102
+ 'x-sap-event-source-parameters': 'EventSourceParams', // expected: object
103
+ 'x-sap-event-characteristics': 'EventCharacteristics', // expected: object
104
+ 'x-sap-stateInfo': 'EventStateInfo' // expected: object
105
+ }
106
+
107
+ const annotations = this.getAnnotations(content, eventLevelAnnotationsMapping)
108
+
109
+ return annotations
110
+ }
111
+
112
+ /**
113
+ * Returns validity of the annotation value. Here the parameter
114
+ * content is the annotation name and value of the annotation.
115
+ * @returns true/false depending on the annotation value.
116
+ */
117
+ isValidAnnotationValue(annotation, value) {
118
+ const objectTypeAnnotations = ['StateInfo', 'EventSourceParams', 'EventCharacteristics', 'EventStateInfo']
119
+ // if the value is an array or null
120
+ if (Array.isArray(value) || value === null) return false
121
+
122
+ if (objectTypeAnnotations.includes(annotation) && (value instanceof Object)) {
123
+ return true
124
+ } else if ((!objectTypeAnnotations.includes(annotation)) && (typeof value === 'string')) {
125
+ return true
126
+ } else {
127
+ console.warn('WARNING: Annotations which are not following the expected type are omitted.')
128
+ return false
129
+ }
130
+ }
131
+
132
+ /**
133
+ * Returns annotations for an event/service. Here the parameter content
134
+ * is event/service content along with the corresponding mapping.
135
+ * @returns an object with the annotations as key value pairs.
136
+ */
137
+ getAnnotations(content, annotationsMapping) {
138
+ const annotations = {}
139
+
140
+ for (const [key, value] of Object.entries(content)) {
141
+ // case: known annotation
142
+ if (annotationsMapping[key] && this.isValidAnnotationValue(annotationsMapping[key], value)) {
143
+ annotations['@AsyncAPI.' + annotationsMapping[key]] = value
144
+ }
145
+ // case: unkown annotation
146
+ else if (key.startsWith('x-')) {
147
+ if (!annotations['@AsyncAPI.Extensions']) {
148
+ annotations['@AsyncAPI.Extensions'] = {}
149
+ }
150
+ // removes x- from the element
151
+ annotations['@AsyncAPI.Extensions'][key.substring(2)] = value
152
+ }
153
+ }
154
+
155
+ return annotations
156
+ }
157
+
158
+ /**
159
+ * Returns the referred schema. Here the content is the asyncapi
160
+ * document and payloadObject is the reference path
161
+ * @returns an object with the referred schema.
162
+ */
163
+ resolveRef(content, payloadObject) {
164
+ let schema = {}
165
+
166
+ if (payloadObject['$ref']) {
167
+ const pathArray = payloadObject['$ref'].split('/')
168
+ pathArray.forEach((value) => {
169
+ if (value === '#') schema = content
170
+ else if (schema[value]) schema = schema[value]
171
+ // in case path doesn't exist in the file
172
+ else schema = {}
173
+ })
174
+
175
+ if (!Object.keys(schema).length) {
176
+ console.warn('WARNING: Unable to resolve ref path: ' + payloadObject['$ref'])
177
+ }
178
+ }
179
+ else if (payloadObject instanceof Object) {
180
+ // in case ref is not there and schema object is there
181
+ schema = payloadObject
182
+ }
183
+
184
+ return schema
185
+ }
186
+
187
+ /**
188
+ * Returns the properties and types generated, related to an event.
189
+ * @returns a properties object and a types object.
190
+ */
191
+ getPropertiesAndTypes(eventName, content) {
192
+ const properties = {}
193
+ let types = {}
194
+
195
+ if (content.properties) {
196
+ for (const [key, value] of Object.entries(content.properties)) {
197
+ // will contain type objects generated for a property
198
+ let referredTypes
199
+ let isRequired = false
200
+ if (content['required']?.includes(key)) isRequired = true
201
+ ;[properties[key], referredTypes] = this.parseProperty(eventName, key, value, isRequired)
202
+ // collecting all the types
203
+ types = { ...types, ...referredTypes }
204
+ }
205
+ }
206
+ return [properties, types]
207
+ }
208
+
209
+ /**
210
+ * Returns the processed objects for property and type.
211
+ * @returns property object and type object.
212
+ */
213
+ parseProperty(eventName, propertyName, propertyContent, isRequired) {
214
+ let property = {}
215
+ let propertyAttributes
216
+ let types = {}
217
+
218
+ if (isRequired === true) {
219
+ property['@mandatory'] = true
220
+ }
221
+
222
+ let type = this.getTypeFromArray(propertyContent.type)
223
+
224
+ // case where type is defined
225
+ if (type && (type === 'object' || type === 'array')) {
226
+ // for generation of type: event name + property name
227
+ propertyName = propertyName[0].toUpperCase() + propertyName.substring(1)
228
+ let typeName = eventName + '.' + propertyName
229
+ // generate object for the introduced type
230
+ const elements = this.getTypeElements(propertyContent)
231
+ types[typeName] = { 'kind': 'type', '@cds.external': 'true', ...elements }
232
+ // referring to the generated type from the property
233
+ propertyAttributes = { 'type': typeName }
234
+ } else {
235
+ propertyAttributes = this.getPropertyObject(propertyContent)
236
+ }
237
+ property = { ...property, ...propertyAttributes }
238
+
239
+ return [property, types]
240
+ }
241
+
242
+ /**
243
+ * Returns generated type for the property.
244
+ * @returns an object for a type.
245
+ */
246
+ getTypeElements(propertyContent) {
247
+ let properties = {}
248
+ let type = this.getTypeFromArray(propertyContent.type)
249
+
250
+ // for nested object
251
+ if (type === 'object') {
252
+ for (const [key, value] of Object.entries(propertyContent.properties)) {
253
+ properties[key] = this.getTypeElements(value)
254
+ }
255
+ }
256
+ // for nested array
257
+ else if (type === 'array') {
258
+ properties.items = this.getTypeElements(propertyContent.items)
259
+ return properties
260
+ }
261
+ // base data type mapping
262
+ else {
263
+ return this.getPropertyObject(propertyContent)
264
+ }
265
+
266
+ return { elements: properties }
267
+ }
268
+
269
+ /**
270
+ * Returns type for the property from the AsyncAPI Document.
271
+ * @returns the value of type.
272
+ */
273
+ getTypeFromArray(typeValue) {
274
+ let type
275
+ // if type is an array
276
+ if (Array.isArray(typeValue)) {
277
+ // one of the value will be null
278
+ type = typeValue[0] === 'null' ? typeValue[1] : typeValue[0]
279
+ } else {
280
+ type = typeValue
281
+ }
282
+
283
+ return type
284
+ }
285
+
286
+ /**
287
+ * Returns generated object for a single property.
288
+ * @returns an object for a property.
289
+ */
290
+ getPropertyObject(propertyContent) {
291
+ const property = {}
292
+ if ('$ref' in propertyContent) {
293
+ const refPathParts = propertyContent['$ref'].split('/')
294
+ const propertyName = refPathParts[refPathParts.length - 1]
295
+ property['type'] = { 'ref': [refPathParts[3], propertyName] }
296
+ return property
297
+ }
298
+ const inputType = this.getTypeFromArray(propertyContent.type)
299
+ const cdsType = this.getDataType(inputType, propertyContent.format)
300
+
301
+ property['type'] = cdsType
302
+
303
+ // case: cds type is decimal
304
+ if (cdsType === 'cds.Decimal') {
305
+ if (propertyContent['x-sap-precision']) property.precision = propertyContent['x-sap-precision']
306
+ if (propertyContent['x-sap-scale']) property.scale = propertyContent['x-sap-scale']
307
+ }
308
+
309
+ if (propertyContent['default']) {
310
+ property['default'] = { 'val': propertyContent['default'] }
311
+ }
312
+
313
+ if (propertyContent['enum']) {
314
+ property['enum'] = this.getEnumObject(propertyContent['enum'])
315
+ }
316
+
317
+ return property
318
+ }
319
+
320
+ /**
321
+ * Takes the enums from the AsyncAPI Document.
322
+ * @returns the property object of enums.
323
+ */
324
+ getEnumObject(enumArray) {
325
+ const enumProperty = {}
326
+
327
+ enumArray.forEach(val => {
328
+ enumProperty[val] = {}
329
+ })
330
+
331
+ return enumProperty
332
+ }
333
+
334
+ /**
335
+ * Takes the type and format from the document and returns the corresponding cds type.
336
+ * @returns the corresponding cds type.
337
+ */
338
+ getDataType(type, format = null) {
339
+ const asyncapiToCdsMapping = {
340
+ 'number': 'cds.Double',
341
+ 'boolean': 'cds.Boolean',
342
+ 'integer': 'cds.Integer'
343
+ }
344
+
345
+ if (type === 'string') {
346
+ switch (format) {
347
+ case 'int64':
348
+ return 'cds.Integer64'
349
+ case 'decimal':
350
+ return 'cds.Decimal'
351
+ case 'uuid':
352
+ return 'cds.UUID'
353
+ case 'date':
354
+ return 'cds.Date'
355
+ case 'partial-time':
356
+ return 'cds.Time'
357
+ case 'date-time':
358
+ return 'cds.Timestamp'
359
+ default:
360
+ return 'cds.LargeString'
361
+ }
362
+ } else if (asyncapiToCdsMapping[type]) {
363
+ return asyncapiToCdsMapping[type]
364
+ } else {
365
+ throw new Error(messages.UNRESOLVED_TYPE + `'${type}'`)
366
+ }
367
+ }
368
+ }