@cap-js/ord 1.8.0 → 1.9.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.
@@ -1,3 +1,4 @@
1
+ const assert = require("node:assert");
1
2
  const localize = require("@sap/cds/lib/i18n/localize");
2
3
 
3
4
  // turn effective CSN into interop CSN
@@ -20,7 +21,7 @@ function add_i18n_texts(csn) {
20
21
  // get all texts of the app
21
22
  const i18n = [...(localize.bundles4(csn) || [])]
22
23
  .filter(([locale]) => !!locale)
23
- .map(([locale, value]) => [locale.replaceAll('_', '-'), value]) // CSN interop uses '-' as separator
24
+ .map(([locale, value]) => [locale.replaceAll("_", "-"), value]) // CSN interop uses '-' as separator
24
25
  .reduce((all, [locale, value]) => ({ ...all, [locale]: value }), {});
25
26
 
26
27
  // get all i18n keys referenced in the csn
@@ -119,9 +120,9 @@ function map_annotations(csn) {
119
120
  }
120
121
 
121
122
  // delete all @assert.unique annotations
122
- let assertUniqueAnnos = Object.keys(o).filter(x => x.startsWith('@assert.unique'))
123
+ let assertUniqueAnnos = Object.keys(o).filter((x) => x.startsWith("@assert.unique"));
123
124
  for (let a of assertUniqueAnnos) {
124
- delete o[a]
125
+ delete o[a];
125
126
  }
126
127
  }
127
128
  }
@@ -132,7 +133,7 @@ function map_annotations(csn) {
132
133
  function add_meta_info(csn) {
133
134
  if (typeof csn != "object") return csn; // needed to make tests pass
134
135
  csn["csnInteropEffective"] = "1.0";
135
- csn.meta ??= {}; // ensure there is a meta section
136
+ csn.meta ??= {}; // ensure there is a meta section
136
137
 
137
138
  // Keep only the properties of csn.meta that are relevant for interop:
138
139
  // "flavor", "document", "features", and private extension names (starting with "__")
@@ -143,27 +144,38 @@ function add_meta_info(csn) {
143
144
  delete csn.meta[k];
144
145
  }
145
146
  }
147
+
146
148
  csn.meta.flavor = "effective";
147
149
 
148
- let services = Object.entries(csn.definitions).filter(([, def]) => def.kind === "service");
150
+ const services = Object.entries(csn.definitions).filter(([, def]) => def.kind === "service");
149
151
  if (services.length === 1) {
152
+ const [name, definition] = services[0];
150
153
  // assumption: "short" service name contains no dots
151
- let segments = services[0][0].split(".");
154
+ const segments = name.split(".");
155
+
152
156
  let v = "1";
153
157
  let srv = segments.pop();
154
- let m = srv.match(/^v(\d+)$/);
158
+ const m = srv.match(/^v(\d+)$/);
155
159
  if (m) {
156
160
  srv = segments.pop();
157
161
  v = m[1];
158
162
  }
159
- csn.meta.document = { version: `${v}.0.0` };
160
- csn.meta.__name = srv;
161
- if (segments.length > 0) csn.meta.__namespace = segments.join(".");
163
+
164
+ assert(
165
+ !definition["@ORD.Extensions.version"] || v === definition["@ORD.Extensions.version"].split(".").shift(),
166
+ `Major version in service name (${v}) does not match major version in @ORD.Extensions.version (${definition["@ORD.Extensions.version"]})`,
167
+ );
168
+
169
+ Object.assign(csn.meta, {
170
+ __name: srv,
171
+ document: { version: definition["@ORD.Extensions.version"] ?? `${v}.0.0` },
172
+ ...(segments.length > 0 && { __namespace: segments.join(".") }),
173
+ });
162
174
  }
163
175
 
164
176
  return csn;
165
177
  }
166
178
 
167
179
  module.exports = {
168
- interopCSN
180
+ interopCSN,
169
181
  };
package/lib/logger.js CHANGED
@@ -1,8 +1,6 @@
1
1
  const cds = require("@sap/cds");
2
2
 
3
3
  // Unified INFO-level logging - simple and consistent across all environments
