@cap-js/ord 1.5.0 → 1.7.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/lib/templates.js CHANGED
@@ -1,4 +1,3 @@
1
- const { hasSAPPolicyLevel } = require("./utils");
2
1
  const defaults = require("./defaults");
3
2
  const _ = require("lodash");
4
3
  const {
@@ -76,14 +75,22 @@ const createEntityTypeMappingsItemTemplate = (entity) => {
76
75
  };
77
76
 
78
77
  function _getGroupID(serviceDefinition, groupTypeId = defaults.groupTypeId, appConfig) {
79
- return `${groupTypeId}:${appConfig.ordNamespace}:${_getGroupNameWithNestedNamespace(serviceDefinition, appConfig)}`;
78
+ return `${groupTypeId}:${appConfig.ordNamespace}:${_getCleanServiceName(serviceDefinition, appConfig)}`;
80
79
  }
81
80
 
82
- function _getGroupNameWithNestedNamespace({ name }, appConfig) {
83
- if (!name.startsWith(appConfig.ordNamespace)) {
84
- return name;
81
+ function _startsWithNamespace(name, namespace) {
82
+ if (!name.startsWith(namespace)) return false;
83
+ const rest = name.substring(namespace.length);
84
+ return rest === "" || rest.startsWith(".");
85
+ }
86
+
87
+ function _getCleanServiceName({ name }, appConfig) {
88
+ let sortedName = name;
89
+ if (appConfig.internalNamespace && _startsWithNamespace(name, appConfig.internalNamespace)) {
90
+ sortedName = name.substring(appConfig.internalNamespace.length);
91
+ } else if (_startsWithNamespace(name, appConfig.ordNamespace)) {
92
+ sortedName = name.substring(appConfig.ordNamespace.length);
85
93
  }
86
- let sortedName = name.substring(appConfig.ordNamespace.length);
87
94
  if (sortedName.startsWith(".")) {
88
95
  sortedName = sortedName.substring(1);
89
96
  }
@@ -157,7 +164,7 @@ function _getResourceDefinition(resourceType, mediaType, ordId, serviceName, fil
157
164
  *
158
165
  * @param {string} serviceName The name of the service.
159
166
  * @param {object} serviceDefinition The definition of the service
160
- * @param {Set} groupIds A set of group ids.
167
+ * @param {object} appConfig - The application configuration.
161
168
  * @returns {Object} A group object.
162
169
  */
163
170
  const createGroupsTemplateForService = (serviceName, serviceDefinition, appConfig) => {
@@ -197,7 +204,7 @@ const createEntityTypeTemplate = (appConfig, packageIds, entity) => {
197
204
  // ODM mappings are not created as entity types, they are only used in entityTypeMappings
198
205
  return [];
199
206
  }
200
- if (hasSAPPolicyLevel(appConfig.policyLevels)) {
207
+ if (appConfig.hasSAPPolicyLevel) {
201
208
  // If SAP policy level is present, don't create entity type, they must be in the central repository
202
209
  return [];
203
210
  }
@@ -305,19 +312,19 @@ const createAPIResourceTemplate = (serviceName, serviceDefinition, appConfig, pa
305
312
  extracted = _extractVersionFromServiceName(serviceDefinition.name);
306
313
  if (extracted) {
307
314
  // Create a temporary service definition with the clean name for namespace processing
308
- const cleanServiceDefinition = { ...serviceDefinition, name: extracted.cleanName };
309
- cleanServiceName = _getGroupNameWithNestedNamespace(cleanServiceDefinition, appConfig);
315
+ const versionExtractedServiceDefinition = { ...serviceDefinition, name: extracted.cleanName };
316
+ cleanServiceName = _getCleanServiceName(versionExtractedServiceDefinition, appConfig);
310
317
  version = extracted.version;
311
318
  semanticVersion = extracted.semanticVersion;
312
319
  } else {
313
320
  // Invalid pattern - use current behavior
314
- cleanServiceName = _getGroupNameWithNestedNamespace(serviceDefinition, appConfig);
321
+ cleanServiceName = _getCleanServiceName(serviceDefinition, appConfig);
315
322
  version = "v1";
316
323
  semanticVersion = "1.0.0";
317
324
  }
318
325
  } else {
319
326
  // Non-data product - use current behavior
320
- cleanServiceName = _getGroupNameWithNestedNamespace(serviceDefinition, appConfig);
327
+ cleanServiceName = _getCleanServiceName(serviceDefinition, appConfig);
321
328
  version = "v1";
322
329
  semanticVersion = "1.0.0";
323
330
  }
@@ -349,7 +356,14 @@ const createAPIResourceTemplate = (serviceName, serviceDefinition, appConfig, pa
349
356
  ];
350
357
  } else if (apiProtocol === ORD_API_PROTOCOL.MCP) {
351
358
  resourceDefinitions = [
352
- _getResourceDefinition(MCP_RESOURCE_DEFINITION_TYPE, "json", ordId, serviceName, "mcp.json", accessStrategies),
359
+ _getResourceDefinition(
360
+ MCP_RESOURCE_DEFINITION_TYPE,
361
+ "json",
362
+ ordId,
363
+ serviceName,
364
+ "mcp.json",
365
+ accessStrategies,
366
+ ),
353
367
  ];
354
368
  } else if (apiProtocol === ORD_API_PROTOCOL.GRAPHQL) {
355
369
  // GraphQL only has GraphQL SDL
@@ -363,8 +377,13 @@ const createAPIResourceTemplate = (serviceName, serviceDefinition, appConfig, pa
363
377
  }),
364
378
  },
365
379
  ];
380
+ } else if (apiProtocol === ORD_API_PROTOCOL.ODATA_V2) {
381
+ // openapi-v3 is not supported for OData V2, only EDMX
382
+ resourceDefinitions = [
383
+ _getResourceDefinition("edmx", "xml", ordId, serviceName, "edmx", accessStrategies),
384
+ ];
366
385
  } else {
367
- // OData and others have both OpenAPI and EDMX
386
+ // odata-v4 and others have both OpenAPI and EDMX
368
387
  resourceDefinitions = [
369
388
  _getResourceDefinition("openapi-v3", "json", ordId, serviceName, "oas3.json", accessStrategies),
370
389
  _getResourceDefinition("edmx", "xml", ordId, serviceName, "edmx", accessStrategies),
@@ -372,7 +391,6 @@ const createAPIResourceTemplate = (serviceName, serviceDefinition, appConfig, pa
372
391
  }
373
392
  }
374
393
 
375
- const entityTypeMappings = _getEntityTypeMappings(serviceDefinition);
376
394
  const exposedEntityTypes = _getExposedEntityTypes(serviceDefinition);
377
395
 
378
396
  let obj = {
@@ -392,7 +410,6 @@ const createAPIResourceTemplate = (serviceName, serviceDefinition, appConfig, pa
392
410
  extensible: {
393
411
  supported: "no",
394
412
  },
395
- ...(entityTypeMappings ? { entityTypeMappings } : {}),
396
413
  ...(exposedEntityTypes ? { exposedEntityTypes } : []),
397
414
  ...ordExtensions,
398
415
  };
@@ -431,8 +448,7 @@ const createEventResourceTemplate = (serviceName, serviceDefinition, appConfig,
431
448
  const ordExtensions = readORDExtensions(serviceDefinition);
432
449
  const visibility = _handleVisibility(ordExtensions, serviceDefinition, appConfig.env?.defaultVisibility);
433
450
  const packageId = _getPackageID(appConfig.ordNamespace, packageIds, ORD_RESOURCE_TYPE.event, visibility);
434
- const ordId = `${appConfig.ordNamespace}:eventResource:${_getGroupNameWithNestedNamespace(serviceDefinition, appConfig)}:v1`;
435
- const entityTypeMappings = _getEntityTypeMappings(serviceDefinition);
451
+ const ordId = `${appConfig.ordNamespace}:eventResource:${_getCleanServiceName(serviceDefinition, appConfig)}:v1`;
436
452
  const exposedEntityTypes = _getExposedEntityTypes(serviceDefinition);
437
453
 
438
454
  let obj = {
@@ -456,7 +472,6 @@ const createEventResourceTemplate = (serviceName, serviceDefinition, appConfig,
456
472
  _getResourceDefinition("asyncapi-v2", "json", ordId, serviceName, "asyncapi2.json", accessStrategies),
457
473
  ],
458
474
  extensible: { supported: "no" },
459
- ...(entityTypeMappings ? { entityTypeMappings } : {}),
460
475
  ...(exposedEntityTypes ? { exposedEntityTypes } : []),
461
476
  ...ordExtensions,
462
477
  };
@@ -473,28 +488,6 @@ function isPrimaryDataProductService(serviceDefinition) {
473
488
  );
474
489
  }
475
490
 
476
- function _getEntityTypeMappings(definitionObj) {
477
- if (!definitionObj.entities) {
478
- return;
479
- }
480
- const entities = Object.values(definitionObj.entities).flatMap((entity) => {
481
- const entityData = _flattenEntityGraph(entity)
482
- .flatMap(createEntityTypeMappingsItemTemplate) // now returns arrays
483
- .filter(Boolean);
484
- return _.uniqBy(entityData, CONTENT_MERGE_KEY);
485
- });
486
- const entityTypeTargets = _.uniqBy(entities, CONTENT_MERGE_KEY)
487
- .filter((entity) => entity !== undefined)
488
- .map(({ ordId }) => ({
489
- ordId,
490
- }));
491
- if (entityTypeTargets.length > 0) {
492
- return [{ entityTypeTargets }];
493
- } else {
494
- return;
495
- }
496
- }
497
-
498
491
  function _getExposedEntityTypes(definitionObj) {
499
492
  if (!definitionObj.entities) {
500
493
  return;
@@ -508,10 +501,9 @@ function _getExposedEntityTypes(definitionObj) {
508
501
  .map(({ ordId }) => ({
509
502
  ordId,
510
503
  }));
504
+
511
505
  if (exposedEntityTypes.length > 0) {
512
506
  return exposedEntityTypes;
513
- } else {
514
- return;
515
507
  }
516
508
  }
517
509
 
@@ -614,7 +606,6 @@ module.exports = {
614
606
  createEventResourceTemplate,
615
607
  readORDExtensions,
616
608
  _getPackageID,
617
- _getEntityTypeMappings,
618
609
  _getExposedEntityTypes,
619
610
  _propagateORDVisibility,
620
611
  _handleVisibility,
@@ -1,6 +1,9 @@
1
1
  const { workerData } = require("piscina");
2
2
 
3
- const compileMetadata = require("../metaData");
3
+ const compileMetadata = require("../meta-data");
4
+ const registerCompileTargets = require("../common/register-compile-targets");
5
+
6
+ registerCompileTargets(); // Worker threads skip cds-plugin.js — re-register compile targets
4
7
 
5
8
  module.exports = ({ url }) => {
6
9
  // JSON round-trip: CDS compiler output may contain Generator objects
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@cap-js/ord",
3
- "version": "1.5.0",
3
+ "version": "1.7.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)",
@@ -25,7 +25,8 @@
25
25
  "test:integration:mtls": "jest __tests__/integration/mtls-auth.test.js --testPathIgnorePatterns=/node_modules/ --forceExit",
26
26
  "test:integration:build": "jest __tests__/integration/cds-build.test.js --testPathIgnorePatterns=/node_modules/ --forceExit",
27
27
  "update-snapshot": "jest --ci --updateSnapshot",
28
- "cds:version": "cds v -i"
28
+ "cds:version": "cds v -i",
29
+ "test:all": "npm run test && npm run test:integration:basic && npm run test:integration:mtls && npm run test:integration:build"
29
30
  },
30
31
  "devDependencies": {
31
32
  "@cap-js/graphql": "^0.14.0",
@@ -34,7 +35,7 @@
34
35
  "eslint": "^10.0.0",
35
36
  "express": "^4",
36
37
  "jest": "^30.0.0",
37
- "prettier": "3.8.1",
38
+ "prettier": "3.8.3",
38
39
  "supertest": "^7.0.0"
39
40
  },
40
41
  "peerDependencies": {
@@ -49,18 +50,23 @@
49
50
  "piscina": "^5.1.4"
50
51
  },
51
52
  "engines": {
52
- "node": ">=18 <23"
53
+ "node": ">=20 <25"
53
54
  },
54
55
  "overrides": {
55
- "@sap/cds-compiler": "6.7.3"
56
+ "@sap/cds-compiler": "6.9.2"
56
57
  },
57
58
  "cds": {
58
59
  "requires": {
59
60
  "SAP ORD Service": {
60
- "model": "@cap-js/ord/lib/ord-service"
61
+ "model": "@cap-js/ord/lib/services/ord-service"
61
62
  },
62
63
  "[java]": {
63
64
  "SAP ORD Service": false
65
+ },
66
+ "[mtx-sidecar]": {
67
+ "SAP ORD Service": {
68
+ "model": "@cap-js/ord/lib/services/mtx-ord-provider-service"
69
+ }
64
70
  }
65
71
  }
66
72
  }
package/lib/metaData.js DELETED
@@ -1,108 +0,0 @@
1
- const cds = require("@sap/cds/lib");
2
- const { compile: openapi } = require("@cap-js/openapi");
3
- const { compile: asyncapi } = require("@cap-js/asyncapi");
4
- const { COMPILER_TYPES, OPENAPI_SERVERS_ANNOTATION } = require("./constants");
5
- const Logger = require("./logger");
6
- const { interopCSN } = require("./interopCsn.js");
7
- const cdsc = require("@sap/cds-compiler/lib/main");
8
-
9
- /**
10
- * Read @OpenAPI.servers annotation from service definition
11
- * @param {object} csn - The CSN model
12
- * @param {string} serviceName - The service name
13
- * @returns {string|undefined} - JSON string of servers array or undefined
14
- */
15
- const _getServersFromAnnotation = (csn, serviceName) => {
16
- const servers = csn?.definitions?.[serviceName]?.[OPENAPI_SERVERS_ANNOTATION];
17
- const isValidServers = Array.isArray(servers) && servers.length > 0;
18
- return isValidServers ? JSON.stringify(servers) : undefined;
19
- };
20
-
21
- const getMetadata = async (url, model = null) => {
22
- const parts = url
23
- ?.split("/")
24
- .pop()
25
- .replace(/\.json$/, "")
26
- .split(".");
27
- const compilerType = parts.pop();
28
- const serviceName = parts.join(".");
29
- const csn = model || cds.services[serviceName]?.model;
30
- const compileOptions = cds.env["ord"]?.compileOptions || {};
31
-
32
- let responseFile;
33
- const options = { service: serviceName, as: "str", messages: [] };
34
- switch (compilerType) {
35
- case COMPILER_TYPES.oas3:
36
- try {
37
- // Check for service-level @OpenAPI.servers annotation
38
- const serversFromAnnotation = _getServersFromAnnotation(csn, serviceName);
39
- const openapiOptions = { ...options, ...(compileOptions?.openapi || {}) };
40
-
41
- // Service-level annotation takes precedence over global config
42
- if (serversFromAnnotation) {
43
- openapiOptions["openapi:servers"] = serversFromAnnotation;
44
- }
45
-
46
- responseFile = openapi(csn, openapiOptions);
47
- } catch (error) {
48
- Logger.error(`OpenApi error for service ${serviceName} - ${error.message}`);
49
- throw error;
50
- }
51
- break;
52
- case COMPILER_TYPES.asyncapi2:
53
- try {
54
- responseFile = asyncapi(csn, { ...options, ...(compileOptions?.asyncapi || {}) });
55
- } catch (error) {
56
- Logger.error(`AsyncApi error for service ${serviceName} - ${error.message}`);
57
- throw error;
58
- }
59
- break;
60
- case COMPILER_TYPES.csn:
61
- try {
62
- const opt_eff = { beta: { effectiveCsn: true }, effectiveServiceName: serviceName };
63
- let effCsn = cdsc.for.effective(csn, opt_eff);
64
- responseFile = interopCSN(effCsn);
65
- } catch (error) {
66
- Logger.error(`Csn error for service ${serviceName} - ${error.message}`);
67
- throw error;
68
- }
69
- break;
70
- case COMPILER_TYPES.edmx:
71
- try {
72
- responseFile = await cds.compile(csn).to["edmx"]({ ...options, ...(compileOptions?.edmx || {}) });
73
- } catch (error) {
74
- Logger.error(`Edmx error for service ${serviceName} - ${error.message}`);
75
- throw error;
76
- }
77
- break;
78
- case COMPILER_TYPES.mcp:
79
- try {
80
- responseFile = await cds.compile(csn).to["mcp"]({ ...options, ...(compileOptions?.mcp || {}) });
81
- } catch (error) {
82
- Logger.error("MCP error:", error.message);
83
- throw error;
84
- }
85
- break;
86
- case COMPILER_TYPES.graphql:
87
- try {
88
- const { generateSchema4 } = require("@cap-js/graphql/lib/schema");
89
- const { lexicographicSortSchema, printSchema } = require("graphql");
90
- const linked = cds.linked(csn);
91
- const srv = new cds.ApplicationService(serviceName, linked);
92
- let schema = generateSchema4({ [serviceName]: srv });
93
- schema = lexicographicSortSchema(schema);
94
- responseFile = printSchema(schema);
95
- } catch (error) {
96
- Logger.error("GraphQL SDL error:", error.message);
97
- throw error;
98
- }
99
- break;
100
- }
101
- return {
102
- contentType:
103
- compilerType === "graphql" ? "text/plain" : `application/${compilerType === "edmx" ? "xml" : "json"}`,
104
- response: responseFile,
105
- };
106
- };
107
-
108
- module.exports = getMetadata;
@@ -1,54 +0,0 @@
1
- const cds = require("@sap/cds");
2
- const ord = require("./ord.js");
3
- const compileMetadata = require("./metaData.js");
4
- const defaults = require("./defaults.js");
5
- const { createAuthConfig, createAuthMiddleware } = require("./auth/authentication.js");
6
- const Logger = require("./logger.js");
7
-
8
- class OpenResourceDiscoveryService extends cds.ApplicationService {
9
- async init() {
10
- // Initialize authentication configuration from .cdsrc.json or environment variables
11
- // CF mTLS validator is lazily initialized on first mTLS request
12
- const authConfig = createAuthConfig();
13
- if (authConfig.error) {
14
- throw new Error(`Authentication initialization failed: ${authConfig.error}`);
15
- }
16
-
17
- // Create authentication middleware
18
- const authMiddleware = createAuthMiddleware(authConfig);
19
-
20
- cds.app.get(`${this.path}`, (_, res) => {
21
- return res.status(200).send(defaults.baseTemplate(authConfig));
22
- });
23
-
24
- cds.app.get(`/ord/v1/documents/ord-document`, authMiddleware, async (_, res) => {
25
- const csn = cds.context?.model || cds.model;
26
- const data = ord(csn);
27
- return res.status(200).send(data);
28
- });
29
-
30
- cds.app.get(`/ord/v1/documents/:id`, authMiddleware, async (_, res) => {
31
- return res.status(404).send("404 Not Found");
32
- });
33
-
34
- // Handler for metadata requests (oas3, edmx, csn, etc.)
35
- const metadataHandler = async (req, res) => {
36
- try {
37
- const { contentType, response } = await compileMetadata(req.url);
38
- return res.status(200).contentType(contentType).send(response);
39
- } catch (error) {
40
- Logger.error(error, "Error while processing the resource definition document");
41
- return res.status(500).send(error.message);
42
- }
43
- };
44
-
45
- // Use separate routes instead of optional parameters for path-to-regexp v8 compatibility
46
- cds.app.get(`/ord/v1/:ordId/:service`, authMiddleware, metadataHandler);
47
- cds.app.get(`/ord/v1/:ordId`, authMiddleware, metadataHandler);
48
- cds.app.get(`/ord/v1`, authMiddleware, metadataHandler);
49
-
50
- return super.init();
51
- }
52
- }
53
-
54
- module.exports = { OpenResourceDiscoveryService };
package/lib/utils.js DELETED
@@ -1,12 +0,0 @@
1
- /**
2
- * Checks if at least one policy level in the array is SAP.
3
- *
4
- * @param {string[]} policyLevels - Array of policy levels.
5
- */
6
- function hasSAPPolicyLevel(policyLevels) {
7
- return policyLevels.some((policyLevel) => policyLevel.split(":")[0].toLowerCase() === "sap");
8
- }
9
-
10
- module.exports = {
11
- hasSAPPolicyLevel,
12
- };