@sap/cds-dk 10.0.7 → 10.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/CHANGELOG.md +94 -0
- package/bin/add.js +7 -23
- package/bin/bind.js +10 -0
- package/bin/build.js +20 -34
- package/bin/cds.js +1 -1
- package/bin/compile/index.js +15 -41
- package/bin/completion/completionFs.js +11 -8
- package/bin/completion/index.js +592 -80
- package/bin/completion/scripts/cds.fish +568 -0
- package/bin/completion/scripts/cds.ps1 +329 -0
- package/bin/completion/scripts/cds.sh +261 -0
- package/bin/completion/scripts/cds.zsh +230 -0
- package/bin/completion/templates/cds-tmpl.fish +65 -0
- package/bin/completion/templates/cds-tmpl.ps1 +208 -0
- package/bin/completion/templates/cds-tmpl.sh +93 -0
- package/bin/completion/templates/cds-tmpl.zsh +74 -0
- package/bin/deploy/index.js +7 -1
- package/bin/deploy/options.js +25 -0
- package/{lib → bin}/deploy/to-hana/hana.js +8 -8
- package/{lib → bin}/deploy/to-hana/hdiDeployUtil.js +1 -1
- package/bin/deploy/{to-hana.js → to-hana/index.js} +1 -1
- package/{lib → bin}/deploy/to-hana/mtaUtil.js +1 -1
- package/bin/deploy/to-sql.js +0 -2
- package/bin/export.js +35 -5
- package/bin/help/export.md +3 -0
- package/bin/help.js +1 -5
- package/bin/import.js +6 -0
- package/bin/init.js +5 -24
- package/bin/inspect.js +6 -0
- package/bin/login.js +26 -20
- package/bin/pull.js +19 -6
- package/bin/push.js +19 -6
- package/bin/up.js +14 -11
- package/{lib → bin}/upgrade/constants.js +1 -1
- package/bin/{upgrade.js → upgrade/index.js} +2 -2
- package/{lib → bin}/upgrade/preflight/consistency.js +1 -1
- package/{lib → bin}/upgrade/preflight/index.js +1 -1
- package/{lib → bin}/upgrade/steps/analyze/index.js +1 -1
- package/{lib → bin}/upgrade/steps/install.js +1 -1
- package/{lib → bin}/upgrade/steps/openrewrite.js +1 -1
- package/{lib → bin}/upgrade/steps/removals.js +1 -1
- package/{lib → bin}/upgrade/steps/rewrite.js +1 -1
- package/{lib → bin}/upgrade/steps/verify.js +1 -1
- package/{lib/upgrade/index.js → bin/upgrade/upgrade.js} +2 -2
- package/{lib → bin}/upgrade/util/pom.js +2 -2
- package/{lib → bin}/upgrade/util/prompt.js +1 -1
- package/bin/util/containerize.js +8 -4
- package/bin/version.js +10 -1
- package/{lib → bin}/watch/hot-reload.js +1 -1
- package/bin/{watch.js → watch/index.js} +17 -9
- package/{lib → bin}/watch/livereload-connect.js +1 -1
- package/{lib → bin}/watch/vite-watch.js +1 -1
- package/{lib → bin}/watch/watched.js +2 -2
- package/lib/build/constants.js +1 -1
- package/lib/build/plugins/fiori/index.js +31 -2
- package/lib/build/plugins/internal.js +4 -2
- package/lib/build/plugins/mtx-sidecar/index.js +7 -8
- package/lib/build/plugins/nodejs/index.js +64 -7
- package/lib/compile/to_asyncapi.js +1 -1
- package/lib/env/schema.js +16 -0
- package/lib/env/schemas/cds-rc.js +1 -76
- package/lib/import/importapi.js +2 -3
- package/lib/import/message.js +0 -2
- package/lib/init/constants.js +2 -1
- package/lib/init/index.js +2 -2
- package/lib/init/merge.js +123 -280
- package/lib/init/plugin.js +3 -1
- package/lib/init/projectReader.js +22 -6
- package/lib/init/registries/xs-app.js +3 -5
- package/lib/init/template/ai/files/mta.yaml.hbs +1 -1
- package/lib/init/template/ai/index.js +14 -2
- package/lib/init/template/ams/index.js +2 -2
- package/lib/init/template/app-frontend/files/containerize.yaml.hbs +2 -2
- package/lib/init/template/app-frontend/index.js +4 -4
- package/lib/init/template/application-logging/index.js +2 -2
- package/lib/init/template/approuter/files/package.json +1 -1
- package/lib/init/template/approuter/index.js +8 -3
- package/lib/init/template/attachments/files/package.json.hbs +1 -1
- package/lib/init/template/attachments/index.js +2 -2
- package/lib/init/template/audit-logging/index.js +2 -2
- package/lib/init/template/cloud-logging/files/package.json.hbs +4 -4
- package/lib/init/template/cloud-logging/index.js +2 -2
- package/lib/init/template/completion/completionSetup.js +36 -39
- package/lib/init/template/connectivity/index.js +2 -2
- package/lib/init/template/data/as-csv.js +15 -5
- package/lib/init/template/data/as-json.js +114 -42
- package/lib/init/template/data/index.js +16 -2
- package/lib/init/template/destination/index.js +2 -2
- package/lib/init/template/dynatrace/files/package.json.hbs +3 -3
- package/lib/init/template/dynatrace/index.js +2 -2
- package/lib/init/template/enterprise-messaging/index.js +2 -2
- package/lib/init/template/enterprise-messaging-shared/index.js +2 -2
- package/lib/init/template/extensibility/files/mta.yaml.hbs +11 -0
- package/lib/init/template/extensibility/files/xs-security.json.hbs +1 -1
- package/lib/init/template/extensibility/index.js +3 -3
- package/lib/init/template/github-actions/index.js +2 -2
- package/lib/init/template/hana/index.js +3 -2
- package/lib/init/template/hana-serverless/files/db/package.json +14 -0
- package/lib/init/template/hana-serverless/files/identity.values.yaml.hbs +10 -0
- package/lib/init/template/hana-serverless/files/mta.yaml.hbs +64 -0
- package/lib/init/template/hana-serverless/files/package.json.hbs +35 -0
- package/lib/init/template/hana-serverless/files/src/.hdiconfig +139 -0
- package/lib/init/template/hana-serverless/files/undeploy.json +7 -0
- package/lib/init/template/hana-serverless/files/values.yaml.hbs +18 -0
- package/lib/init/template/hana-serverless/index.js +184 -0
- package/lib/init/template/html5-repo/files/containerize.yaml.hbs +2 -2
- package/lib/init/template/html5-repo/files/mta.yaml.hbs +5 -0
- package/lib/init/template/html5-repo/index.js +18 -14
- package/lib/init/template/http/index.js +123 -26
- package/lib/init/template/ias/index.js +2 -2
- package/lib/init/template/kafka/index.js +2 -2
- package/lib/init/template/kyma/index.js +7 -5
- package/lib/init/template/malware-scanner/index.js +2 -2
- package/lib/init/template/mta/files/mta.yaml.hbs +1 -1
- package/lib/init/template/multitenancy/index.js +18 -3
- package/lib/init/template/notifications/index.js +2 -2
- package/lib/init/template/portal/files/mta.yaml.hbs +4 -0
- package/lib/init/template/portal/index.js +13 -8
- package/lib/init/template/postgres/index.js +2 -2
- package/lib/init/template/redis/index.js +2 -2
- package/lib/init/template/sample/files/db/currencies.cds +2 -0
- package/lib/init/template/sample/files/db/data/sap.capire.bookshop-Authors.csv +1 -1
- package/lib/init/template/tiny-sample/files/nodejs/db/data/CatalogService.Books.csv +2 -2
- package/lib/init/template/workzone-standard/index.js +5 -5
- package/lib/init/template/xsuaa/index.js +20 -2
- package/lib/lint/eslintHelp.json +1 -1
- package/lib/mtx/auth_manager.js +103 -37
- package/lib/mtx/params.js +3 -2
- package/lib/mtx/settings_manager.js +45 -28
- package/lib/mtx/tenant_action.js +0 -1
- package/lib/mtx/util/credentials.js +62 -0
- package/node_modules/@sap/cds/CHANGELOG.md +57 -0
- package/node_modules/@sap/cds/app/index.js +5 -4
- package/node_modules/@sap/cds/eslint.config.mjs +1 -0
- package/node_modules/@sap/cds/lib/compile/minify.js +14 -10
- package/node_modules/@sap/cds/lib/env/cds-env.js +1 -1
- package/node_modules/@sap/cds/lib/env/cds-requires.js +22 -11
- package/node_modules/@sap/cds/lib/env/defaults.js +4 -1
- package/node_modules/@sap/cds/lib/env/schemas/cds-rc.js +5 -2
- package/node_modules/@sap/cds/lib/plugins.js +9 -5
- package/node_modules/@sap/cds/lib/req/spawn.js +1 -1
- package/node_modules/@sap/cds/lib/srv/srv-dispatch.js +1 -1
- package/node_modules/@sap/cds/lib/srv/srv-models.js +1 -1
- package/node_modules/@sap/cds/lib/utils/cds-utils.js +2 -1
- package/node_modules/@sap/cds/libx/_runtime/common/generic/assert.js +2 -3
- package/node_modules/@sap/cds/libx/_runtime/common/generic/auth/restrict.js +8 -1
- package/node_modules/@sap/cds/libx/_runtime/common/generic/crud.js +18 -8
- package/node_modules/@sap/cds/libx/_runtime/fiori/lean-draft.js +25 -17
- package/node_modules/@sap/cds/libx/_runtime/messaging/enterprise-messaging-utils/registerEndpoints.js +9 -3
- package/node_modules/@sap/cds/libx/_runtime/remote/Service.js +4 -19
- package/node_modules/@sap/cds/libx/_runtime/remote/utils/LruCache.js +42 -0
- package/node_modules/@sap/cds/libx/_runtime/remote/utils/client.js +14 -4
- package/node_modules/@sap/cds/libx/_runtime/remote/utils/cloudSdkProvider.js +2 -9
- package/node_modules/@sap/cds/libx/_runtime/remote/utils/destinationClient.js +150 -0
- package/node_modules/@sap/cds/libx/_runtime/remote/utils/error.js +13 -0
- package/node_modules/@sap/cds/libx/_runtime/remote/utils/fetchClient.js +26 -18
- package/node_modules/@sap/cds/libx/_runtime/remote/utils/query.js +34 -23
- package/node_modules/@sap/cds/libx/_runtime/remote/utils/token.js +82 -0
- package/node_modules/@sap/cds/libx/_runtime/ucl/Service.js +36 -189
- package/node_modules/@sap/cds/libx/common/utils/streaming.js +16 -16
- package/node_modules/@sap/cds/libx/http/location.js +3 -3
- package/node_modules/@sap/cds/libx/odata/middleware/batch.js +16 -10
- package/node_modules/@sap/cds/libx/odata/middleware/metadata.js +9 -6
- package/node_modules/@sap/cds/libx/odata/middleware/service-document.js +1 -1
- package/node_modules/@sap/cds/libx/odata/parse/afterburner.js +19 -3
- package/node_modules/@sap/cds/libx/odata/parse/cqn2odata.js +83 -86
- package/node_modules/@sap/cds/libx/queue/SchedulingService.js +17 -2
- package/node_modules/@sap/cds/libx/queue/processing.js +1 -3
- package/node_modules/@sap/cds/libx/rest/RestAdapter.js +3 -7
- package/node_modules/@sap/cds/libx/rest/middleware/delete.js +3 -3
- package/node_modules/@sap/cds/libx/rest/middleware/parse.js +13 -8
- package/node_modules/@sap/cds/libx/rest/middleware/read.js +3 -3
- package/node_modules/@sap/cds/libx/rest/middleware/upsert.js +2 -2
- package/node_modules/@sap/cds/package.json +1 -1
- package/node_modules/@sap/cds-compiler/CHANGELOG.md +60 -8
- package/node_modules/@sap/cds-compiler/bin/cdsc.js +2 -2
- package/node_modules/@sap/cds-compiler/doc/IncompatibleChanges_v7.md +46 -19
- package/node_modules/@sap/cds-compiler/lib/api/main.js +10 -7
- package/node_modules/@sap/cds-compiler/lib/api/options.js +8 -2
- package/node_modules/@sap/cds-compiler/lib/base/csnRefs.js +5 -2
- package/node_modules/@sap/cds-compiler/lib/base/message-registry.js +4 -0
- package/node_modules/@sap/cds-compiler/lib/base/optionProcessor.js +12 -0
- package/node_modules/@sap/cds-compiler/lib/checks/enricher.js +1 -0
- package/node_modules/@sap/cds-compiler/lib/compiler/assert-consistency.js +8 -7
- package/node_modules/@sap/cds-compiler/lib/compiler/checks.js +3 -3
- package/node_modules/@sap/cds-compiler/lib/compiler/define.js +301 -75
- package/node_modules/@sap/cds-compiler/lib/compiler/dictionaries.js +2 -0
- package/node_modules/@sap/cds-compiler/lib/compiler/extend.js +40 -26
- package/node_modules/@sap/cds-compiler/lib/compiler/populate.js +45 -60
- package/node_modules/@sap/cds-compiler/lib/compiler/propagator.js +16 -7
- package/node_modules/@sap/cds-compiler/lib/compiler/resolve.js +41 -15
- package/node_modules/@sap/cds-compiler/lib/compiler/shared.js +7 -6
- package/node_modules/@sap/cds-compiler/lib/compiler/tweak-assocs.js +62 -50
- package/node_modules/@sap/cds-compiler/lib/compiler/utils.js +29 -14
- package/node_modules/@sap/cds-compiler/lib/edm/edmAnnoPreprocessor.js +6 -5
- package/node_modules/@sap/cds-compiler/lib/gen/CdlGrammar.checksum +1 -1
- package/node_modules/@sap/cds-compiler/lib/gen/CdlParser.js +1119 -1109
- package/node_modules/@sap/cds-compiler/lib/gen/Dictionary.json +19 -3
- package/node_modules/@sap/cds-compiler/lib/json/from-csn.js +10 -16
- package/node_modules/@sap/cds-compiler/lib/json/to-csn.js +58 -39
- package/node_modules/@sap/cds-compiler/lib/main.d.ts +8 -0
- package/node_modules/@sap/cds-compiler/lib/main.js +1 -0
- package/node_modules/@sap/cds-compiler/lib/model/csnUtils.js +2 -0
- package/node_modules/@sap/cds-compiler/lib/parsers/AstBuildingParser.js +2 -1
- package/node_modules/@sap/cds-compiler/lib/parsers/XprTree.js +8 -0
- package/node_modules/@sap/cds-compiler/lib/render/toCdl.js +22 -1
- package/node_modules/@sap/cds-compiler/lib/render/toSql.js +17 -2
- package/node_modules/@sap/cds-compiler/lib/render/utils/common.js +6 -1
- package/node_modules/@sap/cds-compiler/lib/render/utils/operators.js +27 -0
- package/node_modules/@sap/cds-compiler/lib/render/utils/standardDatabaseFunctions.js +125 -3
- package/node_modules/@sap/cds-compiler/lib/tool-lib/revealInternalProperties.js +2 -1
- package/node_modules/@sap/cds-compiler/lib/transform/db/applyTransformations.js +3 -0
- package/node_modules/@sap/cds-compiler/lib/transform/db/associations.js +8 -2
- package/node_modules/@sap/cds-compiler/lib/transform/db/backlinks.js +22 -6
- package/node_modules/@sap/cds-compiler/lib/transform/db/constraints.js +62 -1
- package/node_modules/@sap/cds-compiler/lib/transform/effective/annotations.js +40 -0
- package/node_modules/@sap/cds-compiler/lib/transform/effective/main.js +1 -0
- package/node_modules/@sap/cds-compiler/lib/transform/forRelationalDB.js +6 -1
- package/node_modules/@sap/cds-compiler/lib/transform/translateAssocsToJoins.js +19 -8
- package/node_modules/@sap/cds-compiler/package.json +1 -1
- package/node_modules/@sap/cds-mtxs/CHANGELOG.md +15 -0
- package/node_modules/@sap/cds-mtxs/bin/cds-mtx.js +34 -16
- package/node_modules/@sap/cds-mtxs/lib/utils.js +19 -4
- package/node_modules/@sap/cds-mtxs/package.json +1 -1
- package/node_modules/@sap/cds-mtxs/srv/cf/abstract-provisioning-service.js +1 -1
- package/node_modules/@sap/cds-mtxs/srv/extensibility-service.js +22 -13
- package/node_modules/@sap/cds-mtxs/srv/model-provider.js +17 -2
- package/node_modules/@sap/cds-mtxs/srv/plugins/hana/ctnr-mgr-base.js +1 -1
- package/node_modules/@sap/cds-mtxs/srv/plugins/hana.js +4 -8
- package/node_modules/@sap/xsenv/CHANGELOG.md +7 -0
- package/node_modules/@sap/xsenv/package.json +10 -7
- package/node_modules/body-parser/node_modules/content-type/README.md +2 -0
- package/node_modules/body-parser/node_modules/content-type/dist/index.d.ts +20 -0
- package/node_modules/body-parser/node_modules/content-type/dist/index.js +20 -14
- package/node_modules/body-parser/node_modules/content-type/dist/index.js.map +1 -1
- package/node_modules/body-parser/node_modules/content-type/package.json +1 -1
- package/node_modules/hdb/cfg/VERSION +1 -1
- package/node_modules/hdb/lib/protocol/ClientInfo.js +16 -3
- package/node_modules/hdb/lib/protocol/Connection.js +1 -0
- package/node_modules/hdb/package.json +3 -2
- package/node_modules/negotiator/lib/accept.js +54 -0
- package/node_modules/negotiator/lib/charset.js +10 -34
- package/node_modules/negotiator/lib/encoding.js +9 -30
- package/node_modules/negotiator/lib/language.js +15 -36
- package/node_modules/negotiator/lib/mediaType.js +16 -134
- package/node_modules/negotiator/node_modules/content-type/LICENSE +22 -0
- package/node_modules/negotiator/node_modules/content-type/README.md +71 -0
- package/node_modules/negotiator/node_modules/content-type/dist/index.d.ts +46 -0
- package/node_modules/negotiator/node_modules/content-type/dist/index.js +176 -0
- package/node_modules/negotiator/node_modules/content-type/dist/index.js.map +1 -0
- package/node_modules/negotiator/node_modules/content-type/package.json +52 -0
- package/node_modules/negotiator/package.json +16 -10
- package/node_modules/qs/.github/THREAT_MODEL.md +3 -3
- package/node_modules/qs/CHANGELOG.md +15 -0
- package/node_modules/qs/README.md +19 -1
- package/node_modules/qs/dist/qs.js +21 -21
- package/node_modules/qs/eslint.config.mjs +1 -1
- package/node_modules/qs/lib/parse.js +3 -4
- package/node_modules/qs/lib/stringify.js +23 -8
- package/node_modules/qs/lib/utils.js +10 -3
- package/node_modules/qs/package.json +4 -4
- package/node_modules/qs/test/parse.js +192 -3
- package/node_modules/qs/test/stringify.js +317 -0
- package/node_modules/qs/test/utils.js +12 -0
- package/node_modules/type-is/node_modules/content-type/README.md +2 -0
- package/node_modules/type-is/node_modules/content-type/dist/index.d.ts +20 -0
- package/node_modules/type-is/node_modules/content-type/dist/index.js +20 -14
- package/node_modules/type-is/node_modules/content-type/dist/index.js.map +1 -1
- package/node_modules/type-is/node_modules/content-type/package.json +1 -1
- package/node_modules/ws/lib/permessage-deflate.js +3 -1
- package/node_modules/ws/package.json +1 -1
- package/package.json +3 -3
- package/bin/completion/scripts/cds-bash.sh +0 -20
- package/bin/completion/scripts/cds-fish.fish +0 -13
- package/bin/completion/scripts/cds-ps.ps1 +0 -19
- package/bin/completion/scripts/cds-zsh.sh +0 -13
- package/lib/deploy/index.js +0 -25
- package/lib/import/asyncapi/importAsyncAPI.js +0 -33
- package/lib/import/asyncapi/index.js +0 -18
- package/lib/import/asyncapi/parserContext.js +0 -454
- package/lib/import/asyncapi/utils.js +0 -49
- package/node_modules/@sap/cds/libx/_runtime/ucl/queries.js +0 -61
- package/node_modules/negotiator/HISTORY.md +0 -114
- /package/{lib → bin}/upgrade/context.js +0 -0
- /package/{lib → bin}/upgrade/migrations/cds10/migrations.json +0 -0
- /package/{lib → bin}/upgrade/preflight/cds-version.js +0 -0
- /package/{lib → bin}/upgrade/preflight/project-type.js +0 -0
- /package/{lib → bin}/upgrade/steps/analyze/classification.js +0 -0
- /package/{lib → bin}/upgrade/steps/analyze/detector.js +0 -0
- /package/{lib → bin}/upgrade/steps/analyze/evaluator.js +0 -0
- /package/{lib → bin}/upgrade/steps/analyze/reporter.js +0 -0
- /package/{lib → bin}/upgrade/steps/analyze/setup.js +0 -0
- /package/{lib → bin}/upgrade/steps/analyze/utils/detection.js +0 -0
- /package/{lib → bin}/upgrade/steps/analyze/utils/files.js +0 -0
- /package/{lib → bin}/upgrade/steps/analyze/utils/grep.js +0 -0
- /package/{lib → bin}/upgrade/steps/analyze/utils/yaml.js +0 -0
- /package/{lib → bin}/upgrade/util/workspaces.js +0 -0
- /package/{lib → bin}/watch/esm-resolve-registration.js +0 -0
- /package/{lib → bin}/watch/livereload-server.js +0 -0
- /package/{lib → bin}/watch/node-watch.js +0 -0
- /package/{lib → bin}/watch/open.js +0 -0
- /package/{lib → bin}/watch/watcher.js +0 -0
|
@@ -0,0 +1,150 @@
|
|
|
1
|
+
const cds = require('../../cds')
|
|
2
|
+
const { getToken } = require('./token')
|
|
3
|
+
const { LruCache } = require('./LruCache')
|
|
4
|
+
const { isTimeoutError, getTimeoutError } = require('./error')
|
|
5
|
+
|
|
6
|
+
const LOG = cds.log('remote')
|
|
7
|
+
|
|
8
|
+
const _destinationCache = new LruCache(cds.env.remote.cache_size)
|
|
9
|
+
|
|
10
|
+
let BUFFER_MS
|
|
11
|
+
|
|
12
|
+
const SUPPORTED_AUTH_TYPES = { NoAuthentication: 1, BasicAuthentication: 1, OAuth2ClientCredentials: 1 }
|
|
13
|
+
|
|
14
|
+
const HEADERS_PREFIX = 'URL.headers.'
|
|
15
|
+
const QUERIES_PREFIX = 'URL.queries.'
|
|
16
|
+
|
|
17
|
+
const _decodeJwt = jwt => {
|
|
18
|
+
try {
|
|
19
|
+
const payload = JSON.parse(Buffer.from(jwt.split('.')[1], 'base64url').toString())
|
|
20
|
+
const { zid, app_tid, zone_uuid, user_id, user_uuid } = payload
|
|
21
|
+
return { tenant: zid || app_tid || zone_uuid, user: user_id || user_uuid }
|
|
22
|
+
} catch {
|
|
23
|
+
LOG.warn('Failed to decode JWT payload')
|
|
24
|
+
return {}
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
const _identity = jwt => {
|
|
29
|
+
const { tenant, user } = (jwt ? _decodeJwt(jwt) : cds.context) ?? {}
|
|
30
|
+
return { tenant, user: user?.id ?? user }
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
const _fetchDestination = async (uri, name, token, userJwt) => {
|
|
34
|
+
const headers = { authorization: `Bearer ${token}` }
|
|
35
|
+
// Note: Cloud SDK only sends x-user-token for OAuth2UserTokenExchange/OAuth2SAMLBearerAssertion
|
|
36
|
+
if (userJwt) headers['x-user-token'] = userJwt
|
|
37
|
+
LOG._debug && LOG.debug(`Fetching destination '${name}'`)
|
|
38
|
+
|
|
39
|
+
const { timeout } = cds.env.remote
|
|
40
|
+
const signal = AbortSignal.timeout(cds.utils.ms4(timeout))
|
|
41
|
+
let res
|
|
42
|
+
try {
|
|
43
|
+
res = await fetch(`${uri}/destination-configuration/v1/destinations/${encodeURIComponent(name)}`, {
|
|
44
|
+
headers,
|
|
45
|
+
signal
|
|
46
|
+
})
|
|
47
|
+
} catch (e) {
|
|
48
|
+
if (isTimeoutError(e)) throw getTimeoutError(e, `Fetching destination '${name}'`, timeout)
|
|
49
|
+
throw e
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
if (res.status === 404) return null
|
|
53
|
+
if (!res.ok) throw new Error(`Fetching destination '${name}' failed with status ${res.status}.`)
|
|
54
|
+
return res.json()
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
const _processResponse = ({ destinationConfiguration: dc, authTokens }) => {
|
|
58
|
+
if (!(dc.Authentication in SUPPORTED_AUTH_TYPES))
|
|
59
|
+
LOG.warn(`Auth type ${dc.Authentication} is not yet supported. Falling back to best-effort.`)
|
|
60
|
+
|
|
61
|
+
const authToken = authTokens?.[0]
|
|
62
|
+
const headers = {}
|
|
63
|
+
if (authToken?.error != null) throw new Error(`Destination returned auth token error: ${authToken.error}`)
|
|
64
|
+
if (authToken?.http_header) headers[authToken.http_header.key.toLowerCase()] = authToken.http_header.value
|
|
65
|
+
if (dc['sap-client']) headers['sap-client'] = dc['sap-client']
|
|
66
|
+
|
|
67
|
+
const queryParameters = {}
|
|
68
|
+
for (const [k, v] of Object.entries(dc)) {
|
|
69
|
+
if (k.startsWith(HEADERS_PREFIX)) headers[k.slice(HEADERS_PREFIX.length).toLowerCase()] = v
|
|
70
|
+
else if (k.startsWith(QUERIES_PREFIX)) queryParameters[k.slice(QUERIES_PREFIX.length)] = v
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
const expiresIn = authToken?.expiresIn && Number(authToken.expiresIn)
|
|
74
|
+
|
|
75
|
+
return {
|
|
76
|
+
url: dc.URL,
|
|
77
|
+
authentication: dc.Authentication,
|
|
78
|
+
expiresIn,
|
|
79
|
+
...(Object.keys(headers).length && { headers }),
|
|
80
|
+
...(Object.keys(queryParameters).length && { queryParameters })
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
const _lookup = async (creds, destinationName, tenant, jwt) => {
|
|
85
|
+
if (tenant) {
|
|
86
|
+
const subscriberToken = await getToken(creds, tenant)
|
|
87
|
+
const subscriberResult = await _fetchDestination(creds.uri, destinationName, subscriberToken, jwt)
|
|
88
|
+
if (subscriberResult) {
|
|
89
|
+
LOG._debug && LOG.debug(`Destination '${destinationName}' resolved for subscriber tenant '${tenant}'`)
|
|
90
|
+
return _processResponse(subscriberResult)
|
|
91
|
+
}
|
|
92
|
+
LOG._debug &&
|
|
93
|
+
LOG.debug(
|
|
94
|
+
`Destination '${destinationName}' not found for subscriber tenant '${tenant}', falling back to provider`
|
|
95
|
+
)
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
const providerToken = await getToken(creds)
|
|
99
|
+
const providerResult = await _fetchDestination(creds.uri, destinationName, providerToken)
|
|
100
|
+
if (providerResult) {
|
|
101
|
+
LOG._debug && LOG.debug(`Destination '${destinationName}' resolved for provider tenant`)
|
|
102
|
+
return _processResponse(providerResult)
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
throw new Error(`Destination '${destinationName}' not found.`)
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
const getDestination = async (destinationName, { jwt } = {}) => {
|
|
109
|
+
const creds = cds.env.requires?.destinations?.credentials
|
|
110
|
+
if (!creds?.uri || !creds?.clientid) {
|
|
111
|
+
LOG._debug && LOG.debug('No Destination Service credentials found')
|
|
112
|
+
return null
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
LOG._debug && LOG.debug(`Resolving destination '${destinationName}'`)
|
|
116
|
+
|
|
117
|
+
const { tenant, user } = _identity(jwt)
|
|
118
|
+
// JWT present but no tenant claim — skip cache to avoid cross-tenant pollution
|
|
119
|
+
const cacheKey = jwt && !tenant ? undefined : [destinationName, tenant, user].filter(Boolean).join(':')
|
|
120
|
+
if (!cacheKey) LOG._debug && LOG.debug(`Destination '${destinationName}': JWT has no tenant claim, skipping cache`)
|
|
121
|
+
|
|
122
|
+
const cached = _destinationCache.get(cacheKey)
|
|
123
|
+
if (cached) {
|
|
124
|
+
LOG._debug && LOG.debug(`Destination '${destinationName}' served from cache`)
|
|
125
|
+
return cached
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
const promise = _lookup(creds, destinationName, tenant, jwt)
|
|
129
|
+
.then(resolved => {
|
|
130
|
+
if (!resolved) return null
|
|
131
|
+
const { expiresIn = 60 * 60, ...destination } = resolved
|
|
132
|
+
const expiresInMs = expiresIn * 1000
|
|
133
|
+
BUFFER_MS ??= cds.utils.ms4(cds.env.remote.cache_expiry_buffer)
|
|
134
|
+
const ttl = Math.max(0, expiresInMs - BUFFER_MS)
|
|
135
|
+
LOG._debug && LOG.debug(`Destination for '${cacheKey}' cached for ${ttl}ms`)
|
|
136
|
+
_destinationCache.set(cacheKey, destination, ttl)
|
|
137
|
+
return destination
|
|
138
|
+
})
|
|
139
|
+
.catch(err => {
|
|
140
|
+
LOG.warn(`Failed to resolve destination '${destinationName}':`, err.message)
|
|
141
|
+
_destinationCache.delete(cacheKey)
|
|
142
|
+
throw err
|
|
143
|
+
})
|
|
144
|
+
|
|
145
|
+
// cache promise immediately to deduplicate concurrent in-flight requests
|
|
146
|
+
_destinationCache.set(cacheKey, promise)
|
|
147
|
+
return promise
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
module.exports = { getDestination }
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
const isTimeoutError = e => e.name === 'TimeoutError'
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* @param {Error} cause
|
|
5
|
+
* @param {string} prefix
|
|
6
|
+
* @param {number|string} timeout
|
|
7
|
+
*/
|
|
8
|
+
const getTimeoutError = (cause, prefix, timeout) =>
|
|
9
|
+
Object.assign(new Error(`${prefix} exceeded timeout of ${typeof timeout === 'number' ? `${timeout}ms` : timeout}.`), {
|
|
10
|
+
cause
|
|
11
|
+
})
|
|
12
|
+
|
|
13
|
+
module.exports = { isTimeoutError, getTimeoutError }
|
|
@@ -2,6 +2,8 @@ const cds = require('../../cds')
|
|
|
2
2
|
const LOG = cds.log('remote')
|
|
3
3
|
|
|
4
4
|
const { Readable } = require('stream')
|
|
5
|
+
const { getDestination } = require('./destinationClient')
|
|
6
|
+
const { isTimeoutError, getTimeoutError } = require('./error')
|
|
5
7
|
|
|
6
8
|
// process.env.destinations — CloudSDK convention for local destination resolution:
|
|
7
9
|
// a JSON array of { name, url, ... } objects, e.g. for local development or testing.
|
|
@@ -23,7 +25,7 @@ const _normalizeAuthentication = destination => {
|
|
|
23
25
|
return { ...destination, authentication }
|
|
24
26
|
}
|
|
25
27
|
|
|
26
|
-
const _resolveDestination = destination => {
|
|
28
|
+
const _resolveDestination = async destination => {
|
|
27
29
|
if (destination.url) return _normalizeAuthentication(destination)
|
|
28
30
|
|
|
29
31
|
const matches = findLocalDestinations(destination.destinationName)
|
|
@@ -38,7 +40,8 @@ const _resolveDestination = destination => {
|
|
|
38
40
|
return _normalizeAuthentication({ ...destination, ...found })
|
|
39
41
|
}
|
|
40
42
|
|
|
41
|
-
|
|
43
|
+
const fetched = await getDestination(destination.destinationName, { jwt: destination.jwt })
|
|
44
|
+
if (fetched) return _normalizeAuthentication({ ...destination, ...fetched })
|
|
42
45
|
|
|
43
46
|
throw new Error(`Cannot resolve destination '${destination.destinationName}'.`)
|
|
44
47
|
}
|
|
@@ -109,11 +112,19 @@ const _readBody = async (response, responseType) => {
|
|
|
109
112
|
*/
|
|
110
113
|
const executeHttpRequest = async (destination, requestConfig) => {
|
|
111
114
|
// Resolve destination
|
|
112
|
-
destination = _resolveDestination(destination)
|
|
115
|
+
destination = await _resolveDestination(destination)
|
|
113
116
|
const baseUrl = (destination.url || '').replace(/\/$/, '')
|
|
114
117
|
|
|
115
|
-
const {
|
|
116
|
-
|
|
118
|
+
const {
|
|
119
|
+
method = 'GET',
|
|
120
|
+
url = '',
|
|
121
|
+
data,
|
|
122
|
+
responseType,
|
|
123
|
+
maxBodyLength,
|
|
124
|
+
timeout = cds.env.remote.timeout,
|
|
125
|
+
csrf
|
|
126
|
+
} = requestConfig
|
|
127
|
+
const signal = timeout && AbortSignal.timeout(cds.utils.ms4(timeout))
|
|
117
128
|
|
|
118
129
|
// Assemble headers
|
|
119
130
|
const headers = { ...destination.headers, ...requestConfig.headers }
|
|
@@ -122,10 +133,16 @@ const executeHttpRequest = async (destination, requestConfig) => {
|
|
|
122
133
|
headers.authorization = 'Basic ' + Buffer.from(`${username}:${password}`).toString('base64')
|
|
123
134
|
}
|
|
124
135
|
|
|
125
|
-
|
|
136
|
+
// Build full URL
|
|
137
|
+
let fullUrl = baseUrl + (url && !url.startsWith('/') ? `/${url}` : url)
|
|
138
|
+
const queryParameters = { ...destination.queryParameters, ...requestConfig.params }
|
|
139
|
+
if (Object.keys(queryParameters).length) {
|
|
140
|
+
const qs = new URLSearchParams(Object.entries(queryParameters).map(([k, v]) => [k, String(v)])).toString()
|
|
141
|
+
fullUrl += (fullUrl.includes('?') ? '&' : '?') + qs
|
|
142
|
+
}
|
|
126
143
|
|
|
127
144
|
if (csrf && method !== 'GET' && method !== 'HEAD') {
|
|
128
|
-
const result = await _fetchCsrfToken(
|
|
145
|
+
const result = await _fetchCsrfToken(fullUrl.split('?')[0], headers, csrf, signal)
|
|
129
146
|
if (result?.token) headers['x-csrf-token'] = result.token
|
|
130
147
|
if (result?.cookie) headers.cookie = headers.cookie ? `${headers.cookie};${result.cookie}` : result.cookie
|
|
131
148
|
}
|
|
@@ -143,14 +160,6 @@ const executeHttpRequest = async (destination, requestConfig) => {
|
|
|
143
160
|
throw new Error(`Request body length ${len} exceeds configured max_body_length ${maxBodyLength}.`)
|
|
144
161
|
}
|
|
145
162
|
|
|
146
|
-
// Build full URL
|
|
147
|
-
const { queryParameters } = destination
|
|
148
|
-
let fullUrl = urlWithoutQuery
|
|
149
|
-
if (queryParameters && Object.keys(queryParameters).length) {
|
|
150
|
-
const qs = new URLSearchParams(Object.entries(queryParameters).map(([k, v]) => [k, String(v)])).toString()
|
|
151
|
-
fullUrl += (fullUrl.includes('?') ? '&' : '?') + qs
|
|
152
|
-
}
|
|
153
|
-
|
|
154
163
|
if (LOG._debug) LOG.debug(`${method} ${fullUrl}`)
|
|
155
164
|
|
|
156
165
|
// Execute request
|
|
@@ -158,9 +167,8 @@ const executeHttpRequest = async (destination, requestConfig) => {
|
|
|
158
167
|
try {
|
|
159
168
|
response = await fetch(fullUrl, { method, headers, body, signal })
|
|
160
169
|
} catch (e) {
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
throw Object.assign(new Error(msg), { cause: e })
|
|
170
|
+
if (isTimeoutError(e)) throw getTimeoutError(e, 'Remote request', timeout)
|
|
171
|
+
throw e
|
|
164
172
|
}
|
|
165
173
|
|
|
166
174
|
// Parse response
|
|
@@ -25,38 +25,39 @@ const _cqnWithPublicEntries = query => {
|
|
|
25
25
|
return Object.fromEntries(Object.entries(query).filter(([key]) => !key.startsWith('_')))
|
|
26
26
|
}
|
|
27
27
|
|
|
28
|
-
const
|
|
28
|
+
const _extractHcqlRequestConfig = cqn => {
|
|
29
|
+
if (cqn.flat) cqn.flat()
|
|
30
|
+
|
|
29
31
|
if (cqn.SELECT && cqn.SELECT.from) return { method: 'POST', data: { SELECT: _cqnWithPublicEntries(cqn.SELECT) } }
|
|
30
32
|
if (cqn.INSERT && cqn.INSERT.into) return { method: 'POST', data: { INSERT: _cqnWithPublicEntries(cqn.INSERT) } }
|
|
31
33
|
if (cqn.UPDATE && cqn.UPDATE.entity) return { method: 'POST', data: { UPDATE: _cqnWithPublicEntries(cqn.UPDATE) } }
|
|
32
34
|
if (cqn.UPSERT && cqn.UPSERT.into) return { method: 'POST', data: { UPSERT: _cqnWithPublicEntries(cqn.UPSERT) } }
|
|
33
35
|
if (cqn.DELETE && cqn.DELETE.from) return { method: 'POST', data: { DELETE: _cqnWithPublicEntries(cqn.DELETE) } }
|
|
34
36
|
|
|
35
|
-
cds.error(400, 'Invalid CQN object can not be processed.', JSON.stringify(cqn),
|
|
37
|
+
cds.error(400, 'Invalid CQN object can not be processed.', JSON.stringify(cqn), _extractHcqlRequestConfig)
|
|
36
38
|
}
|
|
37
39
|
|
|
38
|
-
const
|
|
39
|
-
|
|
40
|
+
const _extractODataRequestConfig = (req, srv) => {
|
|
41
|
+
if (req.query.UPDATE?.where) cds.error(400, 'Filtering is not supported to specify the subject of UPDATE requests')
|
|
42
|
+
if (req.query.DELETE?.where) cds.error(400, 'Filtering is not supported to specify the subject of DELETE requests')
|
|
40
43
|
|
|
41
|
-
const queryObject = cds.odata.urlify(
|
|
44
|
+
const queryObject = cds.odata.urlify(req, srv)
|
|
42
45
|
|
|
43
46
|
const requestConfig = {
|
|
44
47
|
method: queryObject.method,
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
.replace(/\( /g, '(')
|
|
49
|
-
.replace(/ \)/g, ')')
|
|
48
|
+
// REVISIT: remove when we can assume that number of remote services running Okra is negligible
|
|
49
|
+
// ugly workaround for Okra not allowing spaces in ( x eq 1 )
|
|
50
|
+
url: queryObject.path.replace(/\( /g, '(').replace(/ \)/g, ')')
|
|
50
51
|
}
|
|
51
52
|
|
|
52
53
|
if (queryObject.method !== 'GET' && queryObject.method !== 'HEAD') {
|
|
53
|
-
requestConfig.data = kind === 'odata-v2' ? convertV2PayloadData(queryObject.body, req.target) : queryObject.body
|
|
54
|
+
requestConfig.data = srv.kind === 'odata-v2' ? convertV2PayloadData(queryObject.body, req.target) : queryObject.body
|
|
54
55
|
}
|
|
55
56
|
|
|
56
57
|
return requestConfig
|
|
57
58
|
}
|
|
58
59
|
|
|
59
|
-
const
|
|
60
|
+
const _createODataRequestConfig = (query, data, target, srvKind) => {
|
|
60
61
|
const cleanQuery = query.trim()
|
|
61
62
|
const blankIndex = cleanQuery.substring(0, 8).indexOf(' ')
|
|
62
63
|
|
|
@@ -66,13 +67,13 @@ const _stringToRequestConfig = (query, data, target) => {
|
|
|
66
67
|
}
|
|
67
68
|
|
|
68
69
|
if (data && requestConfig.method !== 'GET' && requestConfig.method !== 'HEAD') {
|
|
69
|
-
requestConfig.data =
|
|
70
|
+
requestConfig.data = srvKind === 'odata-v2' ? Object.assign({}, convertV2PayloadData(data, target)) : data
|
|
70
71
|
}
|
|
71
72
|
|
|
72
73
|
return requestConfig
|
|
73
74
|
}
|
|
74
75
|
|
|
75
|
-
const _pathToRequestConfig = (method, path, data, target, srvName) => {
|
|
76
|
+
const _pathToRequestConfig = (method, path, data, target, srvName, srvKind) => {
|
|
76
77
|
let url = path
|
|
77
78
|
|
|
78
79
|
if (!url.startsWith('/')) {
|
|
@@ -88,7 +89,7 @@ const _pathToRequestConfig = (method, path, data, target, srvName) => {
|
|
|
88
89
|
|
|
89
90
|
const requestConfig = { method, url }
|
|
90
91
|
if (data && requestConfig.method !== 'GET' && requestConfig.method !== 'HEAD') {
|
|
91
|
-
requestConfig.data =
|
|
92
|
+
requestConfig.data = srvKind === 'odata-v2' ? Object.assign({}, convertV2PayloadData(data, target)) : data
|
|
92
93
|
}
|
|
93
94
|
|
|
94
95
|
return requestConfig
|
|
@@ -101,12 +102,22 @@ const _hasHeader = (headers, header) =>
|
|
|
101
102
|
|
|
102
103
|
const KINDS_SUPPORTING_BATCH = { odata: true, 'odata-v2': true, 'odata-v4': true }
|
|
103
104
|
|
|
104
|
-
const _extractRequestConfig = (req,
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
if (typeof query === '
|
|
108
|
-
|
|
109
|
-
|
|
105
|
+
const _extractRequestConfig = (req, srv) => {
|
|
106
|
+
// REVISIT: Split off the other 'kind's here? odata / odata-v4, odata-v2, rest ...
|
|
107
|
+
|
|
108
|
+
if (srv.kind === 'hcql' && typeof req.query === 'object') return _extractHcqlRequestConfig(req.query)
|
|
109
|
+
if (typeof req.query === 'object') return _extractODataRequestConfig(req, srv)
|
|
110
|
+
if (typeof req.query === 'string') return _createODataRequestConfig(req.query, req.data, req.target, srv.kind)
|
|
111
|
+
|
|
112
|
+
// > no model, no service.definition
|
|
113
|
+
return _pathToRequestConfig(
|
|
114
|
+
req.method,
|
|
115
|
+
req.path,
|
|
116
|
+
req.data,
|
|
117
|
+
req.target,
|
|
118
|
+
srv.definition?.name || srv.namespace,
|
|
119
|
+
srv.kind
|
|
120
|
+
)
|
|
110
121
|
}
|
|
111
122
|
|
|
112
123
|
const _batchEnvelope = (requestConfig, maxGetUrlLength) => {
|
|
@@ -137,8 +148,8 @@ const _batchEnvelope = (requestConfig, maxGetUrlLength) => {
|
|
|
137
148
|
requestConfig.data = batchedRequest
|
|
138
149
|
}
|
|
139
150
|
|
|
140
|
-
module.exports.extractRequestConfig = (req,
|
|
141
|
-
const requestConfig = _extractRequestConfig(req,
|
|
151
|
+
module.exports.extractRequestConfig = (req, service) => {
|
|
152
|
+
const requestConfig = _extractRequestConfig(req, service)
|
|
142
153
|
|
|
143
154
|
if (service.kind === 'odata-v2' && req.event === 'READ' && requestConfig.url?.match(/(\/any\()|(\/all\()/)) {
|
|
144
155
|
req.reject(501, 'Lambda expressions are not supported in OData v2')
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
const cds = require('../../cds')
|
|
2
|
+
const { LruCache } = require('./LruCache')
|
|
3
|
+
const { isTimeoutError, getTimeoutError } = require('./error')
|
|
4
|
+
|
|
5
|
+
const LOG = cds.log('remote')
|
|
6
|
+
|
|
7
|
+
const _tokenCache = new LruCache(cds.env.remote.cache_size)
|
|
8
|
+
|
|
9
|
+
let BUFFER_MS
|
|
10
|
+
|
|
11
|
+
const _fetchToken = async ({ clientid, clientsecret, certificate, key, url }, tenant) => {
|
|
12
|
+
const headers = { 'content-type': 'application/x-www-form-urlencoded' }
|
|
13
|
+
if (tenant) headers['x-zid'] = tenant
|
|
14
|
+
let body = 'grant_type=client_credentials'
|
|
15
|
+
let dispatcher
|
|
16
|
+
|
|
17
|
+
if (certificate) {
|
|
18
|
+
let Agent
|
|
19
|
+
try {
|
|
20
|
+
;({ Agent } = require('node:undici'))
|
|
21
|
+
} catch {
|
|
22
|
+
throw new Error(
|
|
23
|
+
'mTLS token fetching requires Node.js 24 or higher (built-in module "node:undici" not available).'
|
|
24
|
+
)
|
|
25
|
+
}
|
|
26
|
+
body += `&client_id=${encodeURIComponent(clientid)}`
|
|
27
|
+
dispatcher = new Agent({ connect: { cert: certificate, key } })
|
|
28
|
+
} else {
|
|
29
|
+
headers.authorization = 'Basic ' + Buffer.from(`${clientid}:${clientsecret}`).toString('base64')
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
const { timeout } = cds.env.remote
|
|
33
|
+
const signal = AbortSignal.timeout(cds.utils.ms4(timeout))
|
|
34
|
+
let res
|
|
35
|
+
try {
|
|
36
|
+
res = await fetch(url + '/oauth/token', { method: 'POST', headers, body, dispatcher, signal })
|
|
37
|
+
} catch (e) {
|
|
38
|
+
if (isTimeoutError(e)) throw getTimeoutError(e, 'OAuth2 token request', timeout)
|
|
39
|
+
throw e
|
|
40
|
+
}
|
|
41
|
+
if (!res.ok) throw Object.assign(new Error('OAuth2 token request failed.'), { statusCode: res.status })
|
|
42
|
+
const json = await res.json()
|
|
43
|
+
if (!json.access_token) throw new Error('OAuth2 token response missing access_token.')
|
|
44
|
+
return json
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* Returns a cached OAuth2 client credentials token, fetching a new one when absent or expired.
|
|
49
|
+
* @param {{ clientid, clientsecret?, url, certificate?, key? }} creds
|
|
50
|
+
* @param {string} [tenant]
|
|
51
|
+
* @returns {Promise<string>} access_token
|
|
52
|
+
*/
|
|
53
|
+
const getToken = async (creds, tenant) => {
|
|
54
|
+
const key = [creds.clientid, tenant].filter(Boolean).join(':')
|
|
55
|
+
const cached = _tokenCache.get(key)
|
|
56
|
+
if (cached) {
|
|
57
|
+
LOG._debug && LOG.debug(`OAuth2 token for '${key}' served from cache`)
|
|
58
|
+
return cached
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
LOG._debug && LOG.debug(`Fetching OAuth2 token for '${key}' from ${creds.url}`)
|
|
62
|
+
const promise = _fetchToken(creds, tenant)
|
|
63
|
+
.then(({ access_token, expires_in = 60 * 60 }) => {
|
|
64
|
+
const expiresInMs = expires_in * 1000
|
|
65
|
+
BUFFER_MS ??= cds.utils.ms4(cds.env.remote.cache_expiry_buffer)
|
|
66
|
+
const ttl = Math.max(0, expiresInMs - BUFFER_MS)
|
|
67
|
+
LOG._debug && LOG.debug(`OAuth2 token for '${key}' cached for ${ttl}ms`)
|
|
68
|
+
_tokenCache.set(key, access_token, ttl)
|
|
69
|
+
return access_token
|
|
70
|
+
})
|
|
71
|
+
.catch(err => {
|
|
72
|
+
LOG.warn(`Failed to fetch OAuth2 token for '${key}':`, err.message)
|
|
73
|
+
_tokenCache.delete(key)
|
|
74
|
+
throw err
|
|
75
|
+
})
|
|
76
|
+
|
|
77
|
+
// store promise immediately to deduplicate concurrent in-flight requests
|
|
78
|
+
_tokenCache.set(key, promise)
|
|
79
|
+
return promise
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
module.exports = { getToken }
|