4
- const Logger = cds.log("ord-plugin", {
4
+ module.exports = cds.log("ord-plugin", {
5
5
  level: cds.log?.levels?.INFO,
6
6
  });
7
-
8
- module.exports = Logger;
package/lib/meta-data.js CHANGED
@@ -7,18 +7,10 @@ const { compile: asyncapi } = require("@cap-js/asyncapi");
7
7
 
8
8
  const Logger = require("./logger");
9
9
  const { interopCSN } = require("./interop-csn.js");
10
- const { COMPILER_TYPES, OPENAPI_SERVERS_ANNOTATION } = require("./constants");
10
+ const { OPENAPI_SERVERS_ANNOTATION } = require("./constants");
11
11
 
12
- function extractServiceName(url) {
13
- return path.basename(url, ".json").split(".").slice(0, -1).join(".");
14
- }
15
-
16
- function extractCompilerType(url) {
17
- return path.basename(url, ".json").split(".").pop();
18
- }
19
-
20
- const compilers = Object.freeze({
21
- [COMPILER_TYPES.csn]: function (csn, options) {
12
+ const COMPILERS = Object.freeze({
13
+ csn: function (csn, options) {
22
14
  return {
23
15
  contentType: "application/json",
24
16
  response: interopCSN(
@@ -26,13 +18,13 @@ const compilers = Object.freeze({
26
18
  ),
27
19
  };
28
20
  },
29
- [COMPILER_TYPES.mcp]: async function (csn, options) {
21
+ mcp: async function (csn, options) {
30
22
  return {
31
23
  contentType: "application/json",
32
24
  response: cds.compile(csn).to["mcp"]({ ...options, ...(cds.env.ord?.compileOptions?.mcp || {}) }),
33
25
  };
34
26
  },
35
- [COMPILER_TYPES.oas3]: function (csn, options) {
27
+ oas3: function (csn, options) {
36
28
  // Check for service-level @OpenAPI.servers annotation
37
29
  const servers = csn?.definitions?.[options.service]?.[OPENAPI_SERVERS_ANNOTATION];
38
30
  const openapiOptions = { ...options, ...(cds.env?.ord?.compileOptions?.openapi || {}) };
@@ -47,15 +39,13 @@ const compilers = Object.freeze({
47
39
  response: openapi(csn, openapiOptions),
48
40
  };
49
41
  },
50
- [COMPILER_TYPES.edmx]: function (csn, options) {
42
+ edmx: function (csn, options) {
51
43
  return {
52
44
  contentType: "application/xml",
53
- response: cds
54
- .compile(csn)
55
- .to["edmx"]({ ...options, ...(cds.env?.ord?.compileOptions?.edmx || {}) }),
45
+ response: cds.compile(csn).to["edmx"]({ ...options, ...(cds.env?.ord?.compileOptions?.edmx || {}) }),
56
46
  };
57
47
  },
58
- [COMPILER_TYPES.graphql]: function (csn, options) {
48
+ graphql: function (csn, options) {
59
49
  const { generateSchema4 } = require("@cap-js/graphql/lib/schema");
60
50
  const { printSchema, lexicographicSortSchema } = require("graphql");
61
51
  const srv = new cds.ApplicationService(options.service, cds.linked(csn));
@@ -65,7 +55,7 @@ const compilers = Object.freeze({
65
55
  response: printSchema(lexicographicSortSchema(generateSchema4({ [options.service]: srv }))),
66
56
  };
67
57
  },
68
- [COMPILER_TYPES.asyncapi2]: function (csn, options) {
58
+ asyncapi2: function (csn, options) {
69
59
  return {
70
60
  contentType: "application/json",
71
61
  response: asyncapi(csn, { ...options, ...(cds.env?.ord?.compileOptions?.asyncapi || {}) }),
@@ -74,16 +64,14 @@ const compilers = Object.freeze({
74
64
  });
75
65
 
76
66
  module.exports = async (url, model = null) => {
77
- const name = extractServiceName(url);
78
- const type = extractCompilerType(url);
79
- const csn = model || cds.services[name]?.model;
80
- const options = { service: name, as: "str", messages: [] };
67
+ const type = path.basename(url, ".json").split(".").pop();
68
+ const name = path.basename(url, ".json").split(".").slice(0, -1).join(".");
81
69
 
82
- assert(Object.hasOwn(compilers, type), `Unsupported format: ${type}`);
70
+ assert(Object.hasOwn(COMPILERS, type), `Unsupported format: ${type}`);
83
71
 
84
72
  try {
85
- return await compilers[type](csn, options);
86
- } catch(error) {
73
+ return await COMPILERS[type](model ?? cds.services[name]?.model, { service: name, as: "str", messages: [] });
74
+ } catch (error) {
87
75
  Logger.error(`Compilation failed for service ${name} (compiler: ${type}) - ${error.message}`);
88
76
  throw error;
89
77
  }
package/lib/ord.js CHANGED
@@ -1,130 +1,67 @@
1
- const _ = require("lodash");
2
- const cds = require("@sap/cds");
3
-
4
- const Logger = require("./logger");
5
1
  const defaults = require("./defaults");
6
- const { createAuthConfig } = require("./auth/authentication");
2
+ const utils = require("./common/utils");
7
3
  const Configuration = require("./configuration");
8
- const { getIntegrationDependencies } = require("./integration-dependency");
4
+ const { createGroups } = require("./templates/group");
5
+ const { createPackages } = require("./templates/package");
6
+ const { createProducts } = require("./templates/product");
7
+ const { createEntityTypes } = require("./templates/entity-type");
8
+ const { createAPIResources } = require("./templates/api-resource");
9
+ const { createEventResources } = require("./templates/event-resource");
10
+ const { createIntegrationDependencies } = require("./templates/integration-dependency");
9
11
  const {
10
12
  getCustomORDContent,
11
13
  compareAndHandleCustomORDContentWithExistingContent,
12
14
  } = require("./extend-ord-with-custom");
13
- const {
14
- createAPIResourceTemplate,
15
- createEntityTypeTemplate,
16
- createEventResourceTemplate,
17
- createGroupsTemplateForService,
18
- _propagateORDVisibility,
19
- } = require("./templates");
20
-
21
- const _getGroups = (csn, appConfig) => {
22
- return appConfig.serviceNames
23
- .map((name) => csn.definitions[name])
24
- .flatMap((srvDefinition) => createGroupsTemplateForService(srvDefinition, appConfig))
25
- .filter((resource) => !!resource);
26
- };
27
-
28
- const _getAPIResources = (csn, appConfig, packageIds, accessStrategies) => {
29
- return appConfig.apiResourceNames
30
- .map((name) => csn.definitions[name])
31
- .flatMap((srvDefinition) => createAPIResourceTemplate(srvDefinition, appConfig, packageIds, accessStrategies));
32
- };
33
-
34
- const _getEventResources = (csn, appConfig, packageIds, accessStrategies) => {
35
- return appConfig.eventServiceNames
36
- .map((name) => csn.definitions[name])
37
- .flatMap((srvDefinition) => createEventResourceTemplate(srvDefinition, appConfig, packageIds, accessStrategies));
38
- };
39
-
40
- const _getProducts = (appConfig) => {
41
- const productsObj = defaults.products(appConfig.packageName);
42
-
43
- if (appConfig.env?.products) {
44
- const customProducts = appConfig.env.products[0];
45
- if (customProducts?.ordId?.toLowerCase().startsWith("sap")) {
46
- Logger.error(
47
- "Detected sap product ordId, which should not be defined for custom products, use default value instead. Please check ord global registry.",
48
- );
49
- } else {
50
- _.assign(productsObj[0], customProducts);
51
- }
52
- }
53
-
54
- return productsObj;
55
- };
56
-
57
- const _createDefaultORDDocument = (linkedCsn, appConfig, authConfig) => {
58
- const products = _getProducts(appConfig);
59
- const packages = defaults.packages(appConfig, products);
60
- const packageIds = packages?.map((pkg) => pkg.ordId) || [];
61
- const entityTypes = _getEntityTypes(appConfig, packageIds);
62
- const integrationDependencies = getIntegrationDependencies(linkedCsn, appConfig, packageIds);
63
- const apiResources = _getAPIResources(linkedCsn, appConfig, packageIds, authConfig.accessStrategies);
64
- const eventResources = _getEventResources(linkedCsn, appConfig, packageIds, authConfig.accessStrategies);
65
-
66
- return {
67
- // Unconditionally added top-level properties
68
- $schema: "https://open-resource-discovery.github.io/specification/spec-v1/interfaces/Document.schema.json",
69
- policyLevels: appConfig.policyLevels,
70
- packages: packages,
71
- description: appConfig.env?.description || defaults.description,
72
- openResourceDiscovery: appConfig.env?.openResourceDiscovery || defaults.openResourceDiscovery,
73
-
74
- // Conditionally added top-level properties
75
- ...(!entityTypes.length ? {} : { entityTypes: entityTypes }),
76
- ...(!apiResources.length ? {} : { apiResources: apiResources }),
77
- ...(!eventResources.length ? {} : { eventResources: eventResources }),
78
- ...(appConfig.existingProductORDId ? {} : { products: [products[0]] }),
79
- ...(!appConfig.serviceNames.length ? {} : { groups: _getGroups(linkedCsn, appConfig) }),
80
- ...(!integrationDependencies.length ? {} : { integrationDependencies: integrationDependencies }),
81
- ...(!appConfig.env?.consumptionBundles?.length ? {} : { consumptionBundles: appConfig.env.consumptionBundles }),
82
- };
83
- };
84
-
85
- const _filterUnusedPackages = (ordDocument) => {
86
- if (!ordDocument.packages?.length) return [];
87
-
88
- const usedPackageIds = new Set();
89
-
90
- ordDocument.apiResources?.forEach((api) => usedPackageIds.add(api.partOfPackage));
91
- ordDocument.eventResources?.forEach((event) => usedPackageIds.add(event.partOfPackage));
92
- ordDocument.dataProducts?.forEach((dp) => usedPackageIds.add(dp.partOfPackage));
93
- ordDocument.integrationDependencies?.forEach((dep) => usedPackageIds.add(dep.partOfPackage));
94
- ordDocument.entityTypes?.forEach((et) => {
95
- if (et?.partOfPackage) {
96
- usedPackageIds.add(et.partOfPackage);
97
- }
98
- });
99
-
100
- return ordDocument.packages.filter((pkg) => usedPackageIds.has(pkg.ordId));
101
- };
102
-
103
- const _getEntityTypes = (appConfig, packageIds) => {
104
- return appConfig.entityTypeTargets.flatMap((entity) => createEntityTypeTemplate(appConfig, packageIds, entity));
105
- };
106
-
107
- const _createAuthConfig = () => {
108
- const authConfig = createAuthConfig();
109
-
110
- // Create auth config and fail-closed on configuration errors
111
- if (authConfig.error) {
112
- throw new Error(`Authentication configuration error: ${authConfig.error}`);
113
- }
114
-
115
- return authConfig;
116
- };
117
-
118
- module.exports = (csn, extensions = []) => {
119
- const authConfig = _createAuthConfig();
120
- const linkedCsn = _propagateORDVisibility(cds.linked(csn));
121
- const appConfig = new Configuration(linkedCsn);
122
- const ordDocument = [...(extensions || []), getCustomORDContent(appConfig)]
123
- .filter((extension) => !!extension)
124
- .reduce(
125
- (document, extension) => compareAndHandleCustomORDContentWithExistingContent(document, extension),
126
- _createDefaultORDDocument(linkedCsn, appConfig, authConfig),
127
- );
128
15
 
129
- return Object.assign(ordDocument, { packages: _filterUnusedPackages(ordDocument) });
16
+ function prune(document) {
17
+ const usedPackageIds = new Set(
18
+ document.packages?.length === 0
19
+ ? []
20
+ : [
21
+ ...(document.entityTypes?.map((et) => et.partOfPackage) ?? []),
22
+ ...(document.dataProducts?.map((dp) => dp.partOfPackage) ?? []),
23
+ ...(document.apiResources?.map((ar) => ar.partOfPackage) ?? []),
24
+ ...(document.eventResources?.map((er) => er.partOfPackage) ?? []),
25
+ ...(document.integrationDependencies?.map((id) => id.partOfPackage) ?? []),
26
+ ],
27
+ );
28
+
29
+ return utils.prune(
30
+ Object.assign(document, {
31
+ // remove unused packages
32
+ packages: document.packages?.filter((pkg) => usedPackageIds.has(pkg.ordId)),
33
+ }),
34
+ );
35
+ }
36
+
37
+ function extend(extensions, document) {
38
+ return extensions.reduce(
39
+ (ord, extension) => compareAndHandleCustomORDContentWithExistingContent(ord, extension),
40
+ document,
41
+ );
42
+ }
43
+
44
+ module.exports = (csn, extensions) => {
45
+ const appConfig = new Configuration(csn);
46
+
47
+ return prune(
48
+ extend(
49
+ [...(extensions || []), getCustomORDContent(appConfig)].filter(Boolean), //
50
+ {
51
+ $schema: defaults.$schema,
52
+ groups: createGroups(appConfig),
53
+ baseUrl: appConfig?.env?.baseUrl,
54
+ products: createProducts(appConfig),
55
+ packages: createPackages(appConfig),
56
+ policyLevels: appConfig.policyLevels,
57
+ entityTypes: createEntityTypes(appConfig),
58
+ apiResources: createAPIResources(appConfig),
59
+ eventResources: createEventResources(appConfig),
60
+ consumptionBundles: appConfig.env?.consumptionBundles,
61
+ description: appConfig.env?.description ?? defaults.description,
62
+ integrationDependencies: createIntegrationDependencies(appConfig),
63
+ openResourceDiscovery: appConfig.env?.openResourceDiscovery ?? defaults.openResourceDiscovery,
64
+ },
65
+ ),
66
+ );
130
67
  };
@@ -1,25 +1,8 @@
1
1
  const cds = require("@sap/cds");
2
- const {
3
- CAP_TO_ORD_PROTOCOL_MAP,
4
- ORD_ONLY_PROTOCOLS,
5
- ORD_API_PROTOCOL,
6
- PLUGIN_UNSUPPORTED_PROTOCOLS,
7
- } = require("./constants");
8
- const Logger = require("./logger");
9
2
 
10
- /**
11
- * Reads the explicit @protocol annotation from service definition.
12
- *
13
- * @param {Object} srvDefinition The service definition object.
14
- * @returns {string[]} Array of protocol names, or empty array if not explicitly set.
15
- */
16
- function _getExplicitProtocol(srvDefinition) {
17
- const protocol = srvDefinition["@protocol"];
18
- if (!protocol) {
19
- return [];
20
- }
21
- return Array.isArray(protocol) ? protocol : [protocol];
22
- }
3
+ const Logger = require("./logger");
4
+ const { isPrimaryDataProductService } = require("./common/utils");
5
+ const { CAP_TO_ORD_PROTOCOL_MAP, ORD_ONLY_PROTOCOLS, ORD_API_PROTOCOL } = require("./constants");
23
6
 
24
7
  /**
25
8
  * Resolves protocol for ORD API Resource generation.
@@ -31,95 +14,55 @@ function _getExplicitProtocol(srvDefinition) {
31
14
  * - Rule C: Never produce [null] in entryPoints
32
15
  *
33
16
  * @param {Object} srvDefinition The service definition object.
34
- * @param {Object} options Configuration options.
35
- * @param {Function} options.isPrimaryDataProduct Strategy function to check if service is primary data product.
36
17
  * @returns {Array} Array with single {apiProtocol, entryPoints, hasResourceDefinitions} object, or empty array.
37
18
  */
38
- function resolveApiResourceProtocol(srvDefinition, options = {}) {
39
- const { isPrimaryDataProduct = () => false } = options;
40
-
19
+ function resolveApiResourceProtocol(srvDefinition) {
41
20
  // 1. Primary Data Product - early return
42
- if (isPrimaryDataProduct(srvDefinition)) {
21
+ if (isPrimaryDataProductService(srvDefinition)) {
43
22
  return [
44
23
  {
45
- apiProtocol: ORD_API_PROTOCOL.SAP_DATA_SUBSCRIPTION,
46
24
  entryPoints: [],
47
25
  hasResourceDefinitions: true,
26
+ apiProtocol: ORD_API_PROTOCOL.SAP_DATA_SUBSCRIPTION,
48
27
  },
49
28
  ];
50
29
  }
51
30
 
52
- const capEndpoints = cds.service.protocols.endpoints4({ name: srvDefinition.name, definition: srvDefinition });
53
- const ordProtocols = [];
54
- for (const endpoint of capEndpoints) {
55
- if (PLUGIN_UNSUPPORTED_PROTOCOLS.includes(endpoint.kind)) {
56
- Logger.warn(
57
- `Protocol '${endpoint.kind}' is supported by ORD but this plugin cannot generate its resource definitions yet.`,
58
- );
59
- continue;
60
- }
61
-
62
- const apiProtocol = CAP_TO_ORD_PROTOCOL_MAP[endpoint.kind] ?? endpoint.kind;
63
- if (apiProtocol) {
64
- ordProtocols.push({
65
- apiProtocol,
66
- entryPoints: endpoint.path ? [endpoint.path] : [],
67
- hasResourceDefinitions: true,
68
- });
69
- }
70
- }
31
+ const ordProtocols = cds.service.protocols
32
+ .endpoints4({ name: srvDefinition.name, definition: srvDefinition })
33
+ .filter((endpoint) => Boolean(CAP_TO_ORD_PROTOCOL_MAP[endpoint.kind]))
34
+ .map((endpoint) => ({
35
+ hasResourceDefinitions: true,
36
+ entryPoints: endpoint.path ? [endpoint.path] : [],
37
+ apiProtocol: CAP_TO_ORD_PROTOCOL_MAP[endpoint.kind],
38
+ }));
71
39
 
72
- const explicit = _getExplicitProtocol(srvDefinition);
73
- for (const protocol of explicit) {
74
- // 2. Handle explicit protocol
75
- // 2a. Check if it's an ORD-only protocol (e.g., INA)
40
+ const cdsProtocols = Object.keys(cds.service.protocols.for(srvDefinition));
41
+ for (const protocol of cdsProtocols) {
42
+ // 2. Handle explicit protocol - check if it's an ORD-only protocol (e.g., INA)
76
43
  if (ORD_ONLY_PROTOCOLS[protocol]) {
77
- const config = ORD_ONLY_PROTOCOLS[protocol];
78
- const path = config.hasEntryPoints ? cds.service.protocols.path4(srvDefinition) : null;
44
+ const { apiProtocol, hasEntryPoints, hasResourceDefinitions } = ORD_ONLY_PROTOCOLS[protocol];
45
+ const path = hasEntryPoints ? cds.service.protocols.path4(srvDefinition) : null;
46
+
79
47
  ordProtocols.push({
80
- apiProtocol: config.apiProtocol,
48
+ apiProtocol: apiProtocol,
81
49
  entryPoints: path ? [path] : [],
82
- hasResourceDefinitions: config.hasResourceDefinitions,
50
+ hasResourceDefinitions: hasResourceDefinitions,
83
51
  });
84
52
  continue;
85
53
  }
86
54
 
87
- // 2b. Check if it's a plugin-unsupported protocol
88
- if (PLUGIN_UNSUPPORTED_PROTOCOLS.includes(protocol)) {
55
+ // 3. Handle explicit protocol with no CAP endpoint (Rule A)
56
+ if (!ordProtocols.some((p) => p.apiProtocol === protocol) && !CAP_TO_ORD_PROTOCOL_MAP[protocol]) {
89
57
  Logger.warn(
90
- `Protocol '${protocol}' is supported by ORD but this plugin cannot generate its resource definitions yet.`,
58
+ `Unknown protocol '${protocol}' is not supported, and skipped for service '${srvDefinition.name}'.`,
91
59
  );
92
- continue;
93
60
  }
94
-
95
- // 4. Handle explicit protocol with no CAP endpoint (Rule A)
96
- if (!ordProtocols.some((p) => p.apiProtocol === protocol) && !CAP_TO_ORD_PROTOCOL_MAP[protocol]) {
97
- Logger.warn(`Unknown protocol '${protocol}' is not supported, and skipped for service '${srvDefinition.name}'.`);
98
- }
99
- }
100
-
101
- // no protocol found via CAP endpoints, but explicit protocol(s) defined → Rule A: don't fall back to OData, return empty array
102
- if (ordProtocols.length === 0 && explicit.length > 0) {
103
- return [];
104
- }
105
-
106
- if (ordProtocols.length > 0) {
107
- return ordProtocols;
108
61
  }
109
62
 
110
- // 5. No explicit protocol and no CAP endpoint - fallback to OData (Rule B)
111
- const path = cds.service.protocols.path4(srvDefinition);
112
- return [
113
- {
114
- apiProtocol: ORD_API_PROTOCOL.ODATA_V4,
115
- entryPoints: path ? [path] : [],
116
- hasResourceDefinitions: true,
117
- },
118
- ];
63
+ return ordProtocols;
119
64
  }
120
65
 
121
66
  module.exports = {
122
67
  resolveApiResourceProtocol,
123
- // Exported for testing
124
- _getExplicitProtocol,
125
68
  };
@@ -1,11 +1,12 @@
1
1
  const cds = require("@sap/cds");
2
2
 
3
+ const { slice } = require("../common/slice");
3
4
  const ord = require("../ord.js");
4
5
  const Logger = require("../logger.js");
5
6
  const defaults = require("../defaults.js");
6
- const { LOCAL_TENANT_ID_HEADER_KEY, DOCUMENT_PERSPECTIVES } = require("../constants");
7
7
  const compileMetadata = require("../meta-data.js");
8
8
  const { createAuthConfig, createAuthMiddleware } = require("../auth/authentication.js");
9
+ const { LOCAL_TENANT_ID_HEADER_KEY, DOCUMENT_PERSPECTIVES } = require("../constants");
9
10
 
10
11
  const validationMiddleware = (req, res, next) => {
11
12
  const toggles = cds.env.requires.toggles;
@@ -13,7 +14,10 @@ const validationMiddleware = (req, res, next) => {
13
14
  const extensibility = cds.env.requires.extensibility;
14
15
  const tenant = req.headers[LOCAL_TENANT_ID_HEADER_KEY];
15
16
 
16
- if (perspective && ![DOCUMENT_PERSPECTIVES.SystemVersion, DOCUMENT_PERSPECTIVES.SystemInstance].includes(perspective)) {
17
+ if (
18
+ perspective &&
19
+ ![DOCUMENT_PERSPECTIVES.SystemVersion, DOCUMENT_PERSPECTIVES.SystemInstance].includes(perspective)
20
+ ) {
17
21
  return res.status(400).send(`Required query parameter 'perspective' is invalid`);
18
22
  }
19
23
 
@@ -79,19 +83,39 @@ class OpenResourceDiscoveryService extends cds.ApplicationService {
79
83
  const authMiddleware = createAuthMiddleware(authConfig);
80
84
 
81
85
  // Default: /.well-known/open-resource-discovery
82
- cds.app.get(`${this.path}`, (req, res) => {
86
+ cds.app.get(`${this.path}`, async (req, res) => {
87
+ const toggles = cds.env.requires.toggles;
88
+ const extensibility = cds.env.requires.extensibility;
83
89
  const tenant = req.headers[LOCAL_TENANT_ID_HEADER_KEY];
90
+ const extensions = Array.from(Object.values(this.extensions));
84
91
 
85
- return res.status(200).send(defaults.baseTemplate(authConfig, tenant));
92
+ return res
93
+ .status(200)
94
+ .send(
95
+ defaults.baseTemplate(
96
+ authConfig,
97
+ ord(cds.model, extensions),
98
+ !tenant || (!toggles && !extensibility)
99
+ ? undefined
100
+ : ord(await resolveCdsModel(DOCUMENT_PERSPECTIVES.SystemInstance, tenant), extensions),
101
+ ),
102
+ );
86
103
  });
87
104
 
88
105
  cds.app.get(`/ord/v1/documents/ord-document`, [authMiddleware, validationMiddleware], async (req, res) => {
89
- const perspective = req.query.perspective || DOCUMENT_PERSPECTIVES.SystemVersion;
106
+ const part = req.query.part || 0;
90
107
  const tenant = req.headers[LOCAL_TENANT_ID_HEADER_KEY];
108
+ const perspective = req.query.perspective || DOCUMENT_PERSPECTIVES.SystemVersion;
91
109
  const model = await resolveCdsModel(perspective, tenant);
92
110
  const extensions = Array.from(Object.values(this.extensions));
111
+ const document = defaults.adjustForPerspective(ord(model, extensions), perspective);
112
+ const slices = slice(document, defaults.sizeLimit);
113
+
114
+ if (isNaN(part) || part < 0 || part >= slices.length) {
115
+ return res.status(400).send(`Required query parameter 'part' is invalid`);
116
+ }
93
117
 
94
- return res.status(200).send(defaults.adjustForPerspective(ord(model, extensions), perspective));
118
+ return res.status(200).send(slices[part]);
95
119
  });
96
120
 
97
121
  cds.app.get(`/ord/v1/documents/:id`, [authMiddleware, validationMiddleware], async (_, res) => {