@sap/cds-dk 10.0.7 → 10.1.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +106 -0
- package/bin/add.js +7 -23
- package/bin/bind.js +14 -0
- package/bin/build.js +21 -34
- package/bin/cds.js +1 -1
- package/bin/compile/index.js +16 -41
- package/bin/completion/completionFs.js +11 -8
- package/bin/completion/index.js +603 -80
- package/bin/completion/scripts/cds.fish +620 -0
- package/bin/completion/scripts/cds.ps1 +403 -0
- package/bin/completion/scripts/cds.sh +327 -0
- package/bin/completion/scripts/cds.zsh +296 -0
- package/bin/completion/templates/cds-tmpl.fish +65 -0
- package/bin/completion/templates/cds-tmpl.ps1 +217 -0
- package/bin/completion/templates/cds-tmpl.sh +93 -0
- package/bin/completion/templates/cds-tmpl.zsh +74 -0
- package/bin/deploy/index.js +11 -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/env.js +4 -1
- package/bin/export.js +44 -6
- package/bin/help/export.md +3 -0
- package/bin/help.js +1 -5
- package/bin/import.js +9 -0
- package/bin/init.js +6 -24
- package/bin/inspect.js +8 -0
- package/bin/lint.js +5 -3
- package/bin/login.js +26 -20
- package/bin/minify.js +6 -1
- package/bin/pull.js +19 -6
- package/bin/push.js +19 -6
- package/bin/repl.js +11 -2
- package/bin/run.js +12 -1
- package/bin/serve.js +13 -2
- package/bin/up.js +17 -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 +10 -4
- package/bin/version.js +10 -1
- package/{lib → bin}/watch/hot-reload.js +1 -1
- package/bin/{watch.js → watch/index.js} +24 -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/build/util.js +2 -4
- 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 +117 -42
- package/lib/init/template/data/index.js +16 -2
- package/lib/init/template/destination/index.js +2 -2
- package/lib/init/template/dynatrace/files/package.json.hbs +3 -3
- package/lib/init/template/dynatrace/index.js +2 -2
- package/lib/init/template/enterprise-messaging/index.js +2 -2
- package/lib/init/template/enterprise-messaging-shared/index.js +2 -2
- package/lib/init/template/extensibility/files/mta.yaml.hbs +11 -0
- package/lib/init/template/extensibility/files/xs-security.json.hbs +1 -1
- package/lib/init/template/extensibility/index.js +3 -3
- package/lib/init/template/github-actions/index.js +2 -2
- package/lib/init/template/hana/index.js +3 -2
- package/lib/init/template/hana-serverless/files/db/package.json +14 -0
- package/lib/init/template/hana-serverless/files/identity.values.yaml.hbs +10 -0
- package/lib/init/template/hana-serverless/files/mta.yaml.hbs +64 -0
- package/lib/init/template/hana-serverless/files/package.json.hbs +35 -0
- package/lib/init/template/hana-serverless/files/src/.hdiconfig +139 -0
- package/lib/init/template/hana-serverless/files/undeploy.json +7 -0
- package/lib/init/template/hana-serverless/files/values.yaml.hbs +18 -0
- package/lib/init/template/hana-serverless/index.js +184 -0
- package/lib/init/template/html5-repo/files/containerize.yaml.hbs +2 -2
- package/lib/init/template/html5-repo/files/mta.yaml.hbs +5 -0
- package/lib/init/template/html5-repo/index.js +18 -14
- package/lib/init/template/http/index.js +123 -26
- package/lib/init/template/ias/index.js +2 -2
- package/lib/init/template/kafka/index.js +2 -2
- package/lib/init/template/kyma/index.js +7 -5
- package/lib/init/template/malware-scanner/index.js +2 -2
- package/lib/init/template/mta/files/mta.yaml.hbs +1 -1
- package/lib/init/template/multitenancy/index.js +18 -3
- package/lib/init/template/notifications/index.js +2 -2
- package/lib/init/template/portal/files/mta.yaml.hbs +4 -0
- package/lib/init/template/portal/index.js +13 -8
- package/lib/init/template/postgres/index.js +2 -2
- package/lib/init/template/redis/index.js +2 -2
- package/lib/init/template/sample/files/db/currencies.cds +2 -0
- package/lib/init/template/sample/files/db/data/sap.capire.bookshop-Authors.csv +1 -1
- package/lib/init/template/tiny-sample/files/nodejs/db/data/CatalogService.Books.csv +2 -2
- package/lib/init/template/workzone-standard/index.js +5 -5
- package/lib/init/template/xsuaa/index.js +20 -2
- package/lib/lint/eslintHelp.json +1 -1
- package/lib/mtx/auth_manager.js +103 -37
- package/lib/mtx/params.js +3 -2
- package/lib/mtx/settings_manager.js +45 -28
- package/lib/mtx/tenant_action.js +0 -1
- package/lib/mtx/util/credentials.js +62 -0
- package/node_modules/@cap-js/db-service/CHANGELOG.md +17 -0
- package/node_modules/@cap-js/db-service/lib/common/generic-pool.js +4 -7
- package/node_modules/@cap-js/db-service/lib/cqn2pql.js +1 -1
- package/node_modules/@cap-js/db-service/lib/cqn2sql.js +15 -11
- package/node_modules/@cap-js/db-service/lib/cqn4sql.js +119 -19
- package/node_modules/@cap-js/db-service/lib/infer/index.js +27 -16
- package/node_modules/@cap-js/db-service/lib/recursion-guard.js +40 -0
- package/node_modules/@cap-js/db-service/lib/search.js +1 -1
- package/node_modules/@cap-js/db-service/package.json +1 -1
- package/node_modules/@cap-js/sqlite/CHANGELOG.md +19 -0
- package/node_modules/@cap-js/sqlite/lib/SQLiteService.js +110 -14
- package/node_modules/@cap-js/sqlite/package.json +3 -3
- package/node_modules/@sap/cds/CHANGELOG.md +57 -0
- package/node_modules/@sap/cds/app/index.js +5 -4
- package/node_modules/@sap/cds/eslint.config.mjs +1 -0
- package/node_modules/@sap/cds/lib/compile/minify.js +14 -10
- package/node_modules/@sap/cds/lib/env/cds-env.js +1 -1
- package/node_modules/@sap/cds/lib/env/cds-requires.js +22 -11
- package/node_modules/@sap/cds/lib/env/defaults.js +4 -1
- package/node_modules/@sap/cds/lib/env/schemas/cds-rc.js +5 -2
- package/node_modules/@sap/cds/lib/plugins.js +9 -5
- package/node_modules/@sap/cds/lib/req/spawn.js +1 -1
- package/node_modules/@sap/cds/lib/srv/srv-dispatch.js +1 -1
- package/node_modules/@sap/cds/lib/srv/srv-models.js +1 -1
- package/node_modules/@sap/cds/lib/utils/cds-utils.js +2 -1
- package/node_modules/@sap/cds/libx/_runtime/common/generic/assert.js +2 -3
- package/node_modules/@sap/cds/libx/_runtime/common/generic/auth/restrict.js +8 -1
- package/node_modules/@sap/cds/libx/_runtime/common/generic/crud.js +18 -8
- package/node_modules/@sap/cds/libx/_runtime/fiori/lean-draft.js +25 -17
- package/node_modules/@sap/cds/libx/_runtime/messaging/enterprise-messaging-utils/registerEndpoints.js +9 -3
- package/node_modules/@sap/cds/libx/_runtime/remote/Service.js +4 -19
- package/node_modules/@sap/cds/libx/_runtime/remote/utils/LruCache.js +42 -0
- package/node_modules/@sap/cds/libx/_runtime/remote/utils/client.js +14 -4
- package/node_modules/@sap/cds/libx/_runtime/remote/utils/cloudSdkProvider.js +2 -9
- package/node_modules/@sap/cds/libx/_runtime/remote/utils/destinationClient.js +150 -0
- package/node_modules/@sap/cds/libx/_runtime/remote/utils/error.js +13 -0
- package/node_modules/@sap/cds/libx/_runtime/remote/utils/fetchClient.js +26 -18
- package/node_modules/@sap/cds/libx/_runtime/remote/utils/query.js +34 -23
- package/node_modules/@sap/cds/libx/_runtime/remote/utils/token.js +82 -0
- package/node_modules/@sap/cds/libx/_runtime/ucl/Service.js +36 -189
- package/node_modules/@sap/cds/libx/common/utils/streaming.js +16 -16
- package/node_modules/@sap/cds/libx/http/location.js +3 -3
- package/node_modules/@sap/cds/libx/odata/middleware/batch.js +16 -10
- package/node_modules/@sap/cds/libx/odata/middleware/metadata.js +9 -6
- package/node_modules/@sap/cds/libx/odata/middleware/service-document.js +1 -1
- package/node_modules/@sap/cds/libx/odata/parse/afterburner.js +19 -3
- package/node_modules/@sap/cds/libx/odata/parse/cqn2odata.js +83 -86
- package/node_modules/@sap/cds/libx/queue/SchedulingService.js +17 -2
- package/node_modules/@sap/cds/libx/queue/processing.js +1 -3
- package/node_modules/@sap/cds/libx/rest/RestAdapter.js +3 -7
- package/node_modules/@sap/cds/libx/rest/middleware/delete.js +3 -3
- package/node_modules/@sap/cds/libx/rest/middleware/parse.js +13 -8
- package/node_modules/@sap/cds/libx/rest/middleware/read.js +3 -3
- package/node_modules/@sap/cds/libx/rest/middleware/upsert.js +2 -2
- package/node_modules/@sap/cds/package.json +1 -1
- package/node_modules/@sap/cds-compiler/CHANGELOG.md +75 -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 +48 -18
- 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 +4 -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 +15 -0
- package/node_modules/@sap/cds-compiler/lib/transform/db/associations.js +10 -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/db/flattening.js +4 -3
- package/node_modules/@sap/cds-compiler/lib/transform/effective/annotations.js +40 -0
- package/node_modules/@sap/cds-compiler/lib/transform/effective/flattening.js +17 -1
- package/node_modules/@sap/cds-compiler/lib/transform/effective/main.js +20 -0
- package/node_modules/@sap/cds-compiler/lib/transform/fioriTreeViews.js +12 -1
- package/node_modules/@sap/cds-compiler/lib/transform/forRelationalDB.js +7 -2
- package/node_modules/@sap/cds-compiler/lib/transform/odata/adaptAnnotationRefs.js +11 -5
- package/node_modules/@sap/cds-compiler/lib/transform/translateAssocsToJoins.js +48 -10
- package/node_modules/@sap/cds-compiler/package.json +1 -1
- package/node_modules/@sap/cds-mtxs/CHANGELOG.md +49 -0
- package/node_modules/@sap/cds-mtxs/bin/cds-mtx-migrate.js +26 -5
- package/node_modules/@sap/cds-mtxs/bin/cds-mtx.js +37 -16
- package/node_modules/@sap/cds-mtxs/lib/utils.js +23 -7
- package/node_modules/@sap/cds-mtxs/package.json +1 -1
- package/node_modules/@sap/cds-mtxs/srv/cf/abstract-provisioning-service.js +63 -8
- package/node_modules/@sap/cds-mtxs/srv/cf/saas-provisioning-service.js +1 -1
- package/node_modules/@sap/cds-mtxs/srv/cf/sms-provisioning-service.js +23 -3
- package/node_modules/@sap/cds-mtxs/srv/extensibility/compat/update.js +15 -0
- package/node_modules/@sap/cds-mtxs/srv/extensibility/linter/annotations.js +2 -2
- package/node_modules/@sap/cds-mtxs/srv/extensibility/linter/config.js +2 -0
- package/node_modules/@sap/cds-mtxs/srv/extensibility/linter/index.js +2 -0
- package/node_modules/@sap/cds-mtxs/srv/extensibility/linter/protected-namespace.js +45 -0
- package/node_modules/@sap/cds-mtxs/srv/extensibility-service.js +30 -16
- package/node_modules/@sap/cds-mtxs/srv/jobs-service.js +49 -10
- package/node_modules/@sap/cds-mtxs/srv/model-provider.js +28 -4
- package/node_modules/@sap/cds-mtxs/srv/plugins/common.js +19 -12
- package/node_modules/@sap/cds-mtxs/srv/plugins/hana/ctnr-mgr-base.js +1 -1
- package/node_modules/@sap/cds-mtxs/srv/plugins/hana/hdi-ctnr-mgr.js +6 -3
- package/node_modules/@sap/cds-mtxs/srv/plugins/hana/serverless.js +271 -0
- package/node_modules/@sap/cds-mtxs/srv/plugins/hana.js +7 -10
- package/node_modules/@sap/cds-mtxs/srv/worker/job-worker-pool.js +212 -0
- package/node_modules/@sap/cds-mtxs/srv/worker/job-worker.js +61 -0
- package/node_modules/@sap/xsenv/CHANGELOG.md +7 -0
- package/node_modules/@sap/xsenv/package.json +10 -7
- package/node_modules/body-parser/node_modules/content-type/README.md +2 -0
- package/node_modules/body-parser/node_modules/content-type/dist/index.d.ts +20 -0
- package/node_modules/body-parser/node_modules/content-type/dist/index.js +20 -14
- package/node_modules/body-parser/node_modules/content-type/dist/index.js.map +1 -1
- package/node_modules/body-parser/node_modules/content-type/package.json +1 -1
- package/node_modules/hdb/cfg/VERSION +1 -1
- package/node_modules/hdb/lib/protocol/ClientInfo.js +16 -3
- package/node_modules/hdb/lib/protocol/Connection.js +1 -0
- package/node_modules/hdb/package.json +3 -2
- package/node_modules/negotiator/lib/accept.js +54 -0
- package/node_modules/negotiator/lib/charset.js +10 -34
- package/node_modules/negotiator/lib/encoding.js +9 -30
- package/node_modules/negotiator/lib/language.js +15 -36
- package/node_modules/negotiator/lib/mediaType.js +16 -134
- package/node_modules/negotiator/node_modules/content-type/LICENSE +22 -0
- package/node_modules/negotiator/node_modules/content-type/README.md +71 -0
- package/node_modules/negotiator/node_modules/content-type/dist/index.d.ts +46 -0
- package/node_modules/negotiator/node_modules/content-type/dist/index.js +176 -0
- package/node_modules/negotiator/node_modules/content-type/dist/index.js.map +1 -0
- package/node_modules/negotiator/node_modules/content-type/package.json +52 -0
- package/node_modules/negotiator/package.json +16 -10
- package/node_modules/qs/.github/THREAT_MODEL.md +3 -3
- package/node_modules/qs/CHANGELOG.md +15 -0
- package/node_modules/qs/README.md +19 -1
- package/node_modules/qs/dist/qs.js +21 -21
- package/node_modules/qs/eslint.config.mjs +1 -1
- package/node_modules/qs/lib/parse.js +3 -4
- package/node_modules/qs/lib/stringify.js +23 -8
- package/node_modules/qs/lib/utils.js +10 -3
- package/node_modules/qs/package.json +4 -4
- package/node_modules/qs/test/parse.js +192 -3
- package/node_modules/qs/test/stringify.js +317 -0
- package/node_modules/qs/test/utils.js +12 -0
- package/node_modules/type-is/node_modules/content-type/README.md +2 -0
- package/node_modules/type-is/node_modules/content-type/dist/index.d.ts +20 -0
- package/node_modules/type-is/node_modules/content-type/dist/index.js +20 -14
- package/node_modules/type-is/node_modules/content-type/dist/index.js.map +1 -1
- package/node_modules/type-is/node_modules/content-type/package.json +1 -1
- package/node_modules/ws/lib/permessage-deflate.js +3 -1
- package/node_modules/ws/package.json +1 -1
- package/package.json +2 -2
- package/bin/completion/scripts/cds-bash.sh +0 -20
- package/bin/completion/scripts/cds-fish.fish +0 -13
- package/bin/completion/scripts/cds-ps.ps1 +0 -19
- package/bin/completion/scripts/cds-zsh.sh +0 -13
- package/lib/deploy/index.js +0 -25
- package/lib/import/asyncapi/importAsyncAPI.js +0 -33
- package/lib/import/asyncapi/index.js +0 -18
- package/lib/import/asyncapi/parserContext.js +0 -454
- package/lib/import/asyncapi/utils.js +0 -49
- package/node_modules/@sap/cds/libx/_runtime/ucl/queries.js +0 -61
- package/node_modules/negotiator/HISTORY.md +0 -114
- /package/{lib → bin}/upgrade/context.js +0 -0
- /package/{lib → bin}/upgrade/migrations/cds10/migrations.json +0 -0
- /package/{lib → bin}/upgrade/preflight/cds-version.js +0 -0
- /package/{lib → bin}/upgrade/preflight/project-type.js +0 -0
- /package/{lib → bin}/upgrade/steps/analyze/classification.js +0 -0
- /package/{lib → bin}/upgrade/steps/analyze/detector.js +0 -0
- /package/{lib → bin}/upgrade/steps/analyze/evaluator.js +0 -0
- /package/{lib → bin}/upgrade/steps/analyze/reporter.js +0 -0
- /package/{lib → bin}/upgrade/steps/analyze/setup.js +0 -0
- /package/{lib → bin}/upgrade/steps/analyze/utils/detection.js +0 -0
- /package/{lib → bin}/upgrade/steps/analyze/utils/files.js +0 -0
- /package/{lib → bin}/upgrade/steps/analyze/utils/grep.js +0 -0
- /package/{lib → bin}/upgrade/steps/analyze/utils/yaml.js +0 -0
- /package/{lib → bin}/upgrade/util/workspaces.js +0 -0
- /package/{lib → bin}/watch/esm-resolve-registration.js +0 -0
- /package/{lib → bin}/watch/livereload-server.js +0 -0
- /package/{lib → bin}/watch/node-watch.js +0 -0
- /package/{lib → bin}/watch/open.js +0 -0
- /package/{lib → bin}/watch/watcher.js +0 -0
- /package/node_modules/@sap/cds-mtxs/srv/worker/{pool.js → model-worker-pool.js} +0 -0
- /package/node_modules/@sap/cds-mtxs/srv/worker/{load.js → model-worker.js} +0 -0
|
@@ -0,0 +1,271 @@
|
|
|
1
|
+
const cds = require('@sap/cds')
|
|
2
|
+
const AbstractContainerManagerClient = require('./ctnr-mgr-base')
|
|
3
|
+
const { token, fetchResiliently } = require('../../../lib/utils')
|
|
4
|
+
const { uuid } = cds.utils
|
|
5
|
+
const LOG = cds.log('mtx'), DEBUG = cds.debug('mtx|serverless')
|
|
6
|
+
|
|
7
|
+
const API = '/serverless/v1/containers'
|
|
8
|
+
|
|
9
|
+
class ServerlessClient extends AbstractContainerManagerClient {
|
|
10
|
+
constructor(credentials) {
|
|
11
|
+
super({ clientid: credentials.identity?.clientid, url: credentials.identity?.url })
|
|
12
|
+
this.gateway = credentials.url
|
|
13
|
+
this.identity = credentials.identity
|
|
14
|
+
this.certificate = this.identity?.certificate
|
|
15
|
+
this.key = this.identity?.key
|
|
16
|
+
this.certurl = this.identity?.certurl
|
|
17
|
+
this.url = this.identity?.url
|
|
18
|
+
this.clientid = this.identity?.clientid
|
|
19
|
+
this.credentialCache = {}
|
|
20
|
+
this.tokenCache = new Map()
|
|
21
|
+
this.delete = this.remove
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
static defaultInstance() {
|
|
25
|
+
ServerlessClient._defaultInstance ??= ServerlessClient.newInstance(ServerlessClient._resolveCredentials())
|
|
26
|
+
return ServerlessClient._defaultInstance
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
static _resolveCredentials() {
|
|
30
|
+
const credentials = { ...cds.env.requires.db.credentials }
|
|
31
|
+
let vcap
|
|
32
|
+
const _vcap = () => (vcap ??= (() => { try { return JSON.parse(process.env.VCAP_SERVICES || '{}') } catch { return {} } })())
|
|
33
|
+
if (!credentials.url) {
|
|
34
|
+
const serverless = (_vcap()['hana-cloud'] || []).find(s => s.plan === 'serverless') || (_vcap()['hana-cloud'] || [])[0]
|
|
35
|
+
const gw = credentials.credentials?.url || serverless?.credentials?.url || serverless?.credentials?.credentials?.url
|
|
36
|
+
if (gw) credentials.url = gw
|
|
37
|
+
}
|
|
38
|
+
const iasBinding = (_vcap().identity || [])[0]?.credentials
|
|
39
|
+
if (iasBinding) credentials.identity = iasBinding
|
|
40
|
+
else if (!credentials.identity) credentials.identity = credentials.credentials?.identity
|
|
41
|
+
if (!credentials.identity?.certificate) {
|
|
42
|
+
const identityBinding = ServerlessClient._resolveIdentityBinding()
|
|
43
|
+
if (identityBinding?.certificate) credentials.identity = identityBinding
|
|
44
|
+
}
|
|
45
|
+
return credentials
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
static _resolveIdentityBinding() {
|
|
49
|
+
const isIdentity = conf => conf?.kind === 'ias' || conf?.vcap?.label === 'identity'
|
|
50
|
+
for (const conf of Object.values(cds.env.requires || {})) {
|
|
51
|
+
if (isIdentity(conf) && conf.credentials?.certificate) return conf.credentials
|
|
52
|
+
}
|
|
53
|
+
return undefined
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
static newInstance(credentials) {
|
|
57
|
+
if (!credentials?.url) cds.error('Serverless gateway URL is not configured in credentials')
|
|
58
|
+
if (!credentials.identity?.certificate) cds.error('No identity (IAS) credentials found for the serverless Tenant API')
|
|
59
|
+
return new ServerlessClient(credentials)
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
_tokenFor = async () => {
|
|
63
|
+
const cached = this.tokenCache.get('provider')
|
|
64
|
+
if (cached && cached.expiry >= Date.now() + 30_000) return `Bearer ${cached.access_token}`
|
|
65
|
+
const raw = await token(this.identity, { form: { grant_type: 'client_credentials' }, path: '/oauth2/token' })
|
|
66
|
+
const { access_token, expires_in } = JSON.parse(raw)
|
|
67
|
+
this.tokenCache.set('provider', { access_token, expiry: Date.now() + expires_in * 1000 })
|
|
68
|
+
return `Bearer ${access_token}`
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
_api = async (tenant, path, conf = {}) => {
|
|
72
|
+
conf.headers ??= {}
|
|
73
|
+
conf.headers.Authorization ??= await this._tokenFor(tenant)
|
|
74
|
+
conf.headers['Accept'] ??= 'application/json'
|
|
75
|
+
conf.headers['Content-Type'] ??= 'application/json'
|
|
76
|
+
conf.headers['X-CorrelationID'] ??= cds.context?.id
|
|
77
|
+
const { retries, maxRetryAfter } = cds.requires?.multitenancy?.serviceManager ?? cds.requires?.multitenancy?.containerManager ?? {}
|
|
78
|
+
return fetchResiliently(this.gateway + path, conf, { retriesLeft: retries ?? 10, maxRetryAfter: maxRetryAfter ?? 5000 })
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
_pollLocation = async (tenant, location, extract) => {
|
|
82
|
+
let attempts = 0
|
|
83
|
+
const maxAttempts = 60, interval = 3000
|
|
84
|
+
for (;;) {
|
|
85
|
+
const { data } = await this._api(tenant, location.startsWith('http') ? location.slice(this.gateway.length) : location, { method: 'GET' })
|
|
86
|
+
const state = extract(data)
|
|
87
|
+
if (state === true) return data
|
|
88
|
+
if (state === 'FAILED') cds.error(`Serverless operation failed for ${location}: ${JSON.stringify(data)}`)
|
|
89
|
+
if (++attempts > maxAttempts) cds.error(`Polling ${location} timed out after ${(interval * maxAttempts) / 1000} seconds`)
|
|
90
|
+
await new Promise(r => setTimeout(r, interval))
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
_matchingContainers = (list, tenant) => {
|
|
95
|
+
const base = this._baseName(tenant)
|
|
96
|
+
return (list || []).filter(c => (c.labels || []).includes(`tenant_id=${tenant}`) || c.name === base || (c.name || '').startsWith(`${base}_`))
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
_findContainer = async (tenant) => {
|
|
100
|
+
const { data } = await this._api(tenant, API, { method: 'GET' })
|
|
101
|
+
const usable = c => !/(FAILED|DELETED|NOT_EXIST)$/.test(c.status?.state || '')
|
|
102
|
+
return this._matchingContainers(data.data, tenant).find(usable)
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
_removeStaleContainers = async (tenant) => {
|
|
106
|
+
const { data } = await this._api(tenant, API, { method: 'GET' })
|
|
107
|
+
const stale = this._matchingContainers(data.data, tenant).filter(c => /FAILED$/.test(c.status?.state || ''))
|
|
108
|
+
for (const c of stale) {
|
|
109
|
+
LOG.info('removing stale serverless container for', { tenant }, 'container', c.name, 'in state', c.status?.state)
|
|
110
|
+
const del = await this._api(tenant, `${API}/${c.id}`, { method: 'DELETE' }).catch(() => ({}))
|
|
111
|
+
const location = del.headers?.['location']
|
|
112
|
+
if (location) await this._pollLocation(tenant, location, d => /(SUCCEEDED|DELETED|NOT_EXIST)$/.test(d.status?.state || '') ? true : false).catch(() => {})
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
_findCredentials = async (tenant, containerId) => {
|
|
117
|
+
const { data } = await this._api(tenant, `${API}/${containerId}/credentials`, { method: 'GET' })
|
|
118
|
+
const list = data.data || []
|
|
119
|
+
return list.find(x => (x.state || '').endsWith('SUCCEEDED')) || list[0]
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
_credentialsDetail = async (tenant, containerId, credentialId) => {
|
|
123
|
+
const { data } = await this._api(tenant, `${API}/${containerId}/credentials/${credentialId}`, { method: 'GET' })
|
|
124
|
+
return this._toHdiCredentials(data)
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
_toHdiCredentials = (c) => ({
|
|
128
|
+
host: c.host,
|
|
129
|
+
port: String(c.port),
|
|
130
|
+
driver: c.driver || 'com.sap.db.jdbc.Driver',
|
|
131
|
+
schema: c.schema,
|
|
132
|
+
hdi_user: c.hdiUser,
|
|
133
|
+
hdi_password: c.hdiPassword,
|
|
134
|
+
user: c.user,
|
|
135
|
+
password: c.password,
|
|
136
|
+
certificate: c.certificate,
|
|
137
|
+
encrypt: true,
|
|
138
|
+
sslTrustAll: false,
|
|
139
|
+
validate_certificate: true,
|
|
140
|
+
})
|
|
141
|
+
|
|
142
|
+
get = async (tenant, options = {}) => {
|
|
143
|
+
const { disableCache } = options
|
|
144
|
+
if (!tenant) throw new Error('Tenant name is required')
|
|
145
|
+
if (!disableCache && this.credentialCache[tenant]) return this.credentialCache[tenant]
|
|
146
|
+
const container = await this._findContainer(tenant)
|
|
147
|
+
if (!container) cds.error(`Tenant '${tenant}' does not exist`, { status: 404 })
|
|
148
|
+
const credential = await this._findCredentials(tenant, container.id)
|
|
149
|
+
if (!credential) cds.error(`Tenant '${tenant}' has no usable credentials`, { status: 404 })
|
|
150
|
+
const credentials = await this._credentialsDetail(tenant, container.id, credential.id)
|
|
151
|
+
LOG.info('resolved serverless container for', { tenant }, 'container', container.name, 'schema', credentials.schema)
|
|
152
|
+
return this.credentialCache[tenant] = { name: container.name, tenant_id: tenant, credentials, container, tags: ['hana'] }
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
_waitContainerReady = async (tenant, containerId) => {
|
|
156
|
+
await this._pollLocation(tenant, `${API}/${containerId}`, d =>
|
|
157
|
+
d.status?.ready === true || d.status?.state?.endsWith('SUCCEEDED') ? true : (d.status?.state?.endsWith('FAILED') ? 'FAILED' : false)
|
|
158
|
+
)
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
_awaitBrokerContainer = async (tenant, attempts = 20, interval = 3000) => {
|
|
162
|
+
for (let i = 0; i < attempts; i++) {
|
|
163
|
+
const container = await this._findContainer(tenant)
|
|
164
|
+
if (container) return container
|
|
165
|
+
await new Promise(r => setTimeout(r, interval))
|
|
166
|
+
}
|
|
167
|
+
return undefined
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
create = async (tenant, parameters) => {
|
|
171
|
+
LOG.info('creating serverless HDI container for', { tenant })
|
|
172
|
+
let container = await this._findContainer(tenant)
|
|
173
|
+
const t0 = cds.env.requires.multitenancy?.t0 ?? 't0'
|
|
174
|
+
if (!container && tenant !== t0 && cds.env.requires.multitenancy?.serverless?.autoSubscription) {
|
|
175
|
+
LOG.info('awaiting broker-provisioned serverless container for', { tenant })
|
|
176
|
+
container = await this._awaitBrokerContainer(tenant)
|
|
177
|
+
}
|
|
178
|
+
if (!container) {
|
|
179
|
+
await this._removeStaleContainers(tenant)
|
|
180
|
+
const create = await this._api(tenant, API, {
|
|
181
|
+
method: 'POST',
|
|
182
|
+
data: { name: this._newContainerName(tenant), type: 'hdi', labels: [`tenant_id=${tenant}`, ...(parameters?.customLabels ?? [])] },
|
|
183
|
+
})
|
|
184
|
+
const location = create.headers['location']
|
|
185
|
+
await this._pollLocation(tenant, location, d => d.status?.state?.endsWith('SUCCEEDED') ? true : (d.status?.state?.endsWith('FAILED') ? 'FAILED' : false))
|
|
186
|
+
container = await this._findContainer(tenant)
|
|
187
|
+
if (!container) cds.error(`Serverless container for tenant '${tenant}' not found after creation`)
|
|
188
|
+
}
|
|
189
|
+
await this._waitContainerReady(tenant, container.id)
|
|
190
|
+
let credential = await this._findCredentials(tenant, container.id)
|
|
191
|
+
if (!credential) {
|
|
192
|
+
const create = await this._api(tenant, `${API}/${container.id}/credentials`, { method: 'POST', data: {} })
|
|
193
|
+
const location = create.headers['location']
|
|
194
|
+
await this._pollLocation(tenant, location, d => d.state?.endsWith('SUCCEEDED') ? true : (d.state?.endsWith('FAILED') ? 'FAILED' : false))
|
|
195
|
+
credential = await this._findCredentials(tenant, container.id)
|
|
196
|
+
}
|
|
197
|
+
const credentials = await this._credentialsDetail(tenant, container.id, credential.id)
|
|
198
|
+
LOG.info('created serverless container for', { tenant }, 'container', container.name, 'schema', credentials.schema)
|
|
199
|
+
return this.credentialCache[tenant] = { name: container.name, tenant_id: tenant, credentials, container, tags: ['hana'] }
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
getAll = async (tenants = '*', options = {}) => {
|
|
203
|
+
const seed = tenants === '*' ? null : tenants
|
|
204
|
+
const listTenant = seed?.[0]
|
|
205
|
+
let containers = []
|
|
206
|
+
if (listTenant) {
|
|
207
|
+
const { data } = await this._api(listTenant, API, { method: 'GET' })
|
|
208
|
+
containers = (data.data || []).filter(c => (c.labels || []).some(l => l.startsWith('tenant_id=')))
|
|
209
|
+
}
|
|
210
|
+
const results = []
|
|
211
|
+
for (const c of containers) {
|
|
212
|
+
const tenant_id = (c.labels.find(l => l.startsWith('tenant_id=')) || '').split('=')[1]
|
|
213
|
+
if (!tenant_id) continue
|
|
214
|
+
const entry = { labels: { tenant_id: [tenant_id] }, container: c }
|
|
215
|
+
if (options.skipCredentials) { results.push(entry); continue }
|
|
216
|
+
try {
|
|
217
|
+
const { credentials } = await this.get(tenant_id, options)
|
|
218
|
+
results.push({ ...entry, credentials })
|
|
219
|
+
} catch (e) {
|
|
220
|
+
if (e.status === 404) DEBUG?.('skipping tenant', tenant_id, 'without usable credentials')
|
|
221
|
+
else throw e
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
return results
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
remove = async (tenant) => {
|
|
228
|
+
const container = await this._findContainer(tenant)
|
|
229
|
+
if (!container) { delete this.credentialCache[tenant]; return {} }
|
|
230
|
+
const { data } = await this._api(tenant, `${API}/${container.id}/credentials`, { method: 'GET' })
|
|
231
|
+
for (const cred of data.data || []) {
|
|
232
|
+
const del = await this._api(tenant, `${API}/${container.id}/credentials/${cred.id}`, { method: 'DELETE' })
|
|
233
|
+
const location = del.headers['location']
|
|
234
|
+
if (location) await this._pollLocation(tenant, location, d => d.state?.endsWith('SUCCEEDED') || d.state?.endsWith('DELETED') ? true : false).catch(() => {})
|
|
235
|
+
}
|
|
236
|
+
const del = await this._api(tenant, `${API}/${container.id}`, { method: 'DELETE' })
|
|
237
|
+
const location = del.headers['location']
|
|
238
|
+
if (location) await this._pollLocation(tenant, location, d => d.status?.state?.endsWith('SUCCEEDED') || d.status?.state?.endsWith('DELETED') ? true : false).catch(() => {})
|
|
239
|
+
delete this.credentialCache[tenant]
|
|
240
|
+
return { container: container.id }
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
_createParams = (tenant, params = {}, metadata) => {
|
|
244
|
+
const hdiParams = this._hdiParams(tenant, params, metadata) ?? {}
|
|
245
|
+
const result = {}
|
|
246
|
+
const { custom_label } = hdiParams?.create ?? {}
|
|
247
|
+
if (custom_label) {
|
|
248
|
+
const [k] = custom_label.split('=')
|
|
249
|
+
if (k === 'tenant_id') cds.error('"tenant_id" is not allowed as a custom label key')
|
|
250
|
+
result.customLabels = [custom_label]
|
|
251
|
+
}
|
|
252
|
+
return Object.keys(result).length > 0 ? result : null
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
_encryptionParams = () => {
|
|
256
|
+
return {}
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
_baseName = (tenant) => {
|
|
260
|
+
let sanitized = String(tenant).replace(/[^A-Za-z0-9_]/g, '_').toUpperCase()
|
|
261
|
+
if (!/^[A-Za-z0-9]/.test(sanitized)) sanitized = `T${sanitized}`
|
|
262
|
+
return sanitized.slice(0, 24)
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
_newContainerName = (tenant) => {
|
|
266
|
+
const suffix = uuid().replace(/-/g, '').toUpperCase()
|
|
267
|
+
return `${this._baseName(tenant)}_${suffix}`.slice(0, 62)
|
|
268
|
+
}
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
module.exports = ServerlessClient
|
|
@@ -15,8 +15,9 @@ const resources = new Map()
|
|
|
15
15
|
|
|
16
16
|
const { deepMerge } = require('../../lib/utils')
|
|
17
17
|
|
|
18
|
-
if (db?.kind === 'hana') {
|
|
19
|
-
|
|
18
|
+
if (db?.kind === 'hana' || db?.kind === 'hana-serverless') {
|
|
19
|
+
const serverless = db.serverless || db.kind === 'hana-serverless' || (db.credentials?.url && db.credentials?.identity)
|
|
20
|
+
if (!db.credentials?.sm_url && !db.credentials?.baseurl && !serverless) cds.error('No Service Manager credentials found. Make sure the application is bound to a BTP Service Manager instance.')
|
|
20
21
|
|
|
21
22
|
const hana = require('./hana/hdi-ctnr-mgr').defaultInstance()
|
|
22
23
|
exports.activated = 'HANA Database'
|
|
@@ -324,14 +325,10 @@ async function _deploy (req, _container, { skipExt = false, skipResources = fals
|
|
|
324
325
|
if (cacheBindings) {
|
|
325
326
|
// health-check credentials for DB connection, get uncached if stale
|
|
326
327
|
|
|
327
|
-
let driver
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
...(process.env.NODE_ENV !== 'production' && projectPackage.devDependencies),
|
|
332
|
-
}
|
|
333
|
-
if (dependencies.hdb) driver = 'hdb'
|
|
334
|
-
if (dependencies['@sap/hana-client']) driver = '@sap/hana-client'
|
|
328
|
+
let driver
|
|
329
|
+
try { require.resolve('hdb'); driver = 'hdb' } catch { /* continue */ }
|
|
330
|
+
if (!driver) try { require.resolve('@sap/hana-client'); driver = '@sap/hana-client' } catch { /* continue */ }
|
|
331
|
+
if (!driver) cds.error('No HANA client driver found. Add hdb or @sap/hana-client to your sidecar dependencies.')
|
|
335
332
|
|
|
336
333
|
const client = require(driver).createClient(container.credentials)
|
|
337
334
|
const connect = promisify(client.connect.bind(client))
|
|
@@ -0,0 +1,212 @@
|
|
|
1
|
+
'use strict'
|
|
2
|
+
|
|
3
|
+
const { Worker } = require('worker_threads')
|
|
4
|
+
const path = require('path')
|
|
5
|
+
const cds = require('@sap/cds')
|
|
6
|
+
|
|
7
|
+
const LOG = cds.log('mtx')
|
|
8
|
+
const WORKER_PATH = path.join(__dirname, 'job-worker.js')
|
|
9
|
+
|
|
10
|
+
const IDLE = 'idle'
|
|
11
|
+
const BUSY = 'busy'
|
|
12
|
+
const BOOTING = 'booting'
|
|
13
|
+
|
|
14
|
+
class DeploymentPool {
|
|
15
|
+
|
|
16
|
+
constructor(size) {
|
|
17
|
+
this.size = size
|
|
18
|
+
this.workers = [] // { worker, state, pending: Map<id, {resolve,reject}> }
|
|
19
|
+
this.waitQueue = [] // queued acquire requests: { resolve, reject }
|
|
20
|
+
this._idCounter = 0
|
|
21
|
+
this._shuttingDown = false
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* Spawn a new worker, bootstrap it, and add it to the pool.
|
|
26
|
+
* Returns a promise that resolves when the worker is ready.
|
|
27
|
+
*/
|
|
28
|
+
_spawn() {
|
|
29
|
+
return new Promise((resolve, reject) => {
|
|
30
|
+
const worker = new Worker(WORKER_PATH, { env: process.env, workerData: { cdsEnv: cds.env, cdsRoot: cds.root } })
|
|
31
|
+
const entry = { worker, state: BOOTING, pending: new Map() }
|
|
32
|
+
this.workers.push(entry)
|
|
33
|
+
|
|
34
|
+
const onReady = (msg) => {
|
|
35
|
+
if (msg.ready) {
|
|
36
|
+
worker.off('message', onReady)
|
|
37
|
+
worker.off('error', onError)
|
|
38
|
+
entry.state = IDLE
|
|
39
|
+
worker.on('message', (msg) => this._onMessage(entry, msg))
|
|
40
|
+
worker.on('error', (err) => this._onError(entry, err))
|
|
41
|
+
worker.on('exit', (code) => this._onExit(entry, code))
|
|
42
|
+
resolve(entry)
|
|
43
|
+
} else if (msg.error) {
|
|
44
|
+
worker.off('message', onReady)
|
|
45
|
+
worker.off('error', onError)
|
|
46
|
+
this.workers = this.workers.filter(e => e !== entry)
|
|
47
|
+
reject(new Error(msg.error))
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
const onError = (err) => {
|
|
52
|
+
worker.off('message', onReady)
|
|
53
|
+
this.workers = this.workers.filter(e => e !== entry)
|
|
54
|
+
reject(err)
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
worker.on('message', onReady)
|
|
58
|
+
worker.on('error', onError)
|
|
59
|
+
})
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* Handle messages from a worker (command responses).
|
|
64
|
+
*/
|
|
65
|
+
_onMessage(entry, { id, result, error }) {
|
|
66
|
+
const pending = entry.pending.get(id)
|
|
67
|
+
if (!pending) return
|
|
68
|
+
entry.pending.delete(id)
|
|
69
|
+
if (error) pending.reject(new Error(error))
|
|
70
|
+
else pending.resolve(result)
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
/**
|
|
74
|
+
* Handle worker errors.
|
|
75
|
+
*/
|
|
76
|
+
_onError(entry, err) {
|
|
77
|
+
LOG.error('Deployment worker error:', err.message)
|
|
78
|
+
// Reject all pending tasks on this worker
|
|
79
|
+
for (const [, { reject }] of entry.pending) {
|
|
80
|
+
reject(new Error(`Worker error: ${err.message}`))
|
|
81
|
+
}
|
|
82
|
+
entry.pending.clear()
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
/**
|
|
86
|
+
* Handle worker exit — reject in-flight tasks and replace the worker.
|
|
87
|
+
*/
|
|
88
|
+
_onExit(entry, code) {
|
|
89
|
+
this.workers = this.workers.filter(e => e !== entry)
|
|
90
|
+
|
|
91
|
+
// Reject any in-flight tasks
|
|
92
|
+
for (const [, { reject }] of entry.pending) {
|
|
93
|
+
reject(new Error(`Worker exited unexpectedly with code ${code}`))
|
|
94
|
+
}
|
|
95
|
+
entry.pending.clear()
|
|
96
|
+
|
|
97
|
+
// Do not replace workers during shutdown
|
|
98
|
+
if (this._shuttingDown) return
|
|
99
|
+
|
|
100
|
+
// Immediately replace the crashed worker
|
|
101
|
+
LOG.warn('Deployment worker exited with code', code, '- spawning replacement')
|
|
102
|
+
this._spawn().then(newEntry => {
|
|
103
|
+
// If there are queued requests waiting, serve them
|
|
104
|
+
if (this.waitQueue.length > 0 && newEntry.state === IDLE) {
|
|
105
|
+
newEntry.state = BUSY
|
|
106
|
+
const { resolve } = this.waitQueue.shift()
|
|
107
|
+
resolve(newEntry)
|
|
108
|
+
}
|
|
109
|
+
}).catch(err => {
|
|
110
|
+
LOG.error('Failed to spawn replacement worker:', err.message)
|
|
111
|
+
// If there are queued requests waiting, reject the first one
|
|
112
|
+
if (this.waitQueue.length > 0) {
|
|
113
|
+
const { reject } = this.waitQueue.shift()
|
|
114
|
+
reject(err)
|
|
115
|
+
}
|
|
116
|
+
})
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
/**
|
|
120
|
+
* Acquire an idle worker. If none available and pool not full, spawns a new one.
|
|
121
|
+
* If all workers are busy and pool is full, queues the request.
|
|
122
|
+
*/
|
|
123
|
+
async acquire() {
|
|
124
|
+
// Try to find an idle worker
|
|
125
|
+
const idle = this.workers.find(e => e.state === IDLE)
|
|
126
|
+
if (idle) {
|
|
127
|
+
idle.state = BUSY
|
|
128
|
+
return idle
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
// Pool not full — spawn a new worker
|
|
132
|
+
if (this.workers.length < this.size) {
|
|
133
|
+
const entry = await this._spawn()
|
|
134
|
+
entry.state = BUSY
|
|
135
|
+
return entry
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
// All busy — wait in queue
|
|
139
|
+
return new Promise((resolve, reject) => {
|
|
140
|
+
this.waitQueue.push({ resolve, reject })
|
|
141
|
+
})
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
/**
|
|
145
|
+
* Release a worker back to idle state. Serves the next queued request if any.
|
|
146
|
+
*/
|
|
147
|
+
release(entry) {
|
|
148
|
+
if (this.waitQueue.length > 0) {
|
|
149
|
+
entry.state = BUSY
|
|
150
|
+
const { resolve } = this.waitQueue.shift()
|
|
151
|
+
resolve(entry)
|
|
152
|
+
} else {
|
|
153
|
+
entry.state = IDLE
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
/**
|
|
158
|
+
* Execute a service action on a worker.
|
|
159
|
+
* Acquires a worker, sends the command, waits for the result, releases the worker.
|
|
160
|
+
*/
|
|
161
|
+
async exec(service, op, tenant, args) {
|
|
162
|
+
const entry = await this.acquire()
|
|
163
|
+
const id = ++this._idCounter
|
|
164
|
+
try {
|
|
165
|
+
const result = await new Promise((resolve, reject) => {
|
|
166
|
+
entry.pending.set(id, { resolve, reject })
|
|
167
|
+
entry.worker.postMessage({ id, service, op, tenant, args, cdsEnv: cds.env })
|
|
168
|
+
})
|
|
169
|
+
return result
|
|
170
|
+
} finally {
|
|
171
|
+
this.release(entry)
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
/**
|
|
176
|
+
* Shut down all workers. Sends a shutdown message to each worker and waits for them to exit.
|
|
177
|
+
*/
|
|
178
|
+
async shutdown() {
|
|
179
|
+
this._shuttingDown = true
|
|
180
|
+
|
|
181
|
+
// Reject all queued acquire requests
|
|
182
|
+
for (const { reject } of this.waitQueue) {
|
|
183
|
+
reject(new Error('Worker pool is shutting down'))
|
|
184
|
+
}
|
|
185
|
+
this.waitQueue = []
|
|
186
|
+
|
|
187
|
+
// Send shutdown message to all workers and wait for them to exit
|
|
188
|
+
await Promise.all(this.workers.map(entry => {
|
|
189
|
+
for (const [, { reject }] of entry.pending) {
|
|
190
|
+
reject(new Error('Worker pool is shutting down'))
|
|
191
|
+
}
|
|
192
|
+
entry.pending.clear()
|
|
193
|
+
entry.worker.postMessage({ shutdown: true })
|
|
194
|
+
return new Promise(resolve => entry.worker.once('exit', resolve))
|
|
195
|
+
}))
|
|
196
|
+
this.workers = []
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
let _pool
|
|
201
|
+
|
|
202
|
+
/**
|
|
203
|
+
* Returns the singleton DeploymentPool instance.
|
|
204
|
+
* @param {number} size - Pool size (number of workers)
|
|
205
|
+
*/
|
|
206
|
+
module.exports = function getDeploymentPool(size) {
|
|
207
|
+
if (!_pool) {
|
|
208
|
+
_pool = new DeploymentPool(size)
|
|
209
|
+
cds.on('shutdown', async () => _pool.shutdown())
|
|
210
|
+
}
|
|
211
|
+
return _pool
|
|
212
|
+
}
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
'use strict'
|
|
2
|
+
|
|
3
|
+
const { parentPort, workerData } = require('worker_threads')
|
|
4
|
+
const cds = require('@sap/cds')
|
|
5
|
+
const { _import, isfile, path } = cds.utils
|
|
6
|
+
|
|
7
|
+
if (workerData?.cdsRoot) cds.root = workerData.cdsRoot
|
|
8
|
+
if (workerData?.cdsEnv) cds.env.add(workerData.cdsEnv)
|
|
9
|
+
|
|
10
|
+
async function bootstrap() {
|
|
11
|
+
const services = [
|
|
12
|
+
'@sap/cds-mtxs/srv/deployment-service',
|
|
13
|
+
'@sap/cds-mtxs/srv/model-provider',
|
|
14
|
+
'@sap/cds-mtxs/srv/cf/saas-provisioning-service',
|
|
15
|
+
'@sap/cds-mtxs/srv/extensibility-service',
|
|
16
|
+
]
|
|
17
|
+
await _local_cli_js()
|
|
18
|
+
cds.emit('bootstrap')
|
|
19
|
+
const model = await cds.load([...services, '@sap/cds-mtxs/db/extensions'])
|
|
20
|
+
cds.model = cds.compile.for.nodejs(model)
|
|
21
|
+
const unserved = !cds.services['cds.xt.DeploymentService']
|
|
22
|
+
if (unserved) await cds.serve(services)
|
|
23
|
+
await cds.plugins
|
|
24
|
+
await cds.emit('served', cds.services)
|
|
25
|
+
parentPort.postMessage({ ready: true })
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
async function _local_cli_js() {
|
|
29
|
+
const _local = file => isfile(file) || isfile(path.join(cds.env.folders.srv, file))
|
|
30
|
+
const cli_js = process.env.CDS_TYPESCRIPT && (_local('cli.ts') || _local('server.ts')) || _local('cli.js') || _local('server.js')
|
|
31
|
+
if (cli_js) await _import(cli_js)
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
parentPort.on('message', async (msg) => {
|
|
35
|
+
if (msg.shutdown) {
|
|
36
|
+
await cds.shutdown()
|
|
37
|
+
process.exit(0)
|
|
38
|
+
}
|
|
39
|
+
const { id, service, op, tenant, args, cdsEnv } = msg
|
|
40
|
+
if (cdsEnv) cds.env.add(cdsEnv)
|
|
41
|
+
const srv = cds.services[service]
|
|
42
|
+
if (!srv) {
|
|
43
|
+
parentPort.postMessage({ id, error: `Service ${service} not available` })
|
|
44
|
+
return
|
|
45
|
+
}
|
|
46
|
+
try {
|
|
47
|
+
const result = await srv[op](tenant, ...Object.values(args))
|
|
48
|
+
parentPort.postMessage({ id, result })
|
|
49
|
+
} catch (e) {
|
|
50
|
+
const error = e.message ?? 'Unknown error'
|
|
51
|
+
parentPort.postMessage({ id, error })
|
|
52
|
+
} finally {
|
|
53
|
+
// cds.db.disconnect(cds.env.requires.multitenancy.t0 ?? 't0')
|
|
54
|
+
// cds.db.disconnect(tenant)
|
|
55
|
+
}
|
|
56
|
+
})
|
|
57
|
+
|
|
58
|
+
bootstrap().catch(e => {
|
|
59
|
+
parentPort.postMessage({ error: `Bootstrap failed: ${e.message}` })
|
|
60
|
+
process.exit(1)
|
|
61
|
+
})
|
|
@@ -5,6 +5,13 @@ This project adheres to [Semantic Versioning](http://semver.org/).
|
|
|
5
5
|
|
|
6
6
|
The format is based on [Keep a Changelog](http://keepachangelog.com/).
|
|
7
7
|
|
|
8
|
+
## 6.2.2 - 2026-08-11
|
|
9
|
+
|
|
10
|
+
### Updated
|
|
11
|
+
- Updated `filter-node-package` dependency to 6.2.2
|
|
12
|
+
- Updated `mocha` dependency to 11.8.0
|
|
13
|
+
- Updated `diff` dependency to 9
|
|
14
|
+
|
|
8
15
|
## 6.2.1 - 2026-05-13
|
|
9
16
|
|
|
10
17
|
### Updated
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@sap/xsenv",
|
|
3
|
-
"version": "6.2.
|
|
3
|
+
"version": "6.2.2",
|
|
4
4
|
"description": "Utility for easy setup and access of SAP HANA XS Advanced environment variables",
|
|
5
5
|
"repository": {},
|
|
6
6
|
"license": "SEE LICENSE IN LICENSE",
|
|
@@ -23,13 +23,13 @@
|
|
|
23
23
|
"prepareRelease": "clean-packages -i"
|
|
24
24
|
},
|
|
25
25
|
"devDependencies": {
|
|
26
|
-
"@eslint/js": "9.39.
|
|
26
|
+
"@eslint/js": "9.39.5",
|
|
27
27
|
"@eslint/eslintrc": "3.3.5",
|
|
28
|
-
"@babel/eslint-parser": "7.
|
|
28
|
+
"@babel/eslint-parser": "7.29.7",
|
|
29
29
|
"c8": "11.0.0",
|
|
30
|
-
"filter-node-package": "6.2.
|
|
30
|
+
"filter-node-package": "6.2.2",
|
|
31
31
|
"lodash": "4.18.1",
|
|
32
|
-
"mocha": "11.
|
|
32
|
+
"mocha": "11.8.0",
|
|
33
33
|
"mock-fs": "5.5.0",
|
|
34
34
|
"node-build": "^3.0.0",
|
|
35
35
|
"node-style": "2.0.0",
|
|
@@ -43,7 +43,10 @@
|
|
|
43
43
|
},
|
|
44
44
|
"overrides": {
|
|
45
45
|
"mocha": {
|
|
46
|
-
"glob": "^13.0.0"
|
|
47
|
-
|
|
46
|
+
"glob": "^13.0.0",
|
|
47
|
+
"serialize-javascript": "^7.0.0"
|
|
48
|
+
},
|
|
49
|
+
"diff": "^9.0.0",
|
|
50
|
+
"lodash": "4.18.1"
|
|
48
51
|
}
|
|
49
52
|
}
|
|
@@ -36,6 +36,8 @@ The parser is lenient and does not error. You should validate `type` and `parame
|
|
|
36
36
|
#### Options
|
|
37
37
|
|
|
38
38
|
- `parameters` (default: `true`): Set to `false` to skip parameters.
|
|
39
|
+
- `comma` (default: `false`): Set to `true` to stop on a comma. This can be used to parse the media range in an `Accept` header.
|
|
40
|
+
- `start` (default: `0`): Set index to start parsing from.
|
|
39
41
|
|
|
40
42
|
### contentType.format(obj)
|
|
41
43
|
|
|
@@ -8,6 +8,7 @@
|
|
|
8
8
|
*/
|
|
9
9
|
export interface ContentType {
|
|
10
10
|
type: string;
|
|
11
|
+
index: number;
|
|
11
12
|
parameters: Record<string, string>;
|
|
12
13
|
}
|
|
13
14
|
/**
|
|
@@ -18,7 +19,26 @@ export declare function format(obj: Partial<ContentType>): string;
|
|
|
18
19
|
* Options for parsing a `Content-Type` header.
|
|
19
20
|
*/
|
|
20
21
|
export interface ParseOptions {
|
|
22
|
+
/**
|
|
23
|
+
* Exit early on the first semicolon, returning only the type.
|
|
24
|
+
* This is useful for parsing the MIME from `Content-Type` headers.
|
|
25
|
+
*
|
|
26
|
+
* @default false
|
|
27
|
+
*/
|
|
21
28
|
parameters?: boolean;
|
|
29
|
+
/**
|
|
30
|
+
* Exits early on a comma, returning the first value and parameters.
|
|
31
|
+
* This is useful for parsing `Accept` headers.
|
|
32
|
+
*
|
|
33
|
+
* @default false
|
|
34
|
+
*/
|
|
35
|
+
comma?: boolean;
|
|
36
|
+
/**
|
|
37
|
+
* The index to start parsing from.
|
|
38
|
+
*
|
|
39
|
+
* @default 0
|
|
40
|
+
*/
|
|
41
|
+
start?: number;
|
|
22
42
|
}
|
|
23
43
|
/**
|
|
24
44
|
* Parse a `Content-Type` header.
|