@sap/cds-dk 10.0.6 → 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 +107 -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/nodejs/index.js +4 -2
- 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/@cap-js/openapi/lib/compile/csdl.js +3 -3
- package/node_modules/@cap-js/openapi/lib/compile/csdl2openapi.js +111 -103
- package/node_modules/@cap-js/openapi/lib/compile/diagram.js +4 -4
- package/node_modules/@cap-js/openapi/lib/compile/types.d.ts +3 -1
- package/node_modules/@cap-js/openapi/package.json +3 -3
- package/node_modules/@sap/cds/CHANGELOG.md +64 -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/for/nodejs.js +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/common-utils/authorizedRequest.js +3 -2
- 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 +51 -0
- package/node_modules/@sap/cds-mtxs/bin/cds-mtx-migrate.js +32 -48
- package/node_modules/@sap/cds-mtxs/bin/cds-mtx.js +35 -16
- package/node_modules/@sap/cds-mtxs/cds-plugin.js +18 -0
- package/node_modules/@sap/cds-mtxs/lib/migration/migration.js +3 -408
- 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 +6 -7
- package/node_modules/@sap/cds-mtxs/srv/extensibility/linter/annotations.js +5 -2
- package/node_modules/@sap/cds-mtxs/srv/extensibility/linter/index.js +6 -5
- package/node_modules/@sap/cds-mtxs/srv/extensibility-service.cds +0 -11
- package/node_modules/@sap/cds-mtxs/srv/extensibility-service.js +34 -35
- package/node_modules/@sap/cds-mtxs/srv/jobs-service.js +30 -24
- package/node_modules/@sap/cds-mtxs/srv/model-provider.js +19 -4
- package/node_modules/@sap/cds-mtxs/srv/plugins/hana/ctnr-mgr-base.js +6 -3
- package/node_modules/@sap/cds-mtxs/srv/plugins/hana/hdi.js +5 -3
- package/node_modules/@sap/cds-mtxs/srv/plugins/hana/srv-mgr.js +3 -2
- package/node_modules/@sap/cds-mtxs/srv/plugins/hana/tms.js +17 -0
- package/node_modules/@sap/cds-mtxs/srv/plugins/hana.js +18 -21
- 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/sax/lib/sax.js +27 -9
- package/node_modules/sax/package.json +1 -1
- 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 +5 -5
- 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 -372
- package/lib/import/asyncapi/utils.js +0 -49
- package/lib/init/template/nodejs/files/.gitignore.hbs +0 -34
- package/node_modules/@sap/cds/libx/_runtime/ucl/queries.js +0 -61
- package/node_modules/@sap/cds-mtxs/lib/migration/extension-project-builder.js +0 -129
- package/node_modules/@sap/cds-mtxs/lib/migration/extension-project.js +0 -89
- package/node_modules/@sap/cds-mtxs/lib/migration/mtx-adapter.js +0 -230
- 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
|
@@ -22,9 +22,10 @@ const authorizedRequest = ({ method, uri, path, oa2, tenant, dataObj, headers, t
|
|
|
22
22
|
method
|
|
23
23
|
}
|
|
24
24
|
|
|
25
|
+
// EMIS management API requires a body for write requests, even if empty
|
|
25
26
|
let data
|
|
26
|
-
if (dataObj) {
|
|
27
|
-
data = JSON.stringify(dataObj)
|
|
27
|
+
if (dataObj || method === 'PUT' || method === 'POST') {
|
|
28
|
+
data = JSON.stringify(dataObj ?? {})
|
|
28
29
|
httpOptions.headers['Content-Type'] = 'application/json'
|
|
29
30
|
httpOptions.headers['Content-Length'] = Buffer.byteLength(data)
|
|
30
31
|
}
|
|
@@ -20,16 +20,18 @@ class EndpointRegistry {
|
|
|
20
20
|
this.webhookCallbacks = new Map()
|
|
21
21
|
this.deployCallbacks = new Map()
|
|
22
22
|
|
|
23
|
-
const
|
|
23
|
+
const _HAS_APP_AUTH = !!(
|
|
24
24
|
cds.env.requires.auth &&
|
|
25
25
|
(cds.env.requires.auth.impl || !(cds.env.requires.auth.kind in { mocked: 1, 'mocked-auth': 1 }))
|
|
26
26
|
)
|
|
27
|
+
const _HAS_MESSAGING_XSUAA = !!cds.env.requires.messaging?.xsuaa
|
|
28
|
+
const _IS_SECURED = _HAS_APP_AUTH || _HAS_MESSAGING_XSUAA
|
|
27
29
|
const _IS_PRODUCTION = process.env.NODE_ENV === 'production'
|
|
28
30
|
|
|
29
31
|
cds.app.use(basePath, cds.middlewares.context())
|
|
30
32
|
if (_IS_SECURED) {
|
|
31
33
|
// unofficial XSUAA fallback
|
|
32
|
-
if (
|
|
34
|
+
if (_HAS_MESSAGING_XSUAA) {
|
|
33
35
|
cds.app.use(basePath, _xsuaa_fallback())
|
|
34
36
|
} else {
|
|
35
37
|
cds.app.use(basePath, cds.middlewares.auth())
|
|
@@ -38,7 +40,11 @@ class EndpointRegistry {
|
|
|
38
40
|
cds.app.use(basePath, (_req, _res, next) => {
|
|
39
41
|
next(cds.context.user._is_anonymous ? 401 : undefined)
|
|
40
42
|
})
|
|
41
|
-
} else if (_IS_PRODUCTION)
|
|
43
|
+
} else if (_IS_PRODUCTION) {
|
|
44
|
+
throw new Error(
|
|
45
|
+
'Messaging endpoints require authentication in production. Configure cds.requires.auth or cds.requires.messaging.xsuaa.'
|
|
46
|
+
)
|
|
47
|
+
}
|
|
42
48
|
|
|
43
49
|
cds.app.use(basePath, express.json({ type: 'application/*+json' }))
|
|
44
50
|
cds.app.use(basePath, express.json())
|
|
@@ -1,10 +1,5 @@
|
|
|
1
1
|
const cds = require('../cds')
|
|
2
|
-
const {
|
|
3
|
-
getCloudSdk,
|
|
4
|
-
getCloudSdkConnectivity,
|
|
5
|
-
getCloudSdkResilience,
|
|
6
|
-
shouldUseCloudSdk
|
|
7
|
-
} = require('./utils/cloudSdkProvider')
|
|
2
|
+
const { getCloudSdk, getCloudSdkResilience, shouldUseCloudSdk } = require('./utils/cloudSdkProvider')
|
|
8
3
|
const { run } = require('./utils/client')
|
|
9
4
|
const { extractRequestConfig } = require('./utils/query')
|
|
10
5
|
const { hasAliasedColumns } = require('./utils/data')
|
|
@@ -171,13 +166,6 @@ const _isSelectWithAliasedColumns = q => q?.SELECT && !q._transitions && q.SELEC
|
|
|
171
166
|
|
|
172
167
|
const resolvedTargetOfQuery = q => q?._transitions?.at(-1)?.target
|
|
173
168
|
|
|
174
|
-
const _resolveSelectionStrategy = selectionStrategy => {
|
|
175
|
-
const { DestinationSelectionStrategies } = getCloudSdkConnectivity()
|
|
176
|
-
const strategy = DestinationSelectionStrategies[selectionStrategy]
|
|
177
|
-
if (typeof strategy !== 'function') cds.error`Unsupported destination selection strategy "${selectionStrategy}".`
|
|
178
|
-
return strategy
|
|
179
|
-
}
|
|
180
|
-
|
|
181
169
|
const _getKind = options => {
|
|
182
170
|
let kind = options.credentials?.kind ?? options.kind
|
|
183
171
|
if (typeof kind === 'object') {
|
|
@@ -206,9 +194,6 @@ class RemoteService extends cds.Service {
|
|
|
206
194
|
this.datasource = this.options.datasource
|
|
207
195
|
|
|
208
196
|
this.destinationOptions = this.options.destinationOptions || {}
|
|
209
|
-
if (typeof this.destinationOptions.selectionStrategy === 'string') {
|
|
210
|
-
this.destinationOptions.selectionStrategy = _resolveSelectionStrategy(this.destinationOptions.selectionStrategy)
|
|
211
|
-
}
|
|
212
197
|
|
|
213
198
|
if (this.options.credentials.destination) this.destination = this.options.credentials.destination
|
|
214
199
|
else this.destination = _getDestination(this.definition?.name ?? this.datasource, this.options.credentials)
|
|
@@ -223,7 +208,7 @@ class RemoteService extends cds.Service {
|
|
|
223
208
|
|
|
224
209
|
// This is required for BAS creating remote services only for events
|
|
225
210
|
// Middlewares are created at time of the first request, on the fly
|
|
226
|
-
if (shouldUseCloudSdk(
|
|
211
|
+
if (shouldUseCloudSdk()) {
|
|
227
212
|
this.middlewares = {
|
|
228
213
|
timeout: getCloudSdkResilience().timeout(this.requestTimeout),
|
|
229
214
|
csrf: this.csrf && getCloudSdk().csrf(this.csrf)
|
|
@@ -249,7 +234,7 @@ class RemoteService extends cds.Service {
|
|
|
249
234
|
if (typeof this.destination === 'object' && !this.destination.url)
|
|
250
235
|
cds.error`"url" or "destination" property must be configured in "credentials" of "${this.name}".`
|
|
251
236
|
|
|
252
|
-
const requestConfig = extractRequestConfig(req,
|
|
237
|
+
const requestConfig = extractRequestConfig(req, this)
|
|
253
238
|
requestConfig.headers = _getHeaders(requestConfig.headers, req)
|
|
254
239
|
|
|
255
240
|
if (this.kind === 'hcql' && req.iterator && !req.objectMode)
|
|
@@ -284,7 +269,7 @@ class RemoteService extends cds.Service {
|
|
|
284
269
|
if (jwt) additionalOptions.jwt = jwt
|
|
285
270
|
|
|
286
271
|
const csrfConfig = requestConfig._autoBatchedGet ? this.csrfInBatch : this.csrf
|
|
287
|
-
if (shouldUseCloudSdk(
|
|
272
|
+
if (shouldUseCloudSdk()) {
|
|
288
273
|
// Mount resilience and csrf middlewares for SAP Cloud SDK
|
|
289
274
|
requestConfig.middleware = [this.middlewares.timeout]
|
|
290
275
|
if (csrfConfig && this.middlewares.csrf) requestConfig.middleware.push(this.middlewares.csrf)
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* LRU cache backed by Map insertion order.
|
|
3
|
+
* On get/set the entry is moved to the MRU end; the LRU entry (first key) is
|
|
4
|
+
* evicted when the map is full. `maxSize = 0` means unlimited.
|
|
5
|
+
*/
|
|
6
|
+
class LruCache {
|
|
7
|
+
/** @param {number} [maxSize=0] Maximum number of entries; 0 = unlimited. */
|
|
8
|
+
constructor(maxSize = 0) {
|
|
9
|
+
this._map = new Map()
|
|
10
|
+
this._maxSize = maxSize
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
/** @param {*} key Cache key. */
|
|
14
|
+
get(key) {
|
|
15
|
+
const value = this._map.get(key)
|
|
16
|
+
if (value !== undefined) {
|
|
17
|
+
this._map.delete(key)
|
|
18
|
+
this._map.set(key, value)
|
|
19
|
+
}
|
|
20
|
+
return value
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* @param {*} key Cache key. `null` and `undefined` are ignored.
|
|
25
|
+
* @param {*} value Value to store.
|
|
26
|
+
* @param {number} [ttl] Time-to-live in milliseconds. Entry is auto-deleted after this duration.
|
|
27
|
+
*/
|
|
28
|
+
set(key, value, ttl) {
|
|
29
|
+
if (key == null) return
|
|
30
|
+
this._map.delete(key)
|
|
31
|
+
if (this._maxSize > 0 && this._map.size >= this._maxSize) this._map.delete(this._map.keys().next().value)
|
|
32
|
+
this._map.set(key, value)
|
|
33
|
+
if (ttl > 0) setTimeout(() => this._map.delete(key), ttl).unref()
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/** @param {*} key Cache key. */
|
|
37
|
+
delete(key) {
|
|
38
|
+
this._map.delete(key)
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
module.exports = { LruCache }
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
const cds = require('../../cds')
|
|
2
2
|
const LOG = cds.log('remote')
|
|
3
3
|
|
|
4
|
-
const { getCloudSdk, shouldUseCloudSdk } = require('./cloudSdkProvider')
|
|
4
|
+
const { getCloudSdk, getCloudSdkConnectivity, shouldUseCloudSdk } = require('./cloudSdkProvider')
|
|
5
5
|
const { executeHttpRequest: fetchExecuteHttpRequest } = require('./fetchClient')
|
|
6
6
|
|
|
7
7
|
const SANITIZE_VALUES = process.env.NODE_ENV === 'production' && cds.env.log.sanitize_values !== false
|
|
@@ -27,7 +27,6 @@ const _sanitizeHeaders = headers => {
|
|
|
27
27
|
}
|
|
28
28
|
|
|
29
29
|
const _executeHttpRequest = async ({ requestConfig, destination, destinationOptions, jwt }) => {
|
|
30
|
-
const useCloudSdk = shouldUseCloudSdk(destination)
|
|
31
30
|
if (typeof destination === 'string') {
|
|
32
31
|
destination = { destinationName: destination, ...destinationOptions }
|
|
33
32
|
if (destination.jwt === undefined) destination.jwt = jwt
|
|
@@ -48,7 +47,7 @@ const _executeHttpRequest = async ({ requestConfig, destination, destinationOpti
|
|
|
48
47
|
const maxBodyLength = cds.env?.remote?.max_body_length
|
|
49
48
|
if (maxBodyLength) requestConfig.maxBodyLength = maxBodyLength
|
|
50
49
|
|
|
51
|
-
if (
|
|
50
|
+
if (shouldUseCloudSdk()) {
|
|
52
51
|
const { executeHttpRequestWithOrigin } = getCloudSdk()
|
|
53
52
|
// cloud sdk requires a new mechanism to differentiate the priority of headers
|
|
54
53
|
// "custom" keeps the highest priority as before
|
|
@@ -56,6 +55,15 @@ const _executeHttpRequest = async ({ requestConfig, destination, destinationOpti
|
|
|
56
55
|
...requestConfig,
|
|
57
56
|
headers: { custom: { ...requestConfig.headers } }
|
|
58
57
|
}
|
|
58
|
+
// selectionStrategy is kept as a plain string in destinationOptions;
|
|
59
|
+
// Cloud SDK needs the corresponding function — resolve it lazily here
|
|
60
|
+
if (typeof destination.selectionStrategy === 'string') {
|
|
61
|
+
const { DestinationSelectionStrategies } = getCloudSdkConnectivity()
|
|
62
|
+
const strategy = DestinationSelectionStrategies[destination.selectionStrategy]
|
|
63
|
+
if (typeof strategy !== 'function')
|
|
64
|
+
cds.error`Unsupported destination selection strategy "${destination.selectionStrategy}".`
|
|
65
|
+
destination = { ...destination, selectionStrategy: strategy }
|
|
66
|
+
}
|
|
59
67
|
if (LOG._debug) LOG.debug('Executing via @sap-cloud-sdk/http-client.')
|
|
60
68
|
return executeHttpRequestWithOrigin(destination, sdkConfig, { fetchCsrfToken: false })
|
|
61
69
|
}
|
|
@@ -270,8 +278,10 @@ module.exports.run = async (requestConfig, options) => {
|
|
|
270
278
|
|
|
271
279
|
if (kind === 'hcql') {
|
|
272
280
|
if (response.headers?.['content-type']?.includes('application/octet-stream')) return response.data
|
|
273
|
-
let { data = [], affected } = response.data
|
|
281
|
+
let { data = [], affected, $count } = response.data
|
|
274
282
|
if (affected !== undefined) data.affected = affected
|
|
283
|
+
if ($count == null && response.headers['x-total-count']) $count = Number(response.headers['x-total-count'])
|
|
284
|
+
if ($count !== undefined) data.$count = $count
|
|
275
285
|
return data
|
|
276
286
|
}
|
|
277
287
|
|
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
const cds = require('../../cds')
|
|
2
|
-
const { findLocalDestinations } = require('./fetchClient')
|
|
3
2
|
|
|
4
3
|
const _require = module => {
|
|
5
4
|
try {
|
|
@@ -39,14 +38,8 @@ const isCloudSdkInstalled = () => {
|
|
|
39
38
|
return _cloudSdkInstalled
|
|
40
39
|
}
|
|
41
40
|
|
|
42
|
-
const shouldUseCloudSdk =
|
|
43
|
-
//
|
|
44
|
-
const _destination = typeof destination === 'string' ? findLocalDestinations(destination)[0] : destination
|
|
45
|
-
if (!_destination?.url) return true
|
|
46
|
-
if (_destination.authentication && !(_destination.authentication in { NoAuthentication: 1, BasicAuthentication: 1 }))
|
|
47
|
-
return true
|
|
48
|
-
// here, it would be _possible_ to use native fetch
|
|
49
|
-
// explicit config?
|
|
41
|
+
const shouldUseCloudSdk = () => {
|
|
42
|
+
// Explicit config takes priority
|
|
50
43
|
if (cds.env.remote?.native_fetch != null) return !cds.env.remote.native_fetch
|
|
51
44
|
// if cloud sdk is there, use it
|
|
52
45
|
return isCloudSdkInstalled()
|
|
@@ -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')
|