@cap-js/ord 1.2.0 → 1.3.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/LICENSE +1 -1
- package/README.md +98 -8
- package/cds-plugin.js +17 -8
- package/data/well-known.json +2 -2
- package/lib/authentication.js +153 -0
- package/lib/build.js +53 -0
- package/lib/constants.js +80 -4
- package/lib/date.js +11 -16
- package/lib/defaults.js +68 -44
- package/lib/es-module.mjs +6 -0
- package/lib/extendOrdWithCustom.js +12 -6
- package/lib/index.js +5 -3
- package/lib/logger.js +9 -3
- package/lib/metaData.js +28 -14
- package/lib/ord-service.cds +3 -0
- package/lib/ord-service.mjs +32 -0
- package/lib/ord.js +233 -93
- package/lib/templates.js +278 -71
- package/package.json +16 -7
- package/lib/plugin.js +0 -33
package/lib/templates.js
CHANGED
|
@@ -2,20 +2,29 @@ const cds = require("@sap/cds");
|
|
|
2
2
|
const defaults = require("./defaults");
|
|
3
3
|
const _ = require("lodash");
|
|
4
4
|
const {
|
|
5
|
+
AUTHENTICATION_TYPE,
|
|
6
|
+
DATA_PRODUCT_ANNOTATION,
|
|
7
|
+
DATA_PRODUCT_TYPE,
|
|
5
8
|
DESCRIPTION_PREFIX,
|
|
9
|
+
ENTITY_RELATIONSHIP_ANNOTATION,
|
|
10
|
+
LEVEL,
|
|
6
11
|
ORD_EXTENSIONS_PREFIX,
|
|
12
|
+
ORD_ODM_ENTITY_NAME_ANNOTATION,
|
|
7
13
|
ORD_RESOURCE_TYPE,
|
|
8
14
|
RESOURCE_VISIBILITY,
|
|
9
|
-
|
|
15
|
+
SEM_VERSION_REGEX,
|
|
16
|
+
SHORT_DESCRIPTION_PREFIX,
|
|
17
|
+
CONTENT_MERGE_KEY,
|
|
18
|
+
CDS_ELEMENT_KIND,
|
|
10
19
|
} = require("./constants");
|
|
11
20
|
const { Logger } = require("./logger");
|
|
12
21
|
|
|
13
22
|
function unflatten(flattedObject) {
|
|
14
|
-
let result = {}
|
|
23
|
+
let result = {};
|
|
15
24
|
_.keys(flattedObject).forEach(function (key) {
|
|
16
|
-
_.set(result, key, flattedObject[key])
|
|
17
|
-
})
|
|
18
|
-
return result
|
|
25
|
+
_.set(result, key, flattedObject[key]);
|
|
26
|
+
});
|
|
27
|
+
return result;
|
|
19
28
|
}
|
|
20
29
|
|
|
21
30
|
function readORDExtensions(model) {
|
|
@@ -36,7 +45,6 @@ function readORDExtensions(model) {
|
|
|
36
45
|
* @param {Object} srvDefinition The service definition object.
|
|
37
46
|
* @returns {Array} An array containing paths and it's kind.
|
|
38
47
|
*/
|
|
39
|
-
|
|
40
48
|
const _generatePaths = (srv, srvDefinition) => {
|
|
41
49
|
const srvObj = { name: srv, definition: srvDefinition };
|
|
42
50
|
const protocols = cds.service.protocols;
|
|
@@ -47,7 +55,7 @@ const _generatePaths = (srv, srvDefinition) => {
|
|
|
47
55
|
//removing instances of graphql protocol from paths
|
|
48
56
|
for (var index = paths.length - 1; index >= 0; index--) {
|
|
49
57
|
if (paths[index].kind === "graphql") {
|
|
50
|
-
Logger.warn(
|
|
58
|
+
Logger.warn("Graphql protocol is not supported.");
|
|
51
59
|
paths.splice(index, 1);
|
|
52
60
|
}
|
|
53
61
|
}
|
|
@@ -62,20 +70,32 @@ const _generatePaths = (srv, srvDefinition) => {
|
|
|
62
70
|
};
|
|
63
71
|
|
|
64
72
|
/**
|
|
65
|
-
* This is a template function to create
|
|
73
|
+
* This is a template function to create item of entityTypeMappings array.
|
|
66
74
|
*
|
|
67
|
-
* @param {string} entity The
|
|
68
|
-
* @returns {Object} An
|
|
75
|
+
* @param {string} entity The entity definition.
|
|
76
|
+
* @returns {Object} An entry of the entityTypeMappings array.
|
|
69
77
|
*/
|
|
70
|
-
const
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
)
|
|
78
|
+
const createEntityTypeMappingsItemTemplate = (entity) => {
|
|
79
|
+
if (entity[ORD_ODM_ENTITY_NAME_ANNOTATION]) {
|
|
80
|
+
return {
|
|
81
|
+
ordId: `sap.odm:entityType:${entity[ORD_ODM_ENTITY_NAME_ANNOTATION]}:v1`,
|
|
82
|
+
entityName: entity[ORD_ODM_ENTITY_NAME_ANNOTATION],
|
|
83
|
+
...entity,
|
|
84
|
+
};
|
|
85
|
+
} else if (entity[ENTITY_RELATIONSHIP_ANNOTATION]) {
|
|
86
|
+
const ordIdParts = entity[ENTITY_RELATIONSHIP_ANNOTATION].split(":");
|
|
87
|
+
const namespace = ordIdParts[0];
|
|
88
|
+
const entityName = ordIdParts[1];
|
|
89
|
+
const version = ordIdParts[2] || "v1";
|
|
90
|
+
return {
|
|
91
|
+
ordId: `${namespace}:entityType:${entityName}:${version}`,
|
|
92
|
+
entityName,
|
|
93
|
+
...entity,
|
|
94
|
+
};
|
|
95
|
+
}
|
|
96
|
+
};
|
|
97
|
+
|
|
98
|
+
function _getGroupID(fullyQualifiedName, groupTypeId = defaults.groupTypeId, appConfig) {
|
|
79
99
|
return `${groupTypeId}:${appConfig.ordNamespace}:${fullyQualifiedName}`;
|
|
80
100
|
}
|
|
81
101
|
|
|
@@ -95,6 +115,49 @@ function _getTitleFromServiceName(srv) {
|
|
|
95
115
|
}
|
|
96
116
|
}
|
|
97
117
|
|
|
118
|
+
/**
|
|
119
|
+
* This is a function to get the version of the entity,
|
|
120
|
+
* validate it and log if it is not a valid semantical version.
|
|
121
|
+
*
|
|
122
|
+
* @param {object} entity An entity object.
|
|
123
|
+
* @returns Version of the entity with '1.0.0' as fallback value.
|
|
124
|
+
*/
|
|
125
|
+
function _getEntityVersion(entity) {
|
|
126
|
+
const entityVersion = entity.ordId.split(":").pop();
|
|
127
|
+
const version = entityVersion.replace("v", "") + ".0.0"; // TODO: version can be stated/overwritten by annotation
|
|
128
|
+
if (!SEM_VERSION_REGEX.test(version)) {
|
|
129
|
+
Logger.warn(`Entity version "${version}" is not a valid semantic version.`);
|
|
130
|
+
}
|
|
131
|
+
return version;
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
/**
|
|
135
|
+
* This is a function to create a resource definition object.
|
|
136
|
+
* @param {string} resourceType The type of the resource.
|
|
137
|
+
* @param {string} mediaType The media type of the resource.
|
|
138
|
+
* @param {string} ordId The ordId of the resource.
|
|
139
|
+
* @param {string} serviceName The name of the service.
|
|
140
|
+
* @param {string} fileExtension The file extension of the resource.
|
|
141
|
+
* @param {Array} accessStrategies The array of accessStrategies objects
|
|
142
|
+
* @returns {Object} A resource definition object.
|
|
143
|
+
* @private
|
|
144
|
+
*/
|
|
145
|
+
function _getResourceDefinition(
|
|
146
|
+
resourceType,
|
|
147
|
+
mediaType,
|
|
148
|
+
ordId,
|
|
149
|
+
serviceName,
|
|
150
|
+
fileExtension,
|
|
151
|
+
accessStrategies = [{ type: AUTHENTICATION_TYPE.Open }],
|
|
152
|
+
) {
|
|
153
|
+
return {
|
|
154
|
+
type: resourceType,
|
|
155
|
+
mediaType: `application/${mediaType}`,
|
|
156
|
+
url: `/ord/v1/${ordId}/${serviceName}.${fileExtension}`,
|
|
157
|
+
accessStrategies,
|
|
158
|
+
};
|
|
159
|
+
}
|
|
160
|
+
|
|
98
161
|
/**
|
|
99
162
|
* This is a template function to create group object of a service for groups array in ORD doc.
|
|
100
163
|
*
|
|
@@ -107,79 +170,144 @@ const createGroupsTemplateForService = (serviceName, serviceDefinition, appConfi
|
|
|
107
170
|
const ordExtensions = readORDExtensions(serviceDefinition);
|
|
108
171
|
|
|
109
172
|
if (!serviceDefinition) {
|
|
110
|
-
Logger.warn(
|
|
111
|
-
return undefined
|
|
173
|
+
Logger.warn("Unable to find service definition:", serviceName);
|
|
174
|
+
return undefined;
|
|
112
175
|
}
|
|
113
176
|
|
|
114
177
|
const groupId = _getGroupID(serviceName, defaults.groupTypeId, appConfig);
|
|
115
178
|
return {
|
|
116
179
|
groupId: groupId,
|
|
117
180
|
groupTypeId: defaults.groupTypeId,
|
|
118
|
-
title: ordExtensions.title ?? _getTitleFromServiceName(serviceName)
|
|
181
|
+
title: ordExtensions.title ?? _getTitleFromServiceName(serviceName),
|
|
182
|
+
};
|
|
183
|
+
};
|
|
184
|
+
|
|
185
|
+
/**
|
|
186
|
+
* This is a template function to create EntityType object for EntityTypes Array.
|
|
187
|
+
* Ensures correct visibility assignment based on referenced resources.
|
|
188
|
+
*
|
|
189
|
+
* @param { object } appConfig The configuration object.
|
|
190
|
+
* @param { Array } packageIds The available package identifiers.
|
|
191
|
+
* @param { object } entity The entity definition.
|
|
192
|
+
* @returns { object } An object for the EntityType.
|
|
193
|
+
*/
|
|
194
|
+
const createEntityTypeTemplate = (appConfig, packageIds, entity) => {
|
|
195
|
+
const ordExtensions = readORDExtensions(entity);
|
|
196
|
+
const visibility = ordExtensions.visibility || RESOURCE_VISIBILITY.public;
|
|
197
|
+
|
|
198
|
+
if (visibility === RESOURCE_VISIBILITY.private) {
|
|
199
|
+
return null;
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
const packageId = _getPackageID(appConfig.ordNamespace, packageIds, ORD_RESOURCE_TYPE.entityType, visibility);
|
|
203
|
+
|
|
204
|
+
return {
|
|
205
|
+
ordId: entity.ordId,
|
|
206
|
+
localId: entity.entityName,
|
|
207
|
+
title: entity["@title"] ?? entity["@Common.Label"] ?? entity.entityName,
|
|
208
|
+
shortDescription: SHORT_DESCRIPTION_PREFIX + entity.entityName,
|
|
209
|
+
description: DESCRIPTION_PREFIX + entity.entityName,
|
|
210
|
+
version: _getEntityVersion(entity),
|
|
211
|
+
lastUpdate: appConfig.lastUpdate,
|
|
212
|
+
visibility,
|
|
213
|
+
partOfPackage: packageId,
|
|
214
|
+
releaseStatus: "active",
|
|
215
|
+
level: entity["@ObjectModel.compositionRoot"] || entity["@ODM.root"] ? LEVEL.rootEntity : LEVEL.subEntity,
|
|
216
|
+
extensible: { supported: "no" },
|
|
217
|
+
...ordExtensions,
|
|
119
218
|
};
|
|
219
|
+
};
|
|
220
|
+
|
|
221
|
+
function _handleVisibility(ordExtensions, definition) {
|
|
222
|
+
let visibility;
|
|
223
|
+
if (ordExtensions.visibility) {
|
|
224
|
+
visibility = ordExtensions.visibility;
|
|
225
|
+
} else if (definition[ORD_EXTENSIONS_PREFIX + "visibility"]) {
|
|
226
|
+
visibility = definition[ORD_EXTENSIONS_PREFIX + "visibility"];
|
|
227
|
+
} else {
|
|
228
|
+
visibility = RESOURCE_VISIBILITY.public;
|
|
229
|
+
}
|
|
230
|
+
return visibility;
|
|
120
231
|
}
|
|
121
232
|
|
|
122
233
|
/**
|
|
123
234
|
* This is a template function to create API Resource object for API Resource Array.
|
|
124
235
|
* Properties of an API resource can be overwritten by the ORD extensions. Example: visibility.
|
|
125
|
-
|
|
236
|
+
* Ensures proper visibility compliance by checking associated EntityTypes.
|
|
126
237
|
* @param {string} serviceName The name of the service.
|
|
127
238
|
* @param {object} serviceDefinition The definition of the service
|
|
239
|
+
* @param {object} appConfig - The application configuration.
|
|
240
|
+
* @param {Array} packageIds - The available package identifiers.
|
|
241
|
+
* @param {Array} accessStrategies The array of accessStrategies objects
|
|
128
242
|
* @returns {Array} An array of objects for the API Resources.
|
|
129
243
|
*/
|
|
130
|
-
const createAPIResourceTemplate = (serviceName, serviceDefinition, appConfig, packageIds) => {
|
|
244
|
+
const createAPIResourceTemplate = (serviceName, serviceDefinition, appConfig, packageIds, accessStrategies) => {
|
|
131
245
|
const ordExtensions = readORDExtensions(serviceDefinition);
|
|
246
|
+
const visibility = _handleVisibility(ordExtensions, serviceDefinition);
|
|
247
|
+
const packageId = _getPackageID(appConfig.ordNamespace, packageIds, ORD_RESOURCE_TYPE.api, visibility);
|
|
248
|
+
|
|
132
249
|
const paths = _generatePaths(serviceName, serviceDefinition);
|
|
133
250
|
const apiResources = [];
|
|
251
|
+
const ordId = `${appConfig.ordNamespace}:apiResource:${serviceName}:v1`;
|
|
134
252
|
|
|
135
253
|
paths.forEach((generatedPath) => {
|
|
136
254
|
let resourceDefinitions = [
|
|
137
|
-
|
|
138
|
-
type: "openapi-v3",
|
|
139
|
-
mediaType: "application/json",
|
|
140
|
-
url: `/.well-known/open-resource-discovery/v1/api-metadata/${serviceName}.oas3.json`,
|
|
141
|
-
accessStrategies: [{ type: "open" }],
|
|
142
|
-
},
|
|
255
|
+
_getResourceDefinition("openapi-v3", "json", ordId, serviceName, "oas3.json", accessStrategies),
|
|
143
256
|
];
|
|
144
257
|
|
|
145
258
|
if (generatedPath.kind !== "rest") {
|
|
146
259
|
//edmx resource definition is not generated in case of 'rest' protocol
|
|
147
|
-
resourceDefinitions.push(
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
url: `/.well-known/open-resource-discovery/v1/api-metadata/${serviceName}.edmx`,
|
|
151
|
-
accessStrategies: [{ type: "open" }],
|
|
152
|
-
});
|
|
260
|
+
resourceDefinitions.push(
|
|
261
|
+
_getResourceDefinition("edmx", "xml", ordId, serviceName, "edmx", accessStrategies),
|
|
262
|
+
);
|
|
153
263
|
}
|
|
154
264
|
|
|
265
|
+
const entityTypeMappings = _getEntityTypeMappings(serviceDefinition);
|
|
266
|
+
|
|
155
267
|
let obj = {
|
|
156
|
-
ordId
|
|
157
|
-
title:
|
|
158
|
-
serviceDefinition["@title"] ??
|
|
159
|
-
serviceDefinition["@Common.Label"] ??
|
|
160
|
-
serviceName,
|
|
268
|
+
ordId,
|
|
269
|
+
title: serviceDefinition["@title"] ?? serviceDefinition["@Common.Label"] ?? serviceName,
|
|
161
270
|
shortDescription: SHORT_DESCRIPTION_PREFIX + serviceName,
|
|
162
|
-
description:
|
|
163
|
-
serviceDefinition["@Core.Description"] ??
|
|
164
|
-
DESCRIPTION_PREFIX + serviceName,
|
|
271
|
+
description: serviceDefinition["@Core.Description"] ?? DESCRIPTION_PREFIX + serviceName,
|
|
165
272
|
version: "1.0.0",
|
|
166
273
|
lastUpdate: appConfig.lastUpdate,
|
|
167
|
-
visibility
|
|
168
|
-
partOfPackage:
|
|
274
|
+
visibility,
|
|
275
|
+
partOfPackage: packageId,
|
|
169
276
|
partOfGroups: [_getGroupID(serviceName, defaults.groupTypeId, appConfig)],
|
|
170
277
|
releaseStatus: "active",
|
|
171
|
-
apiProtocol:
|
|
172
|
-
generatedPath.kind === "odata" ? "odata-v4" : generatedPath.kind,
|
|
278
|
+
apiProtocol: generatedPath.kind === "odata" ? "odata-v4" : generatedPath.kind,
|
|
173
279
|
resourceDefinitions: resourceDefinitions,
|
|
174
280
|
entryPoints: [generatedPath.path],
|
|
175
281
|
extensible: {
|
|
176
282
|
supported: "no",
|
|
177
283
|
},
|
|
178
|
-
entityTypeMappings
|
|
284
|
+
...(entityTypeMappings ? { entityTypeMappings } : {}),
|
|
179
285
|
...ordExtensions,
|
|
180
286
|
};
|
|
181
287
|
|
|
182
|
-
if (
|
|
288
|
+
if (serviceDefinition[DATA_PRODUCT_ANNOTATION] === DATA_PRODUCT_TYPE.primary) {
|
|
289
|
+
obj.apiProtocol = "rest";
|
|
290
|
+
obj.visibility = RESOURCE_VISIBILITY.internal;
|
|
291
|
+
obj.direction = "outbound";
|
|
292
|
+
obj.implementationStandard = "sap.dp:data-subscription-api:v1";
|
|
293
|
+
obj.entryPoints = [];
|
|
294
|
+
obj.resourceDefinitions = [
|
|
295
|
+
_getResourceDefinition(
|
|
296
|
+
"sap-csn-interop-effective-v1",
|
|
297
|
+
"json",
|
|
298
|
+
ordId,
|
|
299
|
+
serviceName,
|
|
300
|
+
"csn.json",
|
|
301
|
+
accessStrategies,
|
|
302
|
+
),
|
|
303
|
+
];
|
|
304
|
+
|
|
305
|
+
apiResources.push(obj);
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
if (obj.visibility !== RESOURCE_VISIBILITY.private) {
|
|
309
|
+
apiResources.push(obj);
|
|
310
|
+
}
|
|
183
311
|
});
|
|
184
312
|
|
|
185
313
|
return apiResources;
|
|
@@ -189,57 +317,136 @@ const createAPIResourceTemplate = (serviceName, serviceDefinition, appConfig, pa
|
|
|
189
317
|
* This is a template function to create Event Resource object for Event Resource Array.
|
|
190
318
|
* There can be only one event resource per service because all events are using the same protocol, they are always Cloud Events.
|
|
191
319
|
* Properties of an event resource can be overwritten by the ORD extensions. Example: visibility.
|
|
320
|
+
* Ensures proper visibility compliance by checking associated EntityTypes.
|
|
192
321
|
*
|
|
193
322
|
* @param {string} serviceName The name of the service.
|
|
194
323
|
* @param {object} serviceDefinition The definition of the service
|
|
324
|
+
* @param {object} appConfig - The application configuration.
|
|
325
|
+
* @param {Array} packageIds - The available package identifiers.
|
|
326
|
+
* @param {Array} accessStrategies The array of accessStrategies objects
|
|
195
327
|
* @returns {Array} An single-item array of objects for the Event Resources.
|
|
196
328
|
*/
|
|
197
|
-
const createEventResourceTemplate = (serviceName, serviceDefinition, appConfig, packageIds) => {
|
|
329
|
+
const createEventResourceTemplate = (serviceName, serviceDefinition, appConfig, packageIds, accessStrategies) => {
|
|
198
330
|
const ordExtensions = readORDExtensions(serviceDefinition);
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
331
|
+
const visibility = _handleVisibility(ordExtensions, serviceDefinition);
|
|
332
|
+
const packageId = _getPackageID(appConfig.ordNamespace, packageIds, ORD_RESOURCE_TYPE.event, visibility);
|
|
333
|
+
const ordId = `${appConfig.ordNamespace}:eventResource:${serviceName}:v1`;
|
|
334
|
+
const entityTypeMappings = _getEntityTypeMappings(serviceDefinition);
|
|
335
|
+
|
|
336
|
+
let obj = {
|
|
337
|
+
ordId,
|
|
202
338
|
title:
|
|
203
339
|
serviceDefinition["@title"] ??
|
|
204
340
|
serviceDefinition["@Common.Label"] ??
|
|
205
341
|
`ODM ${appConfig.appName.replace(/[^a-zA-Z0-9]/g, "")} Events`,
|
|
206
342
|
shortDescription: `${serviceName} event resource`,
|
|
207
343
|
description:
|
|
208
|
-
serviceDefinition[
|
|
344
|
+
serviceDefinition["@description"] ??
|
|
345
|
+
serviceDefinition["@Core.Description"] ??
|
|
209
346
|
"CAP Event resource describing events / messages.",
|
|
210
347
|
version: "1.0.0",
|
|
211
348
|
lastUpdate: appConfig.lastUpdate,
|
|
212
349
|
releaseStatus: "active",
|
|
213
|
-
partOfPackage:
|
|
350
|
+
partOfPackage: packageId,
|
|
214
351
|
partOfGroups: [_getGroupID(serviceName, defaults.groupTypeId, appConfig)],
|
|
215
|
-
visibility
|
|
352
|
+
visibility,
|
|
216
353
|
resourceDefinitions: [
|
|
217
|
-
|
|
218
|
-
type: "asyncapi-v2",
|
|
219
|
-
mediaType: "application/json",
|
|
220
|
-
url: `/.well-known/open-resource-discovery/v1/api-metadata/${serviceName}.asyncapi2.json`,
|
|
221
|
-
accessStrategies: [
|
|
222
|
-
{
|
|
223
|
-
type: "open",
|
|
224
|
-
},
|
|
225
|
-
],
|
|
226
|
-
},
|
|
354
|
+
_getResourceDefinition("asyncapi-v2", "json", ordId, serviceName, "asyncapi2.json", accessStrategies),
|
|
227
355
|
],
|
|
228
356
|
extensible: { supported: "no" },
|
|
357
|
+
...(entityTypeMappings ? { entityTypeMappings } : {}),
|
|
358
|
+
...ordExtensions,
|
|
359
|
+
};
|
|
229
360
|
|
|
230
|
-
|
|
231
|
-
|
|
361
|
+
return obj.visibility === RESOURCE_VISIBILITY.public || obj.visibility === RESOURCE_VISIBILITY.internal
|
|
362
|
+
? [obj]
|
|
363
|
+
: [];
|
|
232
364
|
};
|
|
233
365
|
|
|
234
|
-
function
|
|
366
|
+
function _getEntityTypeMappings(definitionObj) {
|
|
367
|
+
if (!definitionObj.entities) {
|
|
368
|
+
return;
|
|
369
|
+
}
|
|
370
|
+
const entities = Object.values(definitionObj.entities)
|
|
371
|
+
.flatMap((entity) => _flattenEntityGraph(entity))
|
|
372
|
+
.map(createEntityTypeMappingsItemTemplate);
|
|
373
|
+
const entityTypeTargets = _.uniqBy(entities, CONTENT_MERGE_KEY)
|
|
374
|
+
.filter((entity) => entity !== undefined)
|
|
375
|
+
.map(({ ordId }) => ({
|
|
376
|
+
ordId,
|
|
377
|
+
}));
|
|
378
|
+
if (entityTypeTargets.length > 0) {
|
|
379
|
+
return [{ entityTypeTargets }];
|
|
380
|
+
} else {
|
|
381
|
+
return;
|
|
382
|
+
}
|
|
383
|
+
}
|
|
384
|
+
|
|
385
|
+
function _flattenEntityGraph(currentEntity, processedEntities = []) {
|
|
386
|
+
if (!currentEntity.associations) {
|
|
387
|
+
return [currentEntity];
|
|
388
|
+
}
|
|
389
|
+
const entityAssociationTargets = Object.values(currentEntity.associations).map((association) => ({
|
|
390
|
+
target: association.target,
|
|
391
|
+
entity: association._target,
|
|
392
|
+
}));
|
|
393
|
+
|
|
394
|
+
const assertionsTodo = [];
|
|
395
|
+
entityAssociationTargets.forEach(({ target, entity }) => {
|
|
396
|
+
if (processedEntities.includes(target)) {
|
|
397
|
+
return;
|
|
398
|
+
}
|
|
399
|
+
processedEntities = [...processedEntities, target];
|
|
400
|
+
assertionsTodo.push(entity);
|
|
401
|
+
});
|
|
402
|
+
|
|
403
|
+
return [currentEntity, ...assertionsTodo.flatMap((entity) => _flattenEntityGraph(entity, processedEntities))];
|
|
404
|
+
}
|
|
405
|
+
|
|
406
|
+
function _getPackageID(namespace, packageIds, resourceType, visibility = RESOURCE_VISIBILITY.public) {
|
|
235
407
|
if (!packageIds) return;
|
|
236
408
|
|
|
237
|
-
|
|
409
|
+
if (resourceType) {
|
|
410
|
+
return (
|
|
411
|
+
packageIds.find((id) => {
|
|
412
|
+
if (visibility === RESOURCE_VISIBILITY.public) {
|
|
413
|
+
return id.includes(resourceType) && !id.includes("-internal") && !id.includes("-private");
|
|
414
|
+
} else {
|
|
415
|
+
return id.includes(`${resourceType}-${visibility}`);
|
|
416
|
+
}
|
|
417
|
+
}) || packageIds.find((id) => id.includes(namespace))
|
|
418
|
+
);
|
|
419
|
+
}
|
|
420
|
+
|
|
421
|
+
return packageIds.find((id) => id.includes(`-${resourceType}-`)) || packageIds.find((id) => id.includes(namespace));
|
|
422
|
+
}
|
|
423
|
+
|
|
424
|
+
function _propagateORDVisibility(model) {
|
|
425
|
+
for (const [name, def] of Object.entries(model.definitions)) {
|
|
426
|
+
if (def.kind === CDS_ELEMENT_KIND.service && def[ORD_EXTENSIONS_PREFIX + "visibility"]) {
|
|
427
|
+
const serviceName = name;
|
|
428
|
+
const serviceVisibility = def[ORD_EXTENSIONS_PREFIX + "visibility"];
|
|
429
|
+
for (const serviceChildrenDef of model.definitions) {
|
|
430
|
+
if (
|
|
431
|
+
serviceChildrenDef.name.startsWith(serviceName + ".") &&
|
|
432
|
+
!serviceChildrenDef[ORD_EXTENSIONS_PREFIX + "visibility"]
|
|
433
|
+
) {
|
|
434
|
+
serviceChildrenDef[ORD_EXTENSIONS_PREFIX + "visibility"] = serviceVisibility;
|
|
435
|
+
}
|
|
436
|
+
}
|
|
437
|
+
}
|
|
438
|
+
}
|
|
439
|
+
|
|
440
|
+
return model;
|
|
238
441
|
}
|
|
239
442
|
|
|
240
443
|
module.exports = {
|
|
241
444
|
createEntityTypeTemplate,
|
|
445
|
+
createEntityTypeMappingsItemTemplate,
|
|
242
446
|
createGroupsTemplateForService,
|
|
243
447
|
createAPIResourceTemplate,
|
|
244
448
|
createEventResourceTemplate,
|
|
449
|
+
_getPackageID,
|
|
450
|
+
_getEntityTypeMappings,
|
|
451
|
+
_propagateORDVisibility,
|
|
245
452
|
};
|
package/package.json
CHANGED
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@cap-js/ord",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.3.0",
|
|
4
4
|
"description": "CAP Plugin for generating ORD document.",
|
|
5
5
|
"repository": "cap-js/ord",
|
|
6
6
|
"author": "SAP SE (https://www.sap.com)",
|
|
7
7
|
"homepage": "https://cap.cloud.sap/",
|
|
8
|
-
"license": "
|
|
8
|
+
"license": "Apache-2.0",
|
|
9
9
|
"main": "cds-plugin.js",
|
|
10
10
|
"files": [
|
|
11
11
|
"lib",
|
|
@@ -20,15 +20,24 @@
|
|
|
20
20
|
},
|
|
21
21
|
"devDependencies": {
|
|
22
22
|
"eslint": "^8",
|
|
23
|
-
"jest": "^29.7.0"
|
|
23
|
+
"jest": "^29.7.0",
|
|
24
|
+
"prettier": "3.5.3"
|
|
24
25
|
},
|
|
25
26
|
"peerDependencies": {
|
|
26
|
-
"@cap-js/asyncapi": "^1.0.0",
|
|
27
|
-
"@cap-js/openapi": "^1.0.2",
|
|
28
27
|
"@sap/cds": "^8.1.1",
|
|
29
|
-
"@sap/cds-
|
|
28
|
+
"@sap/cds-dk": "^8.9.3"
|
|
30
29
|
},
|
|
31
30
|
"dependencies": {
|
|
31
|
+
"@cap-js/asyncapi": "^1.0.3",
|
|
32
|
+
"@cap-js/openapi": "^1.2.1",
|
|
33
|
+
"bcrypt": "^5.1.1",
|
|
32
34
|
"lodash": "^4.17.21"
|
|
35
|
+
},
|
|
36
|
+
"cds": {
|
|
37
|
+
"requires": {
|
|
38
|
+
"SAP ORD Service": {
|
|
39
|
+
"model": "@cap-js/ord/lib/ord-service"
|
|
40
|
+
}
|
|
41
|
+
}
|
|
33
42
|
}
|
|
34
|
-
}
|
|
43
|
+
}
|
package/lib/plugin.js
DELETED
|
@@ -1,33 +0,0 @@
|
|
|
1
|
-
const cds = require("@sap/cds");
|
|
2
|
-
const { Logger } = require("./logger");
|
|
3
|
-
const { ord, getMetadata, defaults } = require("./");
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
cds.on("bootstrap", (app) => {
|
|
7
|
-
app.use("/.well-known/open-resource-discovery", async (req, res) => {
|
|
8
|
-
if (req.url === "/") {
|
|
9
|
-
res.status(200).send(defaults.baseTemplate);
|
|
10
|
-
} else {
|
|
11
|
-
try {
|
|
12
|
-
const { contentType, response } = await getMetadata(req.url);
|
|
13
|
-
res.status(200).contentType(contentType).send(response);
|
|
14
|
-
} catch (error) {
|
|
15
|
-
Logger.error(error, 'Error while generating metadata');
|
|
16
|
-
res.status(500).send(error.message);
|
|
17
|
-
}
|
|
18
|
-
}
|
|
19
|
-
});
|
|
20
|
-
|
|
21
|
-
app.get("/open-resource-discovery/v1/documents/1", async (req, res) => {
|
|
22
|
-
try {
|
|
23
|
-
const csn = await cds.load(cds.env.folders.srv);
|
|
24
|
-
const data = ord(csn);
|
|
25
|
-
return res.status(200).send(data);
|
|
26
|
-
} catch (error) {
|
|
27
|
-
Logger.error(error, 'Error while creating ORD document');
|
|
28
|
-
return res.status(500).send(error.message);
|
|
29
|
-
}
|
|
30
|
-
});
|
|
31
|
-
});
|
|
32
|
-
|
|
33
|
-
module.exports = cds.server;
|