@cap-js/ord 1.0.3 → 1.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/README.md CHANGED
@@ -4,9 +4,15 @@
4
4
 
5
5
  ## About this project
6
6
 
7
- This plugin enables generation of ORD document for CAP based applications. When you adopt ORD, your application gains a single entry point, known as the Service Provider Interface. This interface allows you to discover and gather relevant information or metadata. You can use this information to construct a static metadata catalog or to perform a detailed runtime inspection of your actual system landscapes.
7
+ This plugin adds support for the [Open Resource Discovery](https://sap.github.io/open-resource-discovery/) (ORD) protocol for CAP based applications.
8
+ When you add the ORD plugin, your application gains a single entry point, which allows to discover and gather machine-readable information or metadata about the application.
9
+ You can use this information to construct a static metadata catalog or to perform a detailed runtime inspection of your actual system instances / system landscapes.
8
10
 
9
- Open Resource Discovery [(ORD)](https://sap.github.io/open-resource-discovery/) is a protocol that enables applications and services to self-describe their available resources and capabilities. It's not only useful for describing static documentation, but it also accurately reflects tenant-specific configurations and extensions at runtime. Typically, ORD is used to describe APIs and Events, but it also supports higher-level concepts like Entity Types (Business Objects) and Data Products (beta).
11
+ For more information, have a look at the [Open Resource Discovery](https://sap.github.io/open-resource-discovery/) page.
12
+
13
+ > ⚠ By installing this plugin, the metadata describing your CAP application will be made openly accessible.
14
+ >
15
+ > If you have a need to protect your metadata, please refrain from installing this plugin until we support metadata protection (planned).
10
16
 
11
17
  ## Requirements and Setup
12
18
 
@@ -21,13 +27,13 @@ npm install @cap-js/ord
21
27
  #### Programmatic API
22
28
 
23
29
  ```js
24
- const cds = require('@sap/cds')
25
- require('@cap-js/ord');
30
+ const cds = require("@sap/cds");
31
+ require("@cap-js/ord");
26
32
  ```
27
33
 
28
34
  ```js
29
- const csn = await cds.load(cds.env.folders.srv)
30
- const ord = cds.compile.to.ord(csn)
35
+ const csn = await cds.load(cds.env.folders.srv);
36
+ const ord = cds.compile.to.ord(csn);
31
37
  ```
32
38
 
33
39
  #### Command Line
@@ -40,24 +46,28 @@ cds compile <path to srv folder> --to ord [-o] [destinationFilePath]
40
46
 
41
47
  #### ORD Endpoints
42
48
 
43
- 1) Run `cds watch` in the application's root.
44
- 2) Check the following relative paths for ORD information - `/.well-known/open-resource-discovery` , `/open-resource-discovery/v1/documents/1`.
45
-
49
+ 1. Run `cds watch` in the application's root.
50
+ 2. Check the following relative paths for ORD information - `/.well-known/open-resource-discovery` , `/open-resource-discovery/v1/documents/1`.
46
51
 
47
52
  <img width="1300" alt="Sample Application Demo" style="border-radius:0.5rem;" src="./asset/etc/ordEndpoint.gif">
48
53
 
49
54
  ### Customizing ORD Document
50
55
 
51
- You can find more information, such as how to customize the ORD Document, in this [document](ord.md).
52
-
56
+ You can find more information, such as how to customize the ORD Document, in this [document](./docs/ord.md).
53
57
 
54
58
  ## Support, Feedback, Contributing
55
59
 
56
60
  This project is open to feature requests/suggestions, bug reports etc. via [GitHub issues](https://github.com/cap-js/ord/issues). Contribution and feedback are encouraged and always welcome. For more information about how to contribute, the project structure, as well as additional contribution information, see our [Contribution Guidelines](CONTRIBUTING.md).
57
61
 
58
62
  ## Security / Disclosure
63
+
59
64
  If you find any bug that may be a security problem, please follow our instructions at [in our security policy](https://github.com/cap-js/ord/issues/security/policy) on how to report it. Please do not create GitHub issues for security-related doubts or problems.
60
65
 
66
+ At the current state, the plugin will expose static metadata with open access.
67
+ This means that the CAP resources are described and documented openly, but it does not imply that the resources themselves can be accessed.
68
+
69
+ If you have a need to protect your metadata, please refrain from installing this plugin until we support metadata protection.
70
+
61
71
  ## Code of Conduct
62
72
 
63
73
  We as members, contributors, and leaders pledge to make participation in our community a harassment-free experience for everyone. By participating in this project, you agree to abide by its [Code of Conduct](https://github.com/cap-js/.github/blob/main/CODE_OF_CONDUCT.md) at all times.
@@ -0,0 +1,25 @@
1
+ const ORD_RESOURCE_TYPE = Object.freeze(
2
+ {
3
+ "service": "service",
4
+ "entity": "entity",
5
+ "event": "event",
6
+ "api": "api"
7
+ });
8
+
9
+ const COMPILER_TYPES = Object.freeze(
10
+ {
11
+ "oas3": "oas3",
12
+ "asyncapi2": "asyncapi2",
13
+ "edmx": "edmx"
14
+ });
15
+
16
+ const CONTENT_MERGE_KEY = "ordId";
17
+
18
+ const ORD_EXTENSIONS_PREFIX = "@ORD.Extensions.";
19
+
20
+ module.exports = {
21
+ ORD_RESOURCE_TYPE,
22
+ COMPILER_TYPES,
23
+ CONTENT_MERGE_KEY,
24
+ ORD_EXTENSIONS_PREFIX
25
+ };
package/lib/defaults.js CHANGED
@@ -1,66 +1,84 @@
1
-
2
1
  const regexWithRemoval = (name) => {
3
- if(name){
4
- return name.replace(/[^a-zA-Z0-9]/g,'');
5
- }
6
- }
7
- const regexWithUnderScore = (name) => {
8
- return regexWithRemoval(name.charAt(0)) + name.slice(1, name.length).replace(/[^a-zA-Z0-9]/g,'_');
9
- }
2
+ if (name) {
3
+ return name.replace(/[^a-zA-Z0-9]/g, "");
4
+ }
5
+ };
6
+
7
+ const nameWithDot = (name) => {
8
+ return (
9
+ regexWithRemoval(name.charAt(0)) +
10
+ name.slice(1, name.length).replace(/[^a-zA-Z0-9]/g, ".")
11
+ );
12
+ };
13
+
14
+ const nameWithSpaces = (name) => {
15
+ return (
16
+ regexWithRemoval(name.charAt(0)) +
17
+ name.slice(1, name.length).replace(/[^a-zA-Z0-9]/g, " ")
18
+ );
19
+ };
20
+
21
+ const defaultProductOrdId = (name) => `customer:product:${nameWithDot(name)}:`;
10
22
 
11
23
  /**
12
24
  * Module containing default configuration for ORD Document.
13
25
  * @module defaults
14
26
  */
15
27
  module.exports = {
16
- openResourceDiscovery: "1.9",
17
- policyLevel: "none",
18
- description: "this is an application description",
19
- products: (name) => [
20
- {
21
- ordId: `customer:product:${regexWithUnderScore(name)}:`,
22
- title: `ORD App Title for ${regexWithRemoval(name)}`,
23
- shortDescription: " shortDescription for products",
24
- vendor: "customer:vendor:SAPCustomer:",
25
- },
26
- ],
27
- groupTypeId: "sap.cds:service",
28
- packages: function getPackageData (name, policyLevel,capNamespace) {
29
- function createPackage(name, tag) {
30
- return {
31
- ordId: `${regexWithRemoval(name)}:package:${capNamespace}${tag}`,
32
- title: `sample title for ${regexWithRemoval(name)}`,
33
- shortDescription: "Here is the shortDescription for packages",
34
- description: "Here is the description for packages",
35
- version: "1.0.0",
36
- partOfProducts: [`customer:product:${regexWithUnderScore(name)}:`],
37
- vendor: "customer:vendor:SAP:"
38
- };
39
- }
40
-
41
- if(policyLevel.split(':')[0].toLowerCase() === 'sap') {
42
- return [createPackage(name, "-api:v1"), createPackage(name, "-event:v1")];
43
- }
44
- else {
45
- return [createPackage(name, ":v1")];
46
- }
47
- },
48
- consumptionBundles: (name) => [
49
- {
50
- ordId: `${regexWithRemoval(name)}:consumptionBundle:unknown:v1`,
51
- version: "1.0.0",
52
- title: "Unprotected resources",
53
- shortDescription:
54
- "If we have another protected API then it will be another object",
55
- description:
56
- "This Consumption Bundle contains all resources of the reference app which are unprotected and do not require authentication",
28
+ $schema:
29
+ "https://sap.github.io/open-resource-discovery/spec-v1/interfaces/Document.schema.json",
30
+ openResourceDiscovery: "1.9",
31
+ policyLevel: "none",
32
+ description: "this is an application description",
33
+ products: (name) => [
34
+ {
35
+ ordId: defaultProductOrdId(name),
36
+ title: nameWithSpaces(name),
37
+ shortDescription: "Description for " + nameWithSpaces(name),
38
+ vendor: "customer:vendor:customer:",
39
+ },
40
+ ],
41
+ groupTypeId: "sap.cds:service",
42
+ packages: function getPackageData(name, policyLevel, ordNamespace) {
43
+ function createPackage(name, tag) {
44
+ return {
45
+ ordId: `${ordNamespace}:package:${regexWithRemoval(name
46
+ )}${tag}`,
47
+ title: nameWithSpaces(name),
48
+ shortDescription:
49
+ "Short description for " + nameWithSpaces(name),
50
+ description: "Description for " + nameWithSpaces(name),
51
+ version: "1.0.0",
52
+ partOfProducts: [defaultProductOrdId(name)],
53
+ vendor: "customer:vendor:Customer:",
54
+ };
55
+ }
56
+
57
+ if (policyLevel.split(":")[0].toLowerCase() === "sap") {
58
+ return [
59
+ createPackage(name, "-api:v1"),
60
+ createPackage(name, "-event:v1"),
61
+ ];
62
+ } else {
63
+ return [createPackage(name, ":v1")];
64
+ }
57
65
  },
58
- ],
59
-
66
+ consumptionBundles: (name) => [
67
+ {
68
+ ordId: `${regexWithRemoval(name)}:consumptionBundle:unknown:v1`,
69
+ version: "1.0.0",
70
+ title: "Unprotected resources",
71
+ shortDescription:
72
+ "If we have another protected API then it will be another object",
73
+ description:
74
+ "This Consumption Bundle contains all resources of the reference app which are unprotected and do not require authentication",
75
+ },
76
+ ],
77
+
60
78
  apiResources: [],
61
79
  eventResources: [],
62
80
  entityTypes: [],
63
- baseTemplate : {
81
+ baseTemplate: {
64
82
  openResourceDiscoveryV1: {
65
83
  documents: [
66
84
  {
@@ -73,5 +91,5 @@ module.exports = {
73
91
  },
74
92
  ],
75
93
  },
76
- }
77
- }
94
+ },
95
+ };
@@ -0,0 +1,67 @@
1
+ const path = require("path");
2
+ const cds = require("@sap/cds");
3
+ const _ = require('lodash');
4
+ const { CONTENT_MERGE_KEY } = require('./constants');
5
+
6
+
7
+ function cleanNullProperties(obj) {
8
+ for (const key in obj) {
9
+ if (obj[key] === null) {
10
+ delete obj[key];
11
+ } else if (typeof obj[key] === 'object') {
12
+ cleanNullProperties(obj[key]);
13
+ }
14
+ }
15
+ return obj;
16
+ }
17
+
18
+ function patchGeneratedOrdResources(destinationObj, sourceObj) {
19
+ const destObj = _.keyBy(destinationObj, CONTENT_MERGE_KEY);
20
+ const srcObj = _.keyBy(sourceObj, CONTENT_MERGE_KEY);
21
+ for (const ordId in srcObj) {
22
+ if (ordId in destObj) {
23
+ destObj[ordId] = _.assignWith(structuredClone(destObj[ordId]), structuredClone(srcObj[ordId]));
24
+ } else {
25
+ destObj[ordId] = srcObj[ordId];
26
+ }
27
+ }
28
+ return cleanNullProperties(Object.values(destObj));
29
+ }
30
+
31
+ function compareAndHandleCustomORDContentWithExistingContent(ordContent, customORDContent, logger) {
32
+ const clonedOrdContent = structuredClone(ordContent);
33
+ for (const key in customORDContent) {
34
+ const propertyType = typeof customORDContent[key];
35
+ if (propertyType !== 'object' && propertyType !== 'array') {
36
+ logger.warn('Found ord top level primitive ord property in customOrdFile:', key, ', please define it in .cdsrc.json.');
37
+ continue;
38
+ }
39
+ if (key in ordContent) {
40
+ clonedOrdContent[key] = patchGeneratedOrdResources(clonedOrdContent[key], customORDContent[key]);
41
+ } else {
42
+ clonedOrdContent[key] = customORDContent[key];
43
+ }
44
+ }
45
+ return clonedOrdContent;
46
+ }
47
+
48
+ function getCustomORDContent(appConfig) {
49
+ if (appConfig.env?.customOrdContentFile) {
50
+ const customORDContent = require(path.join(cds.root, appConfig.env.customOrdContentFile));
51
+ return customORDContent;
52
+ }
53
+ return {};
54
+ }
55
+
56
+ function extendCustomORDContentIfExists(appConfig, ordContent, logger) {
57
+ const customORDContent = getCustomORDContent(appConfig);
58
+
59
+ if (customORDContent) {
60
+ ordContent = compareAndHandleCustomORDContentWithExistingContent(ordContent, customORDContent, logger);
61
+ }
62
+ return ordContent;
63
+ }
64
+
65
+ module.exports = {
66
+ extendCustomORDContentIfExists,
67
+ };
package/lib/metaData.js CHANGED
@@ -1,43 +1,41 @@
1
1
  const cds = require('@sap/cds/lib');
2
- const { compile : openapi } = require('@cap-js/openapi')
3
- const { compile : asyncapi } = require('@cap-js/asyncapi');
2
+ const { compile: openapi } = require('@cap-js/openapi')
3
+ const { compile: asyncapi } = require('@cap-js/asyncapi');
4
+ const { COMPILER_TYPES } = require('./constants');
4
5
 
5
- /**
6
- * Retrieves the compiled meta data for the specific service or event according to its compiled type
7
- * @param {string} data
8
- * @returns {string, JSON|XML} contentType, response
9
- */
10
6
  module.exports = async (data) => {
11
7
  const parts = data?.split("/").pop().replace(/\.json$/, '').split(".");
12
8
  const compilerType = parts.pop();
13
9
  const serviceName = parts.join(".");
14
10
  const csn = cds.services[serviceName].model;
15
11
 
16
- let responseFile;
12
+ let responseFile;
17
13
  const options = { service: serviceName, as: 'str', messages: [] }
18
- if (compilerType === "oas3") {
19
- try {
20
- responseFile = openapi(csn, options);
21
- } catch (error) {
22
- console.error("OpenApi error:", error.message);
23
- throw error;
24
- }
25
- } else if (compilerType === "asyncapi2") {
26
- try {
27
- responseFile = asyncapi(csn, options);
28
- } catch (error) {
29
- console.error("AsyncApi error:", error.message);
30
- throw error;
31
- }
32
- } else if (compilerType === "edmx") {
33
- try {
34
- responseFile = await cds.compile(csn).to["edmx"](options);
35
- } catch (error) {
36
- console.error("Edmx error:", error.message);
37
- throw error;
38
- }
14
+ switch (compilerType) {
15
+ case COMPILER_TYPES.oas3:
16
+ try {
17
+ responseFile = openapi(csn, options);
18
+ } catch (error) {
19
+ console.error("OpenApi error:", error.message);
20
+ throw error;
21
+ }
22
+ break;
23
+ case COMPILER_TYPES.asyncapi2:
24
+ try {
25
+ responseFile = asyncapi(csn, options);
26
+ } catch (error) {
27
+ console.error("AsyncApi error:", error.message);
28
+ throw error;
29
+ }
30
+ break;
31
+ case COMPILER_TYPES.edmx:
32
+ try {
33
+ responseFile = await cds.compile(csn).to["edmx"](options);
34
+ } catch (error) {
35
+ console.error("Edmx error:", error.message);
36
+ throw error;
37
+ }
39
38
  }
40
-
41
39
  return {
42
40
  contentType: `application/${compilerType === "edmx" ? "xml" : "json"}`,
43
41
  response: responseFile
package/lib/ord.js CHANGED
@@ -1,202 +1,166 @@
1
1
  const path = require("path");
2
2
  const cds = require("@sap/cds");
3
3
  const { exists } = cds.utils;
4
+ const _ = require("lodash");
4
5
  const defaults = require("./defaults");
5
6
  const {
6
- fCreateAPIResourceTemplate,
7
- fCreateEventResourceTemplate,
8
- fCreateGroupsTemplateForService,
9
- fCreateGroupsTemplateForEvent,
10
- fCreateEntityTypeTemplate
7
+ createAPIResourceTemplate,
8
+ createEventResourceTemplate,
9
+ createGroupsTemplateForService,
10
+ createEntityTypeTemplate
11
11
  } = require('./templates');
12
+ const { extendCustomORDContentIfExists } = require('./extendOrdWithCustom');
13
+ const { ORD_RESOURCE_TYPE } = require('./constants');
12
14
 
13
- /**
14
- * Initializes global object based on package.json and CSN object.
15
- * @param {Object} csn object
16
- * @returns {Object} An object containing global variables.
17
- */
18
- const fInitializeGlobal = (csn) => {
19
- let packagejsonPath = path.join(cds.root,'package.json')
15
+ const logger = cds.log('ord-plugin');
16
+
17
+ const initializeAppConfig = (csn) => {
18
+ let packagejsonPath = path.join(cds.root, 'package.json')
20
19
  let packageJson;
21
20
  if (exists(packagejsonPath)) {
22
21
  packageJson = require(packagejsonPath);
23
22
  } else {
24
23
  throw new Error(`package.json not found in the project root directory`);
25
24
  }
25
+ const packageName = packageJson.name;
26
26
  const appName = packageJson.name.replace(/^[@]/, "").replace(/[@/]/g, "-");
27
- const aModelKeys = Object.keys(csn.definitions);
28
- const aEvents = [];
29
- const aServices = [];
30
- const aODMEntity = [];
31
-
32
- const capNamespace = csn.namespace;
33
- // namespace variable value if present in cdsrc.json take it there or else from package.json
27
+ const modelKeys = Object.keys(csn.definitions);
28
+ const events = [];
29
+ const serviceNames = [];
30
+ let odmEntity = [];
31
+
32
+ // namespace variable value if present in cdsrc.json take it there or else from package.json
34
33
  //if cdsrc.json does not have applicationNamespace, then use just the namespace
35
- const namespace = cds.env["ord"]?.namespace || `customer.${appName}`;
36
- const applicationNamespace = cds.env?.export?.asyncapi?.applicationNamespace;
37
-
38
- if (applicationNamespace && fGetNamespaceComponents(namespace) !== fGetNamespaceComponents(applicationNamespace)) {
39
- console.warn('ORD and AsyncAPI namespaces should be the same.');
40
- }
34
+ const vendorNamespace = "customer";
35
+ const ordNamespace = cds.env["ord"]?.namespace || `${vendorNamespace}.${packageName.replace(/[^a-zA-Z0-9]/g, "")}`;
36
+ const eventApplicationNamespace = cds.env?.export?.asyncapi?.applicationNamespace;
41
37
 
42
- const fEventFilter = (keyDefinition) => keyDefinition.kind === "event";
43
- const fServicesFilter = (keyDefinition) => keyDefinition.kind === "service" && !keyDefinition['@cds.external'];
44
- const fODMEntityFilter = (key, keyDefinition) => {
45
- return keyDefinition.kind === "entity"
46
- && key.includes(capNamespace)
47
- && !key.includes(".texts")
48
- && keyDefinition["@ODM.entityName"];
38
+ if (eventApplicationNamespace && ordNamespace !== eventApplicationNamespace) {
39
+ console.warn("ORD and AsyncAPI namespaces should be the same.");
49
40
  }
50
41
 
51
- aModelKeys.forEach((key) => {
42
+ for (const key of modelKeys) {
52
43
  const keyDefinition = csn.definitions[key];
53
- if(fServicesFilter(keyDefinition)){
54
- aServices.push(key);
55
- } else if(fODMEntityFilter(key, keyDefinition)) {
56
- aODMEntity.push(fCreateEntityTypeTemplate(keyDefinition));
57
- } else if(fEventFilter(keyDefinition)){
58
- aEvents.push(key);
44
+ switch (keyDefinition.kind) {
45
+ case ORD_RESOURCE_TYPE.service:
46
+ if (!keyDefinition["@cds.external"]) {
47
+ serviceNames.push(key);
48
+ }
49
+ break;
50
+ // TODO: should be rewritten
51
+ case ORD_RESOURCE_TYPE.entity:
52
+ if (!key.includes(".texts") && keyDefinition["@ODM.entityName"]) {
53
+ odmEntity.push(createEntityTypeTemplate(keyDefinition));
54
+ }
55
+ break;
56
+ case ORD_RESOURCE_TYPE.event:
57
+ events.push(key);
58
+ break;
59
59
  }
60
- });
60
+ }
61
61
 
62
+ odmEntity = _.uniqBy(odmEntity, "ordId");
62
63
 
63
64
  return {
64
65
  env: cds.env["ord"],
65
- capNamespace,
66
66
  appName,
67
- aEvents,
68
- aServices,
69
- aODMEntity,
70
- namespace,
71
- applicationNamespace
72
- }
73
- }
67
+ events,
68
+ serviceNames,
69
+ odmEntity,
70
+ ordNamespace,
71
+ eventApplicationNamespace,
72
+ packageName,
73
+ };
74
+ };
75
+
76
+ const _getPolicyLevel = (appConfig) =>
77
+ appConfig.env?.policyLevel || defaults.policyLevel;
78
+
79
+ const _getDescription = (appConfig) =>
80
+ appConfig.env?.description || defaults.description;
81
+
82
+ const _getProducts = (appConfig) =>
83
+ appConfig.env?.products || defaults.products(appConfig.packageName);
74
84
 
75
- /**
76
- * Retrieves first two components of the namespace.
77
- * @param {string} namespace
78
- * @returns {string} The first two components of the namespace.
79
- */
80
- const fGetNamespaceComponents = (namespace) => namespace.split('.').slice(0, 2).join('.');
81
-
82
- /**
83
- * Retrieves the policy level.
84
- * Hierarchy to check data: cdsrc.json > defaults
85
- * @returns {string} The policy level.
86
- */
87
- const fGetPolicyLevel = (global) => global.env?.policyLevel || defaults.policyLevel;
88
-
89
- /**
90
- * Retrieves the root level ORD document description.
91
- * Hierarchy to check data: cdsrc.json > defaults
92
- * @returns {string} The ORD document description.
93
- */
94
- const fGetDescription = (global) => global.env?.description || defaults.description;
95
-
96
- /**
97
- * Retrieves the products.
98
- * Hierarchy to check data: cdsrc.json > defaults
99
- * @returns {Array<object>} The products array.
100
- * if global.namespace is defined in cdsrc.json, then use it, else use the appName from package.json
101
- */
102
- const fGetProducts = (global) => global.env?.products || defaults.products(global.namespace);
103
-
104
- /**
105
- * Retrieves the groups that services belongs to.
106
- * Gets list of groups from CDS runtime object.
107
- * @param {Object} csn object
108
- * @returns {Array<object>} The groups array.
109
- */
110
- const fGetGroups = (csn, global) => {
111
- // storing the group ids in a set to avoid duplicates
112
- let groupIds = new Set();
113
-
114
- let serviceGroups = global.aServices
115
- .map((srv) => fCreateGroupsTemplateForService(srv, csn.definitions[srv], groupIds))
116
- .filter((resource) => resource !== null && resource !== undefined);
117
- let eventGroups = global.aEvents
118
- .map((event) => fCreateGroupsTemplateForEvent(event, csn.definitions[event], groupIds))
119
- .filter((resource) => resource !== null && resource !== undefined);
120
-
121
- return [...serviceGroups, ...eventGroups];
85
+ const _getGroups = (csn, appConfig) => {
86
+ return appConfig.serviceNames
87
+ .flatMap((serviceName) => createGroupsTemplateForService(serviceName, csn.definitions[serviceName], appConfig))
88
+ .filter((resource) => !!resource);
122
89
  };
123
90
 
124
- /**
125
- * Retrieves the packages.
126
- * Hierarchy to check data: cdsrc.json > defaults
127
- * @returns {Array<object>} The packages array.
128
- */
129
- const fGetPackages = (policyLevel,global) => global.env?.packages || (global.aEvents.length) ? defaults.packages(global.namespace,policyLevel,global.capNamespace) : defaults.packages(global.namespace,policyLevel,global.capNamespace).slice(0, 1)
130
-
131
- /**
132
- * Retrieves the consumption bundles.
133
- * Hierarchy to check data: cdsrc.json > defaults
134
- * @returns {Array<object>} The consumption bundles array.
135
- */
136
- const fGetConsumptionBundles = (global) => global.env?.consumptionBundles || (global.aEvents.length) ? defaults.consumptionBundles(global.capNamespace) : defaults.consumptionBundles(global.capNamespace).slice(0,1)
137
-
138
- /**
139
- * Retrieves the API Resources
140
- * Gets list of services from CSN object
141
- * @param {Object} csn object
142
- * @returns {Array<object>} The API Resources array.
143
- */
144
- const fGetAPIResources = (csn, global,packageIds) => {
145
- const apiResources = [];
146
- global.aServices.forEach((srv) => {
147
- fCreateAPIResourceTemplate(srv, csn.definitions[srv], global,packageIds)?.forEach(
148
- (resource) => {
149
- if (resource !== null && resource !== undefined) {
150
- apiResources.push(resource);
151
- }
152
- }
153
- );
154
- });
155
- return apiResources;
91
+ const _getPackages = (policyLevel, appConfig) =>
92
+ appConfig.env?.packages || appConfig.events.length
93
+ ? defaults.packages(appConfig.appName, policyLevel, appConfig.ordNamespace)
94
+ : defaults
95
+ .packages(appConfig.appName, policyLevel, appConfig.ordNamespace)
96
+ .slice(0, 1);
97
+
98
+ const _getAPIResources = (csn, appConfig, packageIds) => {
99
+ return appConfig.serviceNames
100
+ .flatMap((serviceName) => createAPIResourceTemplate(serviceName, csn.definitions[serviceName], appConfig, packageIds) || [])
101
+ .filter((resource) => !!resource);
156
102
  };
157
103
 
158
- /**
159
- * Retrieves the Event Resources
160
- * Gets list of event from CSN object
161
- * @param {Object} csn object
162
- * @returns {Array<object>} The Event Resources array.
163
- */
164
- const fGetEventResources = (csn, global,packageIds) => global.aEvents.map((srv) => fCreateEventResourceTemplate(srv, csn.definitions[srv], global, packageIds)).filter((resource) => resource !== null && resource !== undefined);
104
+ const _getEventResources = (csn, appConfig, packageIds) => {
105
+ if (appConfig.events.length === 0) return [];
106
+ return appConfig.serviceNames
107
+ .filter((serviceName) => appConfig.events.some((eventName) => eventName.startsWith(serviceName)))
108
+ .flatMap((serviceName) => createEventResourceTemplate(serviceName, csn.definitions[serviceName], appConfig, packageIds) || []);
109
+ };
165
110
 
166
- module.exports = (csn) => {
167
- const linkedCsn = cds.linked(csn);
168
- Object.assign(global, fInitializeGlobal(linkedCsn));
169
- const validateSystemNamespace = new RegExp(`^${global.applicationNamespace}\\.[^.]+\\..+$`);
170
- if (global.namespace === undefined && !validateSystemNamespace.test(global.namespace)) {
171
- let error = new Error(`Namespace is not defined in cdsrc.json or it is not in the format of ${global.applicationNamespace}.<appName>.<service>`);
172
- console.error('Namespace error:', error.message);
111
+ function validateNamespace(appConfig) {
112
+ const validateSystemNamespace = new RegExp(`^${appConfig.eventApplicationNamespace}\\.[^.]+\\..+$`);
113
+ if (
114
+ appConfig.ordNamespace === undefined &&
115
+ !validateSystemNamespace.test(appConfig.ordNamespace)
116
+ ) {
117
+ let error = new Error(
118
+ `Namespace is not defined in cdsrc.json or it is not in the format of ${appConfig.eventApplicationNamespace}.<appName>.<service>`
119
+ );
120
+ console.error("Namespace error:", error.message);
173
121
  throw error;
174
122
  }
123
+ }
175
124
 
176
- let oReturn = {openResourceDiscovery: "1.9",
177
- policyLevel: fGetPolicyLevel(global),
178
- description: fGetDescription(global),
179
- products: fGetProducts(global),
180
- groups: fGetGroups(linkedCsn, global),
125
+ function createDefaultORDDocument(linkedCsn, appConfig) {
126
+ let ordDocument = {
127
+ $schema: "https://sap.github.io/open-resource-discovery/spec-v1/interfaces/Document.schema.json",
128
+ openResourceDiscovery: "1.9",
129
+ policyLevel: _getPolicyLevel(appConfig),
130
+ description: _getDescription(appConfig),
131
+ products: _getProducts(appConfig),
132
+ groups: _getGroups(linkedCsn, appConfig),
181
133
  };
182
- if(fGetAPIResources(linkedCsn, global).length > 0 && (fGetEventResources(linkedCsn, global).length>0)){
183
- oReturn.packages = fGetPackages(oReturn.policyLevel, global);
184
- }
185
134
 
186
- // check if oReturn.packages is defined and extract the ordId from the packages and store in a set
187
- let packageIds = new Set();
188
- if(oReturn.packages){
189
- oReturn.packages.forEach((pkg) => {
190
- packageIds.add(pkg.ordId);
191
- });
135
+ if (_getAPIResources(linkedCsn, appConfig).length && _getEventResources(linkedCsn, appConfig).length) {
136
+ ordDocument.packages = _getPackages(ordDocument.policyLevel, appConfig);
192
137
  }
193
- oReturn = {
194
- ...oReturn,
195
- consumptionBundles: fGetConsumptionBundles(global),
196
- apiResources: fGetAPIResources(linkedCsn, global,packageIds),
197
- };
198
- if(fGetEventResources(linkedCsn, global,packageIds).length > 0) {
199
- oReturn.eventResources= fGetEventResources(linkedCsn, global,packageIds);
138
+ return ordDocument;
139
+ }
140
+
141
+ function extractPackageIds(ordDocument) {
142
+ const packageIds = new Set();
143
+ if (ordDocument.packages) {
144
+ ordDocument.packages.map((pkg) => packageIds.add(pkg.ordId));
200
145
  }
201
- return oReturn;
146
+ return packageIds;
202
147
  }
148
+
149
+ module.exports = (csn) => {
150
+ const linkedCsn = cds.linked(csn);
151
+ const appConfig = initializeAppConfig(linkedCsn);
152
+ validateNamespace(appConfig);
153
+
154
+ let ordDocument = createDefaultORDDocument(linkedCsn, appConfig);
155
+ const packageIds = extractPackageIds(ordDocument);
156
+ // TODO: add testcase without apiResources or event, no empty package
157
+ ordDocument.apiResources = _getAPIResources(linkedCsn, appConfig, packageIds);
158
+ const eventResources = _getEventResources(linkedCsn, appConfig, packageIds);
159
+ if (eventResources.length) {
160
+ ordDocument.eventResources = eventResources;
161
+ }
162
+
163
+ ordDocument = extendCustomORDContentIfExists(appConfig, ordDocument, logger);
164
+
165
+ return ordDocument;
166
+ };
package/lib/templates.js CHANGED
@@ -1,25 +1,26 @@
1
1
  const defaults = require("./defaults");
2
2
  const cds = require("@sap/cds");
3
- const fReplaceSpecialCharacters = (namespace) => {
4
- return namespace.replace(/customer\.(.+)/, (match, group1) => 'customer.' + group1.replace(/[^a-zA-Z0-9]/g, ''))
3
+ const _ = require("lodash");
4
+ const { ORD_EXTENSIONS_PREFIX } = require("./constants");
5
+
6
+ function unflatten(flattedObject) {
7
+ let result = {}
8
+ _.keys(flattedObject).forEach(function (key) {
9
+ _.set(result, key, flattedObject[key])
10
+ })
11
+ return result
5
12
  }
6
13
 
7
- /**
8
- * Reads the ORD (Open Resource Discovery) annotation from a given service definition object and returns them as an object.
9
- *
10
- * @param {Object} srv The service definition object.
11
- * @returns {Object} An object containing ORD annotation.
12
- */
13
- const fReadORDExtensions = (srv) =>
14
- Object.entries(srv)
15
- .filter(([key]) => key.startsWith("@ORD.Extensions."))
16
- .reduce(
17
- (ordExtensions, [key, value]) => ({
18
- ...ordExtensions,
19
- [key.slice("@ORD.Extensions.".length)]: value,
20
- }),
21
- {}
22
- );
14
+ function readORDExtensions(model) {
15
+ const ordExtensions = {};
16
+ for (const key in model) {
17
+ if (key.startsWith(ORD_EXTENSIONS_PREFIX)) {
18
+ const ordKey = key.replace(ORD_EXTENSIONS_PREFIX, "");
19
+ ordExtensions[ordKey] = model[key];
20
+ }
21
+ }
22
+ return unflatten(ordExtensions);
23
+ }
23
24
 
24
25
  /**
25
26
  * Reads the service definition and returns an array of entryPoint paths.
@@ -29,28 +30,28 @@ const fReadORDExtensions = (srv) =>
29
30
  * @returns {Array} An array containing paths and it's kind.
30
31
  */
31
32
 
32
- const fGeneratePaths = (srv, srvDefinition) => {
33
- const srvObj = { name: srv, definition: srvDefinition };
34
- const protocols = cds.service.protocols;
33
+ const _generatePaths = (srv, srvDefinition) => {
34
+ const srvObj = { name: srv, definition: srvDefinition };
35
+ const protocols = cds.service.protocols;
35
36
 
36
- const paths = protocols.endpoints4(srvObj);
37
+ const paths = protocols.endpoints4(srvObj);
37
38
 
38
- //TODO: check graphql replication in paths object and re-visit logic
39
- //removing instances of graphql protocol from paths
40
- for (var index = paths.length - 1; index >= 0; index--) {
41
- if (paths[index].kind === "graphql") {
42
- console.warn("Graphql protocol is not supported.");
43
- paths.splice(index, 1);
39
+ //TODO: check graphql replication in paths object and re-visit logic
40
+ //removing instances of graphql protocol from paths
41
+ for (var index = paths.length - 1; index >= 0; index--) {
42
+ if (paths[index].kind === "graphql") {
43
+ console.warn("Graphql protocol is not supported.");
44
+ paths.splice(index, 1);
45
+ }
44
46
  }
45
- }
46
47
 
47
- //putting default as odata in case no supported protcol is there
48
- if (paths.length === 0) {
49
- srvDefinition["@odata"] = true;
50
- paths.push({ kind: "odata", path: protocols.path4(srvDefinition) });
51
- }
48
+ //putting default as odata in case no supported protcol is there
49
+ if (paths.length === 0) {
50
+ srvDefinition["@odata"] = true;
51
+ paths.push({ kind: "odata", path: protocols.path4(srvDefinition) });
52
+ }
52
53
 
53
- return paths;
54
+ return paths;
54
55
  };
55
56
 
56
57
  /**
@@ -59,30 +60,21 @@ const fGeneratePaths = (srv, srvDefinition) => {
59
60
  * @param {string} entity The name of the entity.
60
61
  * @returns {Object} An object for the entity type.
61
62
  */
62
- const fCreateEntityTypeTemplate = (entity) => ({
63
- ordId: `sap.odm:entityType:${entity["@ODM.entityName"]}:v1`,
63
+ const createEntityTypeTemplate = (entity) => ({
64
+ ordId: `sap.odm:entityType:${entity["@ODM.entityName"]}:v1`,
64
65
  });
65
66
 
66
- /**
67
- * This is a template function to form the group id.
68
- *
69
- * @param {string} fullyQualifiedName The fully qualified name of the service or event.
70
- * @param {string} groupTypeId The group type id.
71
- * @returns {string} a group id.
72
- */
73
- function _getGroupID(fullyQualifiedName, groupTypeId = defaults.groupTypeId, isService = true) {
74
- if (isService) {
75
- return `${groupTypeId}:${fReplaceSpecialCharacters(global.namespace)}:${fullyQualifiedName}`;
76
- } else {
77
- return `${groupTypeId}:${fReplaceSpecialCharacters(global.namespace)}:` +
78
- `${fullyQualifiedName.slice(0, fullyQualifiedName.lastIndexOf("."))}`;
79
- }
67
+ function _getGroupID(
68
+ fullyQualifiedName,
69
+ groupTypeId = defaults.groupTypeId,
70
+ appConfig,
71
+ ) {
72
+ return `${groupTypeId}:${appConfig.ordNamespace}:${fullyQualifiedName}`;
80
73
  }
81
74
 
82
-
83
75
  /**
84
76
  * This is a function to resolve the title of the service group.
85
- *
77
+ *
86
78
  * @param {string} srv The name of the service.
87
79
  * @returns {string} The title of the service group.
88
80
  */
@@ -90,265 +82,146 @@ function _getTitleFromServiceName(srv) {
90
82
  let serviceName = srv.substring(srv.lastIndexOf(".") + 1);
91
83
  let index = serviceName.indexOf("Service");
92
84
  if (index >= 0) {
93
- return `${serviceName.substring(0, index)} Service Title`;
85
+ return `${serviceName.substring(0, index)} Service`;
94
86
  } else {
95
- return `${serviceName} Service Title`;
87
+ return `${serviceName} Service`;
96
88
  }
97
89
  }
98
90
 
99
-
100
91
  /**
101
92
  * This is a template function to create group object of a service for groups array in ORD doc.
102
- *
103
- * @param {string} srv The name of the service.
104
- * @param {object} srvDefinition The definition of the service
93
+ *
94
+ * @param {string} serviceName The name of the service.
95
+ * @param {object} serviceDefinition The definition of the service
105
96
  * @param {Set} groupIds A set of group ids.
106
97
  * @returns {Object} A group object.
107
98
  */
108
- const fCreateGroupsTemplateForService = (srv, srvDefinition, groupIds) => {
109
- const ordExtensions = fReadORDExtensions(srvDefinition);
99
+ const createGroupsTemplateForService = (serviceName, serviceDefinition, appConfig) => {
100
+ const ordExtensions = readORDExtensions(serviceDefinition);
110
101
 
111
- let fullyQualifiedServiceName = srv;
112
- if(!srv.includes(global.capNamespace)){
113
- fullyQualifiedServiceName = global.capNamespace + "." + srv;
102
+ if (!serviceDefinition) {
103
+ console.warn("Unable to find service definition:", serviceName)
104
+ return undefined
114
105
  }
115
106
 
116
- if (checkEntityFunctionAction(srvDefinition, global).length > 0) {
117
- let groupId = _getGroupID(fullyQualifiedServiceName, defaults.groupTypeId);
118
- if (groupIds.has(groupId)) {
119
- return null;
120
- } else {
121
- groupIds.add(groupId);
122
- return {
123
- groupId: groupId,
124
- groupTypeId: `${defaults.groupTypeId}`,
125
- title: ordExtensions.title ?? _getTitleFromServiceName(srv)
126
- };
127
- }
128
- }
129
- }
130
-
131
- /**
132
- *
133
- * @param {string} event The name of the event.
134
- * @returns {string} The title of the event group.
135
- */
136
- function _getEventTitle(event) {
137
- let serviceName = event.substring(0, event.lastIndexOf("."));
138
- return _getTitleFromServiceName(serviceName);
139
- }
140
-
141
- /**
142
- * This is a template function to create group object of an event for groups array in ORD doc.
143
- *
144
- * @param {string} event The name of the event.
145
- * @param {object} eventDefinition The definition of the event.
146
- * @param {Set} groupIds A set of group ids.
147
- * @returns {Object} A group object.
148
- */
149
- const fCreateGroupsTemplateForEvent = (event, eventDefinition, groupIds) => {
150
- const ordExtensions = fReadORDExtensions(eventDefinition);
151
-
152
- let fullyQualifiedEventName = event;
153
- if(!event.includes(global.capNamespace)){
154
- fullyQualifiedEventName = global.capNamespace + "." + event;
155
- }
156
-
157
- let groupId = _getGroupID(fullyQualifiedEventName, defaults.groupTypeId, global, false);
158
- if (groupIds.has(groupId)) {
159
- return null;
160
- } else {
161
- groupIds.add(groupId);
162
- return {
163
- groupId: groupId,
164
- groupTypeId: `${defaults.groupTypeId}`,
165
- title: ordExtensions.title ?? _getEventTitle(event)
166
- };
167
- }
107
+ const groupId = _getGroupID(serviceName, defaults.groupTypeId, appConfig);
108
+ return {
109
+ groupId: groupId,
110
+ groupTypeId: defaults.groupTypeId,
111
+ title: ordExtensions.title ?? _getTitleFromServiceName(serviceName)
112
+ };
168
113
  }
169
114
 
170
115
  /**
171
116
  * This is a template function to create API Resource object for API Resource Array.
172
117
  *
173
- * @param {string} srv The name of the service.
174
- * @param {object} srvDefinition The definition of the service
118
+ * @param {string} serviceName The name of the service.
119
+ * @param {object} serviceDefinition The definition of the service
175
120
  * @returns {Array} An array of objects for the API Resources.
176
121
  */
177
- const fCreateAPIResourceTemplate = (srv, srvDefinition, global,packageIds) => {
178
- const ordExtensions = fReadORDExtensions(srvDefinition);
179
- const paths = fGeneratePaths(srv, srvDefinition);
180
- const apiResources = [];
122
+ const createAPIResourceTemplate = (serviceName, serviceDefinition, appConfig, packageIds) => {
123
+ const ordExtensions = readORDExtensions(serviceDefinition);
124
+ const paths = _generatePaths(serviceName, serviceDefinition);
125
+ const apiResources = [];
181
126
 
182
- let srvName = srv;
183
- if (!srvName.includes(global.capNamespace)) {
184
- srvName = global.capNamespace + "." + srv;
185
- }
186
-
187
- if (checkEntityFunctionAction(srvDefinition, global).length > 0) {
188
127
  paths.forEach((generatedPath) => {
189
- let resourceDefinitions = [
190
- {
191
- type: "openapi-v3",
192
- mediaType: "application/json",
193
- url: `/.well-known/open-resource-discovery/v1/api-metadata/${srv}.oas3.json`,
194
- accessStrategies: [{ type: "open" }],
195
- },
196
- ];
128
+ let resourceDefinitions = [
129
+ {
130
+ type: "openapi-v3",
131
+ mediaType: "application/json",
132
+ url: `/.well-known/open-resource-discovery/v1/api-metadata/${serviceName}.oas3.json`,
133
+ accessStrategies: [{ type: "open" }],
134
+ },
135
+ ];
136
+
137
+ if (generatedPath.kind !== "rest") {
138
+ //edmx resource definition is not generated in case of 'rest' protocol
139
+ resourceDefinitions.push({
140
+ type: "edmx",
141
+ mediaType: "application/xml",
142
+ url: `/.well-known/open-resource-discovery/v1/api-metadata/${serviceName}.edmx`,
143
+ accessStrategies: [{ type: "open" }],
144
+ });
145
+ }
146
+
147
+ let obj = {
148
+ ordId: `${appConfig.ordNamespace}:apiResource:${serviceName}:v1`,
149
+ title:
150
+ serviceDefinition["@title"] ??
151
+ serviceDefinition["@Common.Label"] ??
152
+ serviceName,
153
+ shortDescription: serviceName,
154
+ description:
155
+ serviceDefinition["@Core.Description"] ??
156
+ serviceName,
157
+ version: "1.0.0",
158
+ visibility: "public",
159
+ partOfPackage: _getPackageID(appConfig.ordNamespace, packageIds, "api"),
160
+ partOfGroups: [_getGroupID(serviceName, defaults.groupTypeId, appConfig)],
161
+ releaseStatus: "active",
162
+ apiProtocol:
163
+ generatedPath.kind === "odata" ? "odata-v4" : generatedPath.kind,
164
+ resourceDefinitions: resourceDefinitions,
165
+ entryPoints: [generatedPath.path],
166
+ extensible: {
167
+ supported: "no",
168
+ },
169
+ entityTypeMappings: [{ entityTypeTargets: appConfig.odmEntity }],
170
+
171
+ ...ordExtensions,
172
+ };
173
+
174
+ apiResources.push(obj);
175
+ });
197
176
 
198
- if (generatedPath.kind !== "rest") {
199
- //edmx resource definition is not generated in case of 'rest' protocol
200
- resourceDefinitions.push({
201
- type: "edmx",
202
- mediaType: "application/xml",
203
- url: `/.well-known/open-resource-discovery/v1/api-metadata/${srv}.edmx`,
204
- accessStrategies: [{ type: "open" }],
205
- });
206
- }
177
+ if (apiResources.length > 0) return apiResources;
178
+ };
207
179
 
208
- let obj = {
209
- ordId: `${fReplaceSpecialCharacters(global.namespace)}:apiResource:${srvName}:v1`,
180
+ const createEventResourceTemplate = (serviceName, eventDefinition, appConfig, packageIds) => {
181
+ const ordExtensions = readORDExtensions(eventDefinition);
182
+ return {
183
+ ordId: `${appConfig.ordNamespace}:eventResource:${serviceName}:v1`,
210
184
  title:
211
- ordExtensions.title ??
212
- srvDefinition["@title"] ??
213
- srvDefinition["@Common.Label"] ??
214
- `The service is for ${srv}`,
215
- shortDescription:
216
- ordExtensions.shortDescription ??
217
- `Here we have the shortDescription for ${srv}`,
185
+ eventDefinition["@title"] ??
186
+ eventDefinition["@Common.Label"] ??
187
+ `ODM ${appConfig.appName.replace(/[^a-zA-Z0-9]/g, "")} Events`,
188
+ shortDescription: `${serviceName} event resource`,
218
189
  description:
219
- ordExtensions.description ??
220
- srvDefinition["@Core.Description"] ??
221
- `Here we have the description for ${srv}`,
222
- version: ordExtensions.version ?? "1.0.0",
223
- visibility: ordExtensions.visibility ?? "public",
224
- partOfPackage: _getPackageID(global.capNamespace,packageIds,'api'),
225
- partOfGroups: [_getGroupID(srvName, defaults.groupTypeId, global)],
226
- releaseStatus: ordExtensions.active ?? "active",
227
- partOfConsumptionBundles: [
228
- {
229
- ordId: `${fReplaceSpecialCharacters(global.namespace)}:consumptionBundle:noAuth:v1`,
230
- },
190
+ eventDefinition['@description'] ?? eventDefinition['@Core.Description'] ??
191
+ "CAP Event resource describing events / messages.",
192
+ version: "1.0.0",
193
+ releaseStatus: "active",
194
+ partOfPackage: _getPackageID(appConfig.ordNamespace, packageIds, 'event'),
195
+ partOfGroups: [_getGroupID(serviceName, defaults.groupTypeId, appConfig)],
196
+ visibility: "public",
197
+ resourceDefinitions: [
198
+ {
199
+ type: "asyncapi-v2",
200
+ mediaType: "application/json",
201
+ url: `/.well-known/open-resource-discovery/v1/api-metadata/${serviceName}.asyncapi2.json`,
202
+ accessStrategies: [
203
+ {
204
+ type: "open",
205
+ },
206
+ ],
207
+ },
231
208
  ],
232
- apiProtocol:
233
- generatedPath.kind === "odata" ? "odata-v4" : generatedPath.kind,
234
- resourceDefinitions: resourceDefinitions,
235
- entryPoints: [generatedPath.path],
236
- extensible: {
237
- supported: ordExtensions["extensible.supported"] ?? "no",
238
- },
239
- entityTypeMappings: [{ entityTypeTargets: global.aODMEntity }],
240
- };
209
+ extensible: { supported: "no" },
241
210
 
242
- apiResources.push(obj);
243
- });
244
- }
245
- if (apiResources.length > 0) return apiResources;
246
- };
247
-
248
- /**
249
- * This is a template function to create Event Resource object for Event Resource Array.
250
- *
251
- * @param {string} srv The name of the event.
252
- * @param {object} srvDefinition The definition of the event.
253
- * @returns {Object} An object for the Event Resource.
254
- */
255
- const fCreateEventResourceTemplate = (srv, srvDefinition, global,packageIds) => {
256
- const ordExtensions = fReadORDExtensions(srvDefinition);
257
- let srvName = srv;
258
- if(!srvName.includes(global.capNamespace)){
259
- srvName = global.capNamespace + "." + srv;
260
- }
261
- return {
262
- ordId: `${fReplaceSpecialCharacters(global.namespace)}:eventResource:${srvName}:v1`,
263
- title: ordExtensions.title ?? srvDefinition['@title'] ?? srvDefinition['@Common.Label'] ?? `ODM ${global.appName.replace(/[^a-zA-Z0-9]/g, '')} Events`,
264
- shortDescription: ordExtensions.shortDescription ?? "Example ODM Event",
265
- description: ordExtensions.description ??
266
- srvDefinition['@description'] ?? srvDefinition['@Core.Description'] ??
267
- `This is an example event catalog that contains only a partial ODM ${global.appName} V1 event`,
268
- version: ordExtensions.version ?? "1.0.0",
269
- releaseStatus: ordExtensions.releaseStatus ?? "beta",
270
- partOfPackage: _getPackageID(global.capNamespace,packageIds,'event'),
271
- partOfGroups: [_getGroupID(srvName, defaults.groupTypeId, global, false)],
272
- visibility: ordExtensions.visibility ?? "public",
273
- resourceDefinitions: [
274
- {
275
- type: "asyncapi-v2",
276
- mediaType: "application/json",
277
- url: `/.well-known/open-resource-discovery/v1/api-metadata/${srvDefinition._service.name}.asyncapi2.json`,
278
- accessStrategies: [
279
- {
280
- type: "open",
281
- },
282
- ],
283
- },
284
- ],
285
- extensible: { supported: ordExtensions["extensible.supported"] ?? "no" },
286
- };
211
+ ...ordExtensions
212
+ };
287
213
  };
288
214
 
289
-
290
- /**
291
- * This is a function to check whether entity is a function or action.
292
- * @param {object} srvDefinition The definition of the event.
293
- * @returns {Object} An object for the Event Resource.
294
- */
295
- function checkEntityFunctionAction(srvDefinition, global) {
296
- if (srvDefinition.entities) {
297
- return srvDefinition.entities.map((entity) => {
298
- return {
299
- type: "entity",
300
- name: entity.name,
301
- entityType: entity.name,
302
- entitySet: entity.name,
303
- entityTypeMapping: `${global.namespace}:entityType:${entity.name}:v1`,
304
- entitySetMapping: `${global.namespace}:entitySet:${entity.name}:v1`,
305
- };
306
- });
307
- } else if (srvDefinition.actions) {
308
- return srvDefinition.actions.map((action) => {
309
- return {
310
- type: "action",
311
- name: action.name,
312
- actionType: action.name,
313
- actionMapping: `${global.namespace}:action:${action.name}:v1`,
314
- };
315
- });
316
- } else if (srvDefinition.functions) {
317
- return srvDefinition.functions.map((func) => {
318
- return {
319
- type: "function",
320
- name: func.name,
321
- functionType: func.name,
322
- functionMapping: `${global.namespace}:function:${func.name}:v1`,
323
- };
324
- });
325
- }
326
- }
327
-
328
- /**
329
- * This is a function to get the corresponding package ordId mapped to the service.
330
- */
331
-
332
- function _getPackageID(capNamespace, packageIds, apiOrEvent) {
333
- if (packageIds instanceof Set) {
334
- const packageArray = Array.from(packageIds);
335
-
336
- if (apiOrEvent === "api") {
337
- const apiPackage = packageArray.find(pkg => pkg.includes("-api"));
338
- if (apiPackage) return apiPackage;
339
- } else if (apiOrEvent === "event") {
340
- const eventPackage = packageArray.find(pkg => pkg.includes("-event"));
341
- if (eventPackage) return eventPackage;
342
- }
343
-
344
- return packageArray.find(pkg => pkg.includes(capNamespace));
345
- }
215
+ function _getPackageID(namespace, packageIds, resourceType) {
216
+ //TODO: without this check, it will be failed in ordPackageJson.test.js, need to check why it is failing
217
+ if (!(packageIds instanceof Set)) return;
218
+ const packageIdsArray = Array.from(packageIds);
219
+ return packageIdsArray.find((pkg) => pkg.includes("-" + resourceType)) || packageIdsArray.find((pkg) => pkg.includes(namespace));
346
220
  }
347
221
 
348
222
  module.exports = {
349
- fCreateEntityTypeTemplate,
350
- fCreateGroupsTemplateForService,
351
- fCreateGroupsTemplateForEvent,
352
- fCreateAPIResourceTemplate,
353
- fCreateEventResourceTemplate,
223
+ createEntityTypeTemplate,
224
+ createGroupsTemplateForService,
225
+ createAPIResourceTemplate,
226
+ createEventResourceTemplate,
354
227
  };
package/package.json CHANGED
@@ -1,27 +1,34 @@
1
1
  {
2
- "name": "@cap-js/ord",
3
- "version": "1.0.3",
4
- "description": "CAP Plugin for generating ORD document.",
5
- "repository": "cap-js/ord",
6
- "author": "SAP SE (https://www.sap.com)",
7
- "homepage": "https://cap.cloud.sap/",
8
- "license": "SEE LICENSE IN LICENSE",
9
- "main": "cds-plugin.js",
10
- "files": [
11
- "lib",
12
- "data",
13
- "LICENSES"
14
- ],
15
- "scripts": {
16
- "lint": "npx eslint ."
17
- },
18
- "devDependencies": {
19
- "eslint": "^8"
20
- },
21
- "peerDependencies": {
22
- "@sap/cds": "7.9.4",
23
- "@sap/cds-compiler" : "5.0.6",
24
- "@cap-js/asyncapi": "^1.0.0",
25
- "@cap-js/openapi": "^1.0.2"
26
- }
27
- }
2
+ "name": "@cap-js/ord",
3
+ "version": "1.1.0",
4
+ "description": "CAP Plugin for generating ORD document.",
5
+ "repository": "cap-js/ord",
6
+ "author": "SAP SE (https://www.sap.com)",
7
+ "homepage": "https://cap.cloud.sap/",
8
+ "license": "SEE LICENSE IN LICENSE",
9
+ "main": "cds-plugin.js",
10
+ "files": [
11
+ "lib",
12
+ "data",
13
+ "LICENSES"
14
+ ],
15
+ "scripts": {
16
+ "lint": "npx eslint .",
17
+ "test": "jest --ci --collectCoverage",
18
+ "update-snapshot": "jest --ci --updateSnapshot",
19
+ "cds:version": "cds v -i"
20
+ },
21
+ "devDependencies": {
22
+ "eslint": "^8",
23
+ "jest": "^29.7.0"
24
+ },
25
+ "peerDependencies": {
26
+ "@cap-js/asyncapi": "^1.0.0",
27
+ "@cap-js/openapi": "^1.0.2",
28
+ "@sap/cds": "^8.1.1",
29
+ "@sap/cds-compiler": "5.0.6"
30
+ },
31
+ "dependencies": {
32
+ "lodash": "^4.17.21"
33
+ }
34
+ }