@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
|
@@ -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 }
|
|
@@ -2,12 +2,8 @@ const cds = require('../cds')
|
|
|
2
2
|
const LOG = cds.log('ucl')
|
|
3
3
|
|
|
4
4
|
const { X509Certificate } = require('crypto')
|
|
5
|
-
const fs = require('fs').promises
|
|
6
|
-
const https = require('https')
|
|
7
5
|
const sdkProvider = require('../remote/utils/cloudSdkProvider')
|
|
8
6
|
|
|
9
|
-
const { READ_QUERY, CREATE_MUTATION, UPDATE_MUTATION, DELETE_MUTATION } = require('./queries')
|
|
10
|
-
|
|
11
7
|
const SSL_CLIENT_VERIFY_HEADER = 'x-ssl-client-verify'
|
|
12
8
|
const SSL_CLIENT_SUBJECT_HEADER = 'x-ssl-client-subject-dn'
|
|
13
9
|
const SSL_CLIENT_ISSUER_HEADER = 'x-ssl-client-issuer-dn'
|
|
@@ -172,14 +168,15 @@ module.exports = class UCLService extends cds.Service {
|
|
|
172
168
|
if (!OPERATION_DEFAULT_STATES[operation])
|
|
173
169
|
return cds.error(
|
|
174
170
|
400,
|
|
175
|
-
`Invalid operation "${operation}" in tenant mapping notification.
|
|
176
|
-
OPERATION_DEFAULT_STATES
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
.join(', ')}.`
|
|
171
|
+
`Invalid operation "${operation}" in tenant mapping notification. ` +
|
|
172
|
+
`Expected one of: ${Object.keys(OPERATION_DEFAULT_STATES)
|
|
173
|
+
.map(o => `"${o}"`)
|
|
174
|
+
.join(', ')}.`
|
|
180
175
|
)
|
|
181
176
|
|
|
182
177
|
const locationUrl = req.headers.location
|
|
178
|
+
LOG.debug(`Extracted UCL callback location from header: ${locationUrl}`)
|
|
179
|
+
|
|
183
180
|
if (locationUrl?.length) return this._dispatchAsyncMappingNotification(req, operation, locationUrl)
|
|
184
181
|
return this._dispatchSyncMappingNotification(req, operation)
|
|
185
182
|
}
|
|
@@ -187,10 +184,14 @@ module.exports = class UCLService extends cds.Service {
|
|
|
187
184
|
async _dispatchAsyncMappingNotification(req, operation, locationUrl) {
|
|
188
185
|
// Get destination based on configured destination name
|
|
189
186
|
const destinationName = cds.env.requires.ucl.destination
|
|
187
|
+
|
|
188
|
+
LOG.debug(`Callback destination configured: ${destinationName} (${typeof destinationName})`)
|
|
190
189
|
if (typeof destinationName != 'string' || !destinationName?.length)
|
|
191
190
|
return cds.error(500, 'UCL Notification includes location but no callback destination was configured')
|
|
191
|
+
|
|
192
192
|
const cloudSdkConnectivity = sdkProvider.getCloudSdkConnectivity()
|
|
193
|
-
const destination = await cloudSdkConnectivity.getDestination(destinationName)
|
|
193
|
+
const destination = await cloudSdkConnectivity.getDestination({ destinationName })
|
|
194
|
+
LOG.debug(`Fetched Destination from destination service: ${destination.name}(${destination.url})`)
|
|
194
195
|
|
|
195
196
|
// Make sure the received callback location matches the configured destination
|
|
196
197
|
let destinationHost, locationHost
|
|
@@ -211,16 +212,28 @@ module.exports = class UCLService extends cds.Service {
|
|
|
211
212
|
)
|
|
212
213
|
}
|
|
213
214
|
|
|
215
|
+
const receiverTenantId = req.data?.receiverTenant?.applicationTenantId
|
|
216
|
+
LOG.debug(`Handling '${operation}' asynchronously, as tenant: ${receiverTenantId}`)
|
|
217
|
+
|
|
218
|
+
LOG.debug(`Established destination host for UCL callbacks: ${destinationHost}`)
|
|
219
|
+
LOG.debug(`Established intended UCL callback host from location header: ${locationHost}`)
|
|
220
|
+
|
|
214
221
|
// Schedule the tenant mapping task
|
|
215
|
-
await cds.
|
|
222
|
+
await cds.tx({ tenant: receiverTenantId }, async () => {
|
|
223
|
+
await cds.queued(this).send(operation, req.data, req.headers)
|
|
224
|
+
})
|
|
216
225
|
|
|
217
|
-
|
|
218
|
-
|
|
226
|
+
LOG.debug(`Successfully dispatched '${operation}' operation for: ${receiverTenantId}`)
|
|
227
|
+
|
|
228
|
+
req.http.res.status(202) // Respond '202: Accepted' to UCL
|
|
219
229
|
return
|
|
220
230
|
}
|
|
221
231
|
|
|
222
232
|
async _dispatchSyncMappingNotification(req, operation) {
|
|
223
|
-
const
|
|
233
|
+
const receiverTenantId = req.data?.receiverTenant?.applicationTenantId
|
|
234
|
+
LOG.debug(`Handling '${operation}' synchronously, as tenant: ${receiverTenantId}`)
|
|
235
|
+
|
|
236
|
+
const response = (await cds.tx({ tenant: receiverTenantId }, async () => this.send(operation, req.data))) ?? {}
|
|
224
237
|
if (response.error) req.http.res.status(400)
|
|
225
238
|
else response.state ??= OPERATION_DEFAULT_STATES[operation]
|
|
226
239
|
return response
|
|
@@ -228,8 +241,9 @@ module.exports = class UCLService extends cds.Service {
|
|
|
228
241
|
|
|
229
242
|
async handleSuccessfulMapping(callbackResult, tenantMapping) {
|
|
230
243
|
const { operation, operationId } = tenantMapping.data.context
|
|
244
|
+
if (!callbackResult) callbackResult = {}
|
|
231
245
|
|
|
232
|
-
LOG.debug(`UCL ${operation} operation succeeded:`, callbackResult
|
|
246
|
+
LOG.debug(`UCL ${operation} operation succeeded with result:`, callbackResult)
|
|
233
247
|
|
|
234
248
|
// SPII v3 (operationId exists) vs v2
|
|
235
249
|
const method = operationId ? 'PUT' : 'PATCH'
|
|
@@ -242,7 +256,8 @@ module.exports = class UCLService extends cds.Service {
|
|
|
242
256
|
const cloudSdkHttpClient = sdkProvider.getCloudSdk()
|
|
243
257
|
const response = await cloudSdkHttpClient.executeHttpRequest(
|
|
244
258
|
{ destinationName: cds.env.requires.ucl.destination },
|
|
245
|
-
{ method, url: tenantMapping.headers.location, data }
|
|
259
|
+
{ method, url: tenantMapping.headers.location, data },
|
|
260
|
+
{ fetchCsrfToken: false }
|
|
246
261
|
)
|
|
247
262
|
|
|
248
263
|
if (response.status >= 500)
|
|
@@ -251,6 +266,8 @@ module.exports = class UCLService extends cds.Service {
|
|
|
251
266
|
cds.error(`UCL rejected successful ${operation} operation callback: ${response.data.error}`, {
|
|
252
267
|
unrecoverable: true
|
|
253
268
|
})
|
|
269
|
+
|
|
270
|
+
LOG.debug(`UCL Callback completed with status: ${response.status}`)
|
|
254
271
|
}
|
|
255
272
|
|
|
256
273
|
async handleFailedMapping(callbackError, tenantMapping) {
|
|
@@ -264,7 +281,8 @@ module.exports = class UCLService extends cds.Service {
|
|
|
264
281
|
const cloudSdkHttpClient = sdkProvider.getCloudSdk()
|
|
265
282
|
const response = await cloudSdkHttpClient.executeHttpRequest(
|
|
266
283
|
{ destinationName: cds.env.requires.ucl.destination },
|
|
267
|
-
{ method, url: tenantMapping.headers.location, data: { state: 'ERROR' } }
|
|
284
|
+
{ method, url: tenantMapping.headers.location, data: { state: 'ERROR' } },
|
|
285
|
+
{ fetchCsrfToken: false }
|
|
268
286
|
)
|
|
269
287
|
|
|
270
288
|
if (response.status >= 500)
|
|
@@ -273,178 +291,7 @@ module.exports = class UCLService extends cds.Service {
|
|
|
273
291
|
cds.error(`UCL rejected failed ${operation} operation callback: ${response.data.error}`, {
|
|
274
292
|
unrecoverable: true
|
|
275
293
|
})
|
|
276
|
-
}
|
|
277
|
-
|
|
278
|
-
/*
|
|
279
|
-
* the rest is for upserting the application template
|
|
280
|
-
*/
|
|
281
|
-
|
|
282
|
-
async _upsertApplicationTemplate() {
|
|
283
|
-
const _getApplicationTemplate = options => {
|
|
284
|
-
let applicationTemplate = {
|
|
285
|
-
applicationInput: {
|
|
286
|
-
providerName: 'SAP',
|
|
287
|
-
localTenantID: '{{tenant-id}}',
|
|
288
|
-
labels: {
|
|
289
|
-
displayName: '{{subdomain}}'
|
|
290
|
-
}
|
|
291
|
-
},
|
|
292
|
-
labels: {
|
|
293
|
-
managed_app_provisioning: true,
|
|
294
|
-
xsappname: '${xsappname}'
|
|
295
|
-
},
|
|
296
|
-
placeholders: [
|
|
297
|
-
{ name: 'subdomain', description: 'The subdomain of the consumer tenant' },
|
|
298
|
-
{
|
|
299
|
-
name: 'tenant-id',
|
|
300
|
-
description: "The tenant id as it's known in the product's domain",
|
|
301
|
-
jsonPath: '$.subscribedTenantId'
|
|
302
|
-
}
|
|
303
|
-
],
|
|
304
|
-
accessLevel: 'GLOBAL'
|
|
305
|
-
}
|
|
306
|
-
applicationTemplate = cds.utils.merge(applicationTemplate, options.applicationTemplate)
|
|
307
|
-
|
|
308
|
-
const pkg = require(cds.root + '/package')
|
|
309
|
-
if (!applicationTemplate.name) applicationTemplate.name = pkg.name
|
|
310
|
-
if (!applicationTemplate.applicationInput.name) applicationTemplate.applicationInput.name = pkg.name
|
|
311
|
-
if (applicationTemplate.labels.xsappname === '${xsappname}')
|
|
312
|
-
applicationTemplate.labels.xsappname = options.credentials.xsappname
|
|
313
|
-
|
|
314
|
-
return applicationTemplate
|
|
315
|
-
}
|
|
316
|
-
|
|
317
|
-
this._applicationTemplate = _getApplicationTemplate(cds.env.requires.ucl)
|
|
318
|
-
if (!this._applicationTemplate.applicationNamespace) {
|
|
319
|
-
cds.error(
|
|
320
|
-
'The UCL service requires a valid `applicationTemplate`, please provide it as described in the documentation.'
|
|
321
|
-
)
|
|
322
|
-
}
|
|
323
|
-
|
|
324
|
-
if (!cds.requires.multitenancy && cds.env.profile !== 'mtx-sidecar')
|
|
325
|
-
cds.error(
|
|
326
|
-
'The UCL service requires multitenancy, please enable it in your cds configuration with `cds.requires.multitenancy` or by using the mtx sidecar.'
|
|
327
|
-
)
|
|
328
|
-
if (!cds.env.requires.ucl.credentials)
|
|
329
|
-
cds.error('No credentials found for the UCL service, please bind the service to your app.')
|
|
330
|
-
|
|
331
|
-
if (!cds.env.requires.ucl.x509?.cert && !cds.env.requires.ucl.x509?.certPath)
|
|
332
|
-
cds.error('UCL requires `x509.cert` or `x509.certPath`.')
|
|
333
|
-
if (!cds.env.requires.ucl.x509?.pkey && !cds.env.requires.ucl.x509?.pkeyPath)
|
|
334
|
-
cds.error('UCL requires `x509.pkey` or `x509.pkeyPath`.')
|
|
335
|
-
|
|
336
|
-
const [cert, key] = await Promise.all([
|
|
337
|
-
cds.env.requires.ucl.x509?.cert ??
|
|
338
|
-
fs.readFile(cds.utils.path.resolve(cds.root, cds.env.requires.ucl.x509?.certPath)),
|
|
339
|
-
cds.env.requires.ucl.x509?.pkey ??
|
|
340
|
-
fs.readFile(cds.utils.path.resolve(cds.root, cds.env.requires.ucl.x509?.pkeyPath))
|
|
341
|
-
])
|
|
342
|
-
|
|
343
|
-
this.agent = new https.Agent({ cert, key })
|
|
344
|
-
|
|
345
|
-
const existingTemplate = await this._readTemplate()
|
|
346
|
-
const template = existingTemplate ? await this._updateTemplate(existingTemplate) : await this._createTemplate() // TODO: Make sure return value is correct
|
|
347
|
-
|
|
348
|
-
if (!template) cds.error('The UCL service could not create an application template.')
|
|
349
|
-
|
|
350
|
-
cds.once('listening', async () => {
|
|
351
|
-
const provisioning = await cds.connect.to('cds.xt.SaasProvisioningService')
|
|
352
|
-
provisioning.prepend(() => {
|
|
353
|
-
provisioning.on('dependencies', async (_, next) => {
|
|
354
|
-
const dependencies = await next()
|
|
355
|
-
dependencies.push({ xsappname: template.labels.xsappnameCMPClone })
|
|
356
|
-
return dependencies
|
|
357
|
-
})
|
|
358
|
-
})
|
|
359
|
-
})
|
|
360
|
-
}
|
|
361
|
-
|
|
362
|
-
// REVISIT: Replace with fetch (?)
|
|
363
|
-
async _request(query, variables) {
|
|
364
|
-
// Query GraphQL API
|
|
365
|
-
|
|
366
|
-
const opts = {
|
|
367
|
-
host: cds.env.requires.ucl.host || 'compass-gateway-sap-mtls.mps.kyma.cloud.sap',
|
|
368
|
-
path: cds.env.requires.ucl.path || '/director/graphql',
|
|
369
|
-
agent: this.agent,
|
|
370
|
-
method: 'POST',
|
|
371
|
-
headers: { 'Content-Type': 'application/json' }
|
|
372
|
-
}
|
|
373
|
-
|
|
374
|
-
return new Promise((resolve, reject) => {
|
|
375
|
-
const req = https.request(opts, res => {
|
|
376
|
-
const chunks = []
|
|
377
|
-
|
|
378
|
-
res.on('data', chunk => {
|
|
379
|
-
chunks.push(chunk)
|
|
380
|
-
})
|
|
381
|
-
|
|
382
|
-
res.on('end', () => {
|
|
383
|
-
const response = {
|
|
384
|
-
statusCode: res.statusCode,
|
|
385
|
-
headers: res.headers,
|
|
386
|
-
body: Buffer.concat(chunks).toString()
|
|
387
|
-
}
|
|
388
|
-
|
|
389
|
-
const body = JSON.parse(response.body)
|
|
390
|
-
|
|
391
|
-
if (body.errors) cds.error('Request to UCL service failed with:\n' + JSON.stringify(body.errors, null, 2))
|
|
392
|
-
|
|
393
|
-
resolve(body.data)
|
|
394
|
-
})
|
|
395
|
-
})
|
|
396
|
-
|
|
397
|
-
req.on('error', error => {
|
|
398
|
-
reject(error)
|
|
399
|
-
})
|
|
400
|
-
|
|
401
|
-
if (query) {
|
|
402
|
-
req.write(JSON.stringify({ query, variables }))
|
|
403
|
-
}
|
|
404
|
-
|
|
405
|
-
req.end()
|
|
406
|
-
})
|
|
407
|
-
}
|
|
408
|
-
|
|
409
|
-
_handleResponse(result) {
|
|
410
|
-
if (result.response && result.response.errors) {
|
|
411
|
-
let errorMessage = result.response.errors[0].message
|
|
412
|
-
throw new Error(errorMessage)
|
|
413
|
-
} else {
|
|
414
|
-
return result.result
|
|
415
|
-
}
|
|
416
|
-
}
|
|
417
|
-
|
|
418
|
-
async _readTemplate() {
|
|
419
|
-
const xsappname = cds.env.requires.ucl.credentials.xsappname
|
|
420
|
-
const variables = { key: 'xsappname', value: `"${xsappname}"` }
|
|
421
|
-
const res = await this._request(READ_QUERY, variables)
|
|
422
|
-
if (res) return res.applicationTemplates.data[0]
|
|
423
|
-
}
|
|
424
|
-
|
|
425
|
-
async _createTemplate() {
|
|
426
|
-
try {
|
|
427
|
-
return this._handleResponse(await this._request(CREATE_MUTATION, { input: this._applicationTemplate }))
|
|
428
|
-
} catch (e) {
|
|
429
|
-
this._handleResponse(e)
|
|
430
|
-
}
|
|
431
|
-
}
|
|
432
|
-
|
|
433
|
-
async _updateTemplate(template) {
|
|
434
|
-
try {
|
|
435
|
-
const input = { ...this._applicationTemplate }
|
|
436
|
-
delete input.labels
|
|
437
|
-
const response = this._handleResponse(await this._request(UPDATE_MUTATION, { id: template.id, input }))
|
|
438
|
-
LOG.info('Application template updated successfully.')
|
|
439
|
-
return response
|
|
440
|
-
} catch (e) {
|
|
441
|
-
this._handleResponse(e)
|
|
442
|
-
}
|
|
443
|
-
}
|
|
444
294
|
|
|
445
|
-
|
|
446
|
-
const template = await this._readTemplate()
|
|
447
|
-
if (!template) return
|
|
448
|
-
return this._handleResponse(await this._request(DELETE_MUTATION, { id: template.id }))
|
|
295
|
+
LOG.debug(`UCL Callback completed with status: ${response.status}`)
|
|
449
296
|
}
|
|
450
297
|
}
|
|
@@ -5,7 +5,7 @@ exports.validateMimetypeIsAcceptedOrThrow = (headers, contentType) => {
|
|
|
5
5
|
if (!contentType || !headers?.accept) return
|
|
6
6
|
if (headers.accept.includes('*/*')) return
|
|
7
7
|
if (headers.accept.includes(contentType)) return
|
|
8
|
-
if (headers.accept.includes(contentType.slice(0,contentType.indexOf('/')) + '/*')) return
|
|
8
|
+
if (headers.accept.includes(contentType.slice(0, contentType.indexOf('/')) + '/*')) return
|
|
9
9
|
const msg = `Content type "${contentType}" is not listed in accept header "${headers.accept}"`
|
|
10
10
|
cds.error({ status: 406, message: msg })
|
|
11
11
|
}
|
|
@@ -18,7 +18,7 @@ const _mimetypes = {
|
|
|
18
18
|
'.css': 'text/css',
|
|
19
19
|
'.png': 'image/png',
|
|
20
20
|
'.jpeg': 'image/jpeg',
|
|
21
|
-
'.jpg': 'image/jpeg'
|
|
21
|
+
'.jpg': 'image/jpeg'
|
|
22
22
|
}
|
|
23
23
|
|
|
24
24
|
const { extname } = require('path')
|
|
@@ -28,7 +28,7 @@ const _mimetype4 = filename => {
|
|
|
28
28
|
return _mimetypes[filetype]
|
|
29
29
|
}
|
|
30
30
|
|
|
31
|
-
const _annotation = (def,a) => {
|
|
31
|
+
const _annotation = (def, a) => {
|
|
32
32
|
if (!def) return
|
|
33
33
|
if (typeof def[a] === 'string') return def[a]
|
|
34
34
|
}
|
|
@@ -41,32 +41,32 @@ exports.collectStreamMetadata = (result, operation, query) => {
|
|
|
41
41
|
const filename =
|
|
42
42
|
result.$mediaContentDispositionFilename ?? // legacy -> support for odata only?
|
|
43
43
|
result.filename ??
|
|
44
|
-
_annotation
|
|
45
|
-
_annotation
|
|
44
|
+
_annotation(returns, '@Core.ContentDisposition.Filename') ??
|
|
45
|
+
_annotation(element, '@Core.ContentDisposition.Filename')
|
|
46
46
|
|
|
47
47
|
const disposition =
|
|
48
48
|
result.$mediaContentDispositionType ?? // legacy -> support for odata only?
|
|
49
|
-
_annotation
|
|
50
|
-
_annotation
|
|
49
|
+
_annotation(returns, '@Core.ContentDisposition.Type') ??
|
|
50
|
+
_annotation(element, '@Core.ContentDisposition.Type') ??
|
|
51
51
|
(filename ? 'attachment' : 'inline')
|
|
52
52
|
|
|
53
53
|
const mimetype =
|
|
54
|
-
result['*@odata.mediaContentType'] ??
|
|
55
|
-
result.$mediaContentType ??
|
|
54
|
+
result['*@odata.mediaContentType'] ?? // compat -> support for odata only?
|
|
55
|
+
result.$mediaContentType ?? // legacy -> support for odata only?
|
|
56
56
|
result.mimetype ??
|
|
57
|
-
_mimetype4
|
|
58
|
-
_mimetype4
|
|
59
|
-
_annotation
|
|
60
|
-
_annotation
|
|
57
|
+
_mimetype4(filename) ??
|
|
58
|
+
_mimetype4(result.path) ?? // e.g. for file downloads
|
|
59
|
+
_annotation(returns, '@Core.MediaType') ??
|
|
60
|
+
_annotation(element, '@Core.MediaType') ??
|
|
61
61
|
'application/octet-stream' // REVISIT: or rather default to undefined?
|
|
62
62
|
|
|
63
63
|
return { mimetype, filename, disposition }
|
|
64
64
|
}
|
|
65
65
|
|
|
66
|
-
exports.getReadable = function readable4
|
|
66
|
+
exports.getReadable = function readable4(result) {
|
|
67
67
|
if (result == null) return null
|
|
68
68
|
if (result instanceof Readable) return result
|
|
69
|
-
if (Array.isArray(result)) return readable4
|
|
70
|
-
if (typeof result === 'object' && 'value' in result) return readable4
|
|
69
|
+
if (Array.isArray(result)) return readable4(result[0]) // compat
|
|
70
|
+
if (typeof result === 'object' && 'value' in result) return readable4(result.value)
|
|
71
71
|
cds.error(500, `Unexpected result type for streaming: Expected stream.Readable or null but got ${typeof result}`)
|
|
72
72
|
}
|
|
@@ -21,10 +21,10 @@ module.exports = (target, srv, result, keys_as_segments) => {
|
|
|
21
21
|
return acc
|
|
22
22
|
}, {})
|
|
23
23
|
|
|
24
|
-
if (keys_as_segments) {
|
|
24
|
+
if (keys_as_segments) {
|
|
25
25
|
let location = targetName
|
|
26
|
-
for (const k in keyValuePairs) location += `/${keyValuePairs[k]}`
|
|
27
|
-
return location
|
|
26
|
+
for (const k in keyValuePairs) location += `/${keyValuePairs[k]}`
|
|
27
|
+
return location
|
|
28
28
|
}
|
|
29
29
|
|
|
30
30
|
let keys
|
|
@@ -33,7 +33,7 @@ const _validateProperty = (name, value, type) => {
|
|
|
33
33
|
}
|
|
34
34
|
}
|
|
35
35
|
|
|
36
|
-
const _deconstructAndValidateBatchBody = body => {
|
|
36
|
+
const _deconstructAndValidateBatchBody = (body, ct) => {
|
|
37
37
|
const { requests } = body
|
|
38
38
|
|
|
39
39
|
_validateProperty('requests', requests, 'Array')
|
|
@@ -58,6 +58,12 @@ const _deconstructAndValidateBatchBody = body => {
|
|
|
58
58
|
|
|
59
59
|
const { id, method, url, body, atomicityGroup } = request
|
|
60
60
|
|
|
61
|
+
if (atomicityGroup !== undefined) {
|
|
62
|
+
_validateProperty('atomicityGroup', atomicityGroup, 'string')
|
|
63
|
+
if (ct === 'JSON' && !/^[a-zA-Z0-9._~-]+$/.test(atomicityGroup))
|
|
64
|
+
throw _deserializationError('Invalid characters in atomicityGroup')
|
|
65
|
+
}
|
|
66
|
+
|
|
61
67
|
if (atomicityGroup && atomicityGroups.length && atomicityGroup === atomicityGroups.at(-1)[0]?.atomicityGroup)
|
|
62
68
|
atomicityGroups.at(-1).push(request)
|
|
63
69
|
else atomicityGroups.push([request])
|
|
@@ -78,8 +84,6 @@ const _deconstructAndValidateBatchBody = body => {
|
|
|
78
84
|
throw _deserializationError('A Content-Type header has to be specified for a non JSON body.')
|
|
79
85
|
|
|
80
86
|
if (atomicityGroup) {
|
|
81
|
-
_validateProperty('atomicityGroup', atomicityGroup, 'string')
|
|
82
|
-
|
|
83
87
|
// All request objects with the same value for atomicityGroup MUST be adjacent in the requests array
|
|
84
88
|
if (atomicityGroup !== previousAtomicityGroup) {
|
|
85
89
|
if (requestsById[atomicityGroup])
|
|
@@ -251,7 +255,7 @@ const _formatResponseMultipart = response => {
|
|
|
251
255
|
return [txt]
|
|
252
256
|
}
|
|
253
257
|
|
|
254
|
-
const _writeResponseMultipart = (responses, res, boundary) => {
|
|
258
|
+
const _writeResponseMultipart = (responses, res, boundary, ct) => {
|
|
255
259
|
res.write(`--${boundary}${CRLF}`)
|
|
256
260
|
|
|
257
261
|
const rejected = responses.find(r => r.status === 'fail')
|
|
@@ -261,16 +265,18 @@ const _writeResponseMultipart = (responses, res, boundary) => {
|
|
|
261
265
|
}
|
|
262
266
|
|
|
263
267
|
const groupId = responses[0].atomicityGroup
|
|
264
|
-
|
|
268
|
+
const groupBoundary =
|
|
269
|
+
groupId && (ct !== 'JSON' && /^[a-zA-Z0-9.'+_-]{1,70}$/.test(groupId) ? groupId : `changeset_${cds.utils.uuid()}`)
|
|
270
|
+
if (groupBoundary) res.write(`content-type: multipart/mixed;boundary=${groupBoundary}${CRLF}${CRLF}`)
|
|
265
271
|
|
|
266
272
|
for (const response of responses) {
|
|
267
273
|
for (const each of _formatResponseMultipart(response)) {
|
|
268
|
-
if (
|
|
274
|
+
if (groupBoundary) res.write(`--${groupBoundary}${CRLF}`)
|
|
269
275
|
res.write(`${each}${CRLF}`)
|
|
270
276
|
}
|
|
271
277
|
}
|
|
272
278
|
|
|
273
|
-
if (
|
|
279
|
+
if (groupBoundary) res.write(`--${groupBoundary}--${CRLF}`)
|
|
274
280
|
}
|
|
275
281
|
|
|
276
282
|
/*
|
|
@@ -478,7 +484,7 @@ module.exports = adapter => {
|
|
|
478
484
|
// 1. Each individual request accessible by its id
|
|
479
485
|
// 2. Arrays of requests in the same atomicity group
|
|
480
486
|
// > We will only process requests in atomicity groups
|
|
481
|
-
let { requestsById, atomicityGroups } = _deconstructAndValidateBatchBody(body)
|
|
487
|
+
let { requestsById, atomicityGroups } = _deconstructAndValidateBatchBody(body, ct)
|
|
482
488
|
|
|
483
489
|
const only_gets = body.requests.every(r => r.method === 'GET')
|
|
484
490
|
const max_parallel = only_gets ? cds.env.odata.max_batch_parallelization : 1
|
|
@@ -611,7 +617,7 @@ module.exports = adapter => {
|
|
|
611
617
|
|
|
612
618
|
sendPreludeOnce()
|
|
613
619
|
if (isJson) _writeResponseJson(responses, res)
|
|
614
|
-
else _writeResponseMultipart(responses, res, boundary)
|
|
620
|
+
else _writeResponseMultipart(responses, res, boundary, ct)
|
|
615
621
|
})
|
|
616
622
|
|
|
617
623
|
if (requestsById[groupId]) requestsById[groupId].promise = promise
|
|
@@ -650,7 +656,7 @@ module.exports = adapter => {
|
|
|
650
656
|
// NOTE: error middlewares have 4 params
|
|
651
657
|
// create "subrouter" without the error middlewares for handling subrequests
|
|
652
658
|
subrouter = Object.create(adapter.router)
|
|
653
|
-
subrouter.stack = subrouter.stack.filter(l => l.handle.length
|
|
659
|
+
subrouter.stack = subrouter.stack.filter(l => l.handle.length !== 4)
|
|
654
660
|
// combine adapter and global error middlewares to be invoked during error serialization
|
|
655
661
|
error_mws = adapter.router.stack.filter(l => l.handle.length === 4).map(l => l.handle)
|
|
656
662
|
error_mws = error_mws.concat(cds.middlewares.after.filter(mw => mw.length === 4).map(mw => mw.bind(adapter)))
|
|
@@ -80,8 +80,9 @@ module.exports = adapter => {
|
|
|
80
80
|
}
|
|
81
81
|
}
|
|
82
82
|
|
|
83
|
-
|
|
84
|
-
if (
|
|
83
|
+
// same check as in ctx-model.js
|
|
84
|
+
if (cds.requires.extensibility || cds.requires.toggles) {
|
|
85
|
+
const mps = await cds.connect.to('cds.xt.ModelProviderService')
|
|
85
86
|
if (format === 'json') {
|
|
86
87
|
LOG._warn && LOG.warn('JSON metadata is not supported in case of cds.requires.extensibility: true')
|
|
87
88
|
const msg = 'JSON metadata is not supported for this service'
|
|
@@ -94,12 +95,14 @@ module.exports = adapter => {
|
|
|
94
95
|
let edmx = metadataCache.edm
|
|
95
96
|
|
|
96
97
|
if (!edmx) {
|
|
97
|
-
// IMPORTANT
|
|
98
|
-
// If
|
|
99
|
-
|
|
98
|
+
// IMPORTANT
|
|
99
|
+
// If MPS is served in process, only provide the extended model. If undefined, MPS has a shortcut to return the prebuilt edmx.
|
|
100
|
+
// If MPS is a remote service, never provide the model. It potentially get's corrupted during serialization.
|
|
101
|
+
const model = mps instanceof cds.ApplicationService ? cds.context.model : undefined
|
|
102
|
+
edmx = await mps.getEdmx({ tenant, model, service: service.definition.name })
|
|
100
103
|
metadataCache.edm = edmx
|
|
101
104
|
}
|
|
102
|
-
const extBundle =
|
|
105
|
+
const extBundle = await mps.getI18n({ tenant, locale })
|
|
103
106
|
edmx = cds.localize(service.model, locale, edmx, extBundle)
|
|
104
107
|
metadataCache.xmlEtag[locale] = generateEtag(edmx)
|
|
105
108
|
res.set('ETag', metadataCache.xmlEtag[locale])
|
|
@@ -44,7 +44,7 @@ module.exports = adapter => {
|
|
|
44
44
|
}
|
|
45
45
|
|
|
46
46
|
// service.entities does not work because it excludes the texts entities
|
|
47
|
-
const srvEntities = service.reflect(d => d.
|
|
47
|
+
const srvEntities = service.reflect(d => d.kind === 'entity')
|
|
48
48
|
|
|
49
49
|
const exposedEntities = []
|
|
50
50
|
for (const e in srvEntities) {
|
|
@@ -377,7 +377,7 @@ function _processSegments(from, model, namespace, cqn, protocol) {
|
|
|
377
377
|
current = _getDefinition(current, seg, namespace) || _getDefinition(current, seg.replace(/_/g, '.'), namespace)
|
|
378
378
|
|
|
379
379
|
// REVISIT: 404 or 400?
|
|
380
|
-
if (!current) throw new cds.error(404, `Invalid resource path "${path}"`)
|
|
380
|
+
if (!current || current.kind === 'event') throw new cds.error(404, `Invalid resource path "${path}"`)
|
|
381
381
|
|
|
382
382
|
if (current.params && current.kind === 'entity') {
|
|
383
383
|
// > View with params
|
|
@@ -802,6 +802,20 @@ const _checkAllKeysProvided = (params, entity) => {
|
|
|
802
802
|
cds.error({ status: 400, message: msg })
|
|
803
803
|
}
|
|
804
804
|
}
|
|
805
|
+
for (const keyInParam in params) {
|
|
806
|
+
// REVISIT cds^11: entity.keys is used instead of keysOfEntity because it also covers keys excluded
|
|
807
|
+
// from keysOf (e.g. @cds.api.ignore or up_ FK backlinks) that may still appear in request URLs.
|
|
808
|
+
// Reconsider: it's questionable whether @cds.api.ignore-annotated keys should be accepted in URLs,
|
|
809
|
+
// and current Fiori versions may no longer send requests with up_ FK keys.
|
|
810
|
+
const allowed = isView
|
|
811
|
+
? keyInParam in (entity.params ?? {})
|
|
812
|
+
: entity.keys?.[keyInParam] && !entity.keys[keyInParam].isAssociation
|
|
813
|
+
if (!allowed) {
|
|
814
|
+
// prettier-ignore
|
|
815
|
+
const msg = `${isView ? 'Parameter' : 'Key'} "${keyInParam}" is not a ${isView ? 'parameter' : 'key'} of ${isView ? 'view' : 'entity'} "${entity.name}"`
|
|
816
|
+
cds.error({ status: 400, message: msg })
|
|
817
|
+
}
|
|
818
|
+
}
|
|
805
819
|
}
|
|
806
820
|
|
|
807
821
|
const _doesNotExistError = (isExpand, refName, { name, kind, _return_of }) => {
|
|
@@ -981,8 +995,10 @@ module.exports = (cqn, model, namespace, protocol) => {
|
|
|
981
995
|
}
|
|
982
996
|
}
|
|
983
997
|
|
|
984
|
-
|
|
985
|
-
|
|
998
|
+
let _sel = cqn.SELECT
|
|
999
|
+
while (_sel) {
|
|
1000
|
+
if (_sel.where) _processWhere(_sel.where, target)
|
|
1001
|
+
_sel = _sel.from?.SELECT
|
|
986
1002
|
}
|
|
987
1003
|
|
|
988
1004
|
// one?
|