@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.
Files changed (341) hide show
  1. package/CHANGELOG.md +106 -0
  2. package/bin/add.js +7 -23
  3. package/bin/bind.js +14 -0
  4. package/bin/build.js +21 -34
  5. package/bin/cds.js +1 -1
  6. package/bin/compile/index.js +16 -41
  7. package/bin/completion/completionFs.js +11 -8
  8. package/bin/completion/index.js +603 -80
  9. package/bin/completion/scripts/cds.fish +620 -0
  10. package/bin/completion/scripts/cds.ps1 +403 -0
  11. package/bin/completion/scripts/cds.sh +327 -0
  12. package/bin/completion/scripts/cds.zsh +296 -0
  13. package/bin/completion/templates/cds-tmpl.fish +65 -0
  14. package/bin/completion/templates/cds-tmpl.ps1 +217 -0
  15. package/bin/completion/templates/cds-tmpl.sh +93 -0
  16. package/bin/completion/templates/cds-tmpl.zsh +74 -0
  17. package/bin/deploy/index.js +11 -1
  18. package/bin/deploy/options.js +25 -0
  19. package/{lib → bin}/deploy/to-hana/hana.js +8 -8
  20. package/{lib → bin}/deploy/to-hana/hdiDeployUtil.js +1 -1
  21. package/bin/deploy/{to-hana.js → to-hana/index.js} +1 -1
  22. package/{lib → bin}/deploy/to-hana/mtaUtil.js +1 -1
  23. package/bin/deploy/to-sql.js +0 -2
  24. package/bin/env.js +4 -1
  25. package/bin/export.js +44 -6
  26. package/bin/help/export.md +3 -0
  27. package/bin/help.js +1 -5
  28. package/bin/import.js +9 -0
  29. package/bin/init.js +6 -24
  30. package/bin/inspect.js +8 -0
  31. package/bin/lint.js +5 -3
  32. package/bin/login.js +26 -20
  33. package/bin/minify.js +6 -1
  34. package/bin/pull.js +19 -6
  35. package/bin/push.js +19 -6
  36. package/bin/repl.js +11 -2
  37. package/bin/run.js +12 -1
  38. package/bin/serve.js +13 -2
  39. package/bin/up.js +17 -11
  40. package/{lib → bin}/upgrade/constants.js +1 -1
  41. package/bin/{upgrade.js → upgrade/index.js} +2 -2
  42. package/{lib → bin}/upgrade/preflight/consistency.js +1 -1
  43. package/{lib → bin}/upgrade/preflight/index.js +1 -1
  44. package/{lib → bin}/upgrade/steps/analyze/index.js +1 -1
  45. package/{lib → bin}/upgrade/steps/install.js +1 -1
  46. package/{lib → bin}/upgrade/steps/openrewrite.js +1 -1
  47. package/{lib → bin}/upgrade/steps/removals.js +1 -1
  48. package/{lib → bin}/upgrade/steps/rewrite.js +1 -1
  49. package/{lib → bin}/upgrade/steps/verify.js +1 -1
  50. package/{lib/upgrade/index.js → bin/upgrade/upgrade.js} +2 -2
  51. package/{lib → bin}/upgrade/util/pom.js +2 -2
  52. package/{lib → bin}/upgrade/util/prompt.js +1 -1
  53. package/bin/util/containerize.js +10 -4
  54. package/bin/version.js +10 -1
  55. package/{lib → bin}/watch/hot-reload.js +1 -1
  56. package/bin/{watch.js → watch/index.js} +24 -9
  57. package/{lib → bin}/watch/livereload-connect.js +1 -1
  58. package/{lib → bin}/watch/vite-watch.js +1 -1
  59. package/{lib → bin}/watch/watched.js +2 -2
  60. package/lib/build/constants.js +1 -1
  61. package/lib/build/plugins/fiori/index.js +31 -2
  62. package/lib/build/plugins/internal.js +4 -2
  63. package/lib/build/plugins/mtx-sidecar/index.js +7 -8
  64. package/lib/build/plugins/nodejs/index.js +64 -7
  65. package/lib/build/util.js +2 -4
  66. package/lib/compile/to_asyncapi.js +1 -1
  67. package/lib/env/schema.js +16 -0
  68. package/lib/env/schemas/cds-rc.js +1 -76
  69. package/lib/import/importapi.js +2 -3
  70. package/lib/import/message.js +0 -2
  71. package/lib/init/constants.js +2 -1
  72. package/lib/init/index.js +2 -2
  73. package/lib/init/merge.js +123 -280
  74. package/lib/init/plugin.js +3 -1
  75. package/lib/init/projectReader.js +22 -6
  76. package/lib/init/registries/xs-app.js +3 -5
  77. package/lib/init/template/ai/files/mta.yaml.hbs +1 -1
  78. package/lib/init/template/ai/index.js +14 -2
  79. package/lib/init/template/ams/index.js +2 -2
  80. package/lib/init/template/app-frontend/files/containerize.yaml.hbs +2 -2
  81. package/lib/init/template/app-frontend/index.js +4 -4
  82. package/lib/init/template/application-logging/index.js +2 -2
  83. package/lib/init/template/approuter/files/package.json +1 -1
  84. package/lib/init/template/approuter/index.js +8 -3
  85. package/lib/init/template/attachments/files/package.json.hbs +1 -1
  86. package/lib/init/template/attachments/index.js +2 -2
  87. package/lib/init/template/audit-logging/index.js +2 -2
  88. package/lib/init/template/cloud-logging/files/package.json.hbs +4 -4
  89. package/lib/init/template/cloud-logging/index.js +2 -2
  90. package/lib/init/template/completion/completionSetup.js +36 -39
  91. package/lib/init/template/connectivity/index.js +2 -2
  92. package/lib/init/template/data/as-csv.js +15 -5
  93. package/lib/init/template/data/as-json.js +117 -42
  94. package/lib/init/template/data/index.js +16 -2
  95. package/lib/init/template/destination/index.js +2 -2
  96. package/lib/init/template/dynatrace/files/package.json.hbs +3 -3
  97. package/lib/init/template/dynatrace/index.js +2 -2
  98. package/lib/init/template/enterprise-messaging/index.js +2 -2
  99. package/lib/init/template/enterprise-messaging-shared/index.js +2 -2
  100. package/lib/init/template/extensibility/files/mta.yaml.hbs +11 -0
  101. package/lib/init/template/extensibility/files/xs-security.json.hbs +1 -1
  102. package/lib/init/template/extensibility/index.js +3 -3
  103. package/lib/init/template/github-actions/index.js +2 -2
  104. package/lib/init/template/hana/index.js +3 -2
  105. package/lib/init/template/hana-serverless/files/db/package.json +14 -0
  106. package/lib/init/template/hana-serverless/files/identity.values.yaml.hbs +10 -0
  107. package/lib/init/template/hana-serverless/files/mta.yaml.hbs +64 -0
  108. package/lib/init/template/hana-serverless/files/package.json.hbs +35 -0
  109. package/lib/init/template/hana-serverless/files/src/.hdiconfig +139 -0
  110. package/lib/init/template/hana-serverless/files/undeploy.json +7 -0
  111. package/lib/init/template/hana-serverless/files/values.yaml.hbs +18 -0
  112. package/lib/init/template/hana-serverless/index.js +184 -0
  113. package/lib/init/template/html5-repo/files/containerize.yaml.hbs +2 -2
  114. package/lib/init/template/html5-repo/files/mta.yaml.hbs +5 -0
  115. package/lib/init/template/html5-repo/index.js +18 -14
  116. package/lib/init/template/http/index.js +123 -26
  117. package/lib/init/template/ias/index.js +2 -2
  118. package/lib/init/template/kafka/index.js +2 -2
  119. package/lib/init/template/kyma/index.js +7 -5
  120. package/lib/init/template/malware-scanner/index.js +2 -2
  121. package/lib/init/template/mta/files/mta.yaml.hbs +1 -1
  122. package/lib/init/template/multitenancy/index.js +18 -3
  123. package/lib/init/template/notifications/index.js +2 -2
  124. package/lib/init/template/portal/files/mta.yaml.hbs +4 -0
  125. package/lib/init/template/portal/index.js +13 -8
  126. package/lib/init/template/postgres/index.js +2 -2
  127. package/lib/init/template/redis/index.js +2 -2
  128. package/lib/init/template/sample/files/db/currencies.cds +2 -0
  129. package/lib/init/template/sample/files/db/data/sap.capire.bookshop-Authors.csv +1 -1
  130. package/lib/init/template/tiny-sample/files/nodejs/db/data/CatalogService.Books.csv +2 -2
  131. package/lib/init/template/workzone-standard/index.js +5 -5
  132. package/lib/init/template/xsuaa/index.js +20 -2
  133. package/lib/lint/eslintHelp.json +1 -1
  134. package/lib/mtx/auth_manager.js +103 -37
  135. package/lib/mtx/params.js +3 -2
  136. package/lib/mtx/settings_manager.js +45 -28
  137. package/lib/mtx/tenant_action.js +0 -1
  138. package/lib/mtx/util/credentials.js +62 -0
  139. package/node_modules/@cap-js/db-service/CHANGELOG.md +17 -0
  140. package/node_modules/@cap-js/db-service/lib/common/generic-pool.js +4 -7
  141. package/node_modules/@cap-js/db-service/lib/cqn2pql.js +1 -1
  142. package/node_modules/@cap-js/db-service/lib/cqn2sql.js +15 -11
  143. package/node_modules/@cap-js/db-service/lib/cqn4sql.js +119 -19
  144. package/node_modules/@cap-js/db-service/lib/infer/index.js +27 -16
  145. package/node_modules/@cap-js/db-service/lib/recursion-guard.js +40 -0
  146. package/node_modules/@cap-js/db-service/lib/search.js +1 -1
  147. package/node_modules/@cap-js/db-service/package.json +1 -1
  148. package/node_modules/@cap-js/sqlite/CHANGELOG.md +19 -0
  149. package/node_modules/@cap-js/sqlite/lib/SQLiteService.js +110 -14
  150. package/node_modules/@cap-js/sqlite/package.json +3 -3
  151. package/node_modules/@sap/cds/CHANGELOG.md +57 -0
  152. package/node_modules/@sap/cds/app/index.js +5 -4
  153. package/node_modules/@sap/cds/eslint.config.mjs +1 -0
  154. package/node_modules/@sap/cds/lib/compile/minify.js +14 -10
  155. package/node_modules/@sap/cds/lib/env/cds-env.js +1 -1
  156. package/node_modules/@sap/cds/lib/env/cds-requires.js +22 -11
  157. package/node_modules/@sap/cds/lib/env/defaults.js +4 -1
  158. package/node_modules/@sap/cds/lib/env/schemas/cds-rc.js +5 -2
  159. package/node_modules/@sap/cds/lib/plugins.js +9 -5
  160. package/node_modules/@sap/cds/lib/req/spawn.js +1 -1
  161. package/node_modules/@sap/cds/lib/srv/srv-dispatch.js +1 -1
  162. package/node_modules/@sap/cds/lib/srv/srv-models.js +1 -1
  163. package/node_modules/@sap/cds/lib/utils/cds-utils.js +2 -1
  164. package/node_modules/@sap/cds/libx/_runtime/common/generic/assert.js +2 -3
  165. package/node_modules/@sap/cds/libx/_runtime/common/generic/auth/restrict.js +8 -1
  166. package/node_modules/@sap/cds/libx/_runtime/common/generic/crud.js +18 -8
  167. package/node_modules/@sap/cds/libx/_runtime/fiori/lean-draft.js +25 -17
  168. package/node_modules/@sap/cds/libx/_runtime/messaging/enterprise-messaging-utils/registerEndpoints.js +9 -3
  169. package/node_modules/@sap/cds/libx/_runtime/remote/Service.js +4 -19
  170. package/node_modules/@sap/cds/libx/_runtime/remote/utils/LruCache.js +42 -0
  171. package/node_modules/@sap/cds/libx/_runtime/remote/utils/client.js +14 -4
  172. package/node_modules/@sap/cds/libx/_runtime/remote/utils/cloudSdkProvider.js +2 -9
  173. package/node_modules/@sap/cds/libx/_runtime/remote/utils/destinationClient.js +150 -0
  174. package/node_modules/@sap/cds/libx/_runtime/remote/utils/error.js +13 -0
  175. package/node_modules/@sap/cds/libx/_runtime/remote/utils/fetchClient.js +26 -18
  176. package/node_modules/@sap/cds/libx/_runtime/remote/utils/query.js +34 -23
  177. package/node_modules/@sap/cds/libx/_runtime/remote/utils/token.js +82 -0
  178. package/node_modules/@sap/cds/libx/_runtime/ucl/Service.js +36 -189
  179. package/node_modules/@sap/cds/libx/common/utils/streaming.js +16 -16
  180. package/node_modules/@sap/cds/libx/http/location.js +3 -3
  181. package/node_modules/@sap/cds/libx/odata/middleware/batch.js +16 -10
  182. package/node_modules/@sap/cds/libx/odata/middleware/metadata.js +9 -6
  183. package/node_modules/@sap/cds/libx/odata/middleware/service-document.js +1 -1
  184. package/node_modules/@sap/cds/libx/odata/parse/afterburner.js +19 -3
  185. package/node_modules/@sap/cds/libx/odata/parse/cqn2odata.js +83 -86
  186. package/node_modules/@sap/cds/libx/queue/SchedulingService.js +17 -2
  187. package/node_modules/@sap/cds/libx/queue/processing.js +1 -3
  188. package/node_modules/@sap/cds/libx/rest/RestAdapter.js +3 -7
  189. package/node_modules/@sap/cds/libx/rest/middleware/delete.js +3 -3
  190. package/node_modules/@sap/cds/libx/rest/middleware/parse.js +13 -8
  191. package/node_modules/@sap/cds/libx/rest/middleware/read.js +3 -3
  192. package/node_modules/@sap/cds/libx/rest/middleware/upsert.js +2 -2
  193. package/node_modules/@sap/cds/package.json +1 -1
  194. package/node_modules/@sap/cds-compiler/CHANGELOG.md +75 -8
  195. package/node_modules/@sap/cds-compiler/bin/cdsc.js +2 -2
  196. package/node_modules/@sap/cds-compiler/doc/IncompatibleChanges_v7.md +46 -19
  197. package/node_modules/@sap/cds-compiler/lib/api/main.js +10 -7
  198. package/node_modules/@sap/cds-compiler/lib/api/options.js +8 -2
  199. package/node_modules/@sap/cds-compiler/lib/base/csnRefs.js +5 -2
  200. package/node_modules/@sap/cds-compiler/lib/base/message-registry.js +4 -0
  201. package/node_modules/@sap/cds-compiler/lib/base/optionProcessor.js +12 -0
  202. package/node_modules/@sap/cds-compiler/lib/checks/enricher.js +1 -0
  203. package/node_modules/@sap/cds-compiler/lib/compiler/assert-consistency.js +8 -7
  204. package/node_modules/@sap/cds-compiler/lib/compiler/checks.js +3 -3
  205. package/node_modules/@sap/cds-compiler/lib/compiler/define.js +301 -75
  206. package/node_modules/@sap/cds-compiler/lib/compiler/dictionaries.js +2 -0
  207. package/node_modules/@sap/cds-compiler/lib/compiler/extend.js +40 -26
  208. package/node_modules/@sap/cds-compiler/lib/compiler/populate.js +45 -60
  209. package/node_modules/@sap/cds-compiler/lib/compiler/propagator.js +16 -7
  210. package/node_modules/@sap/cds-compiler/lib/compiler/resolve.js +48 -18
  211. package/node_modules/@sap/cds-compiler/lib/compiler/shared.js +7 -6
  212. package/node_modules/@sap/cds-compiler/lib/compiler/tweak-assocs.js +62 -50
  213. package/node_modules/@sap/cds-compiler/lib/compiler/utils.js +29 -14
  214. package/node_modules/@sap/cds-compiler/lib/edm/edmAnnoPreprocessor.js +6 -5
  215. package/node_modules/@sap/cds-compiler/lib/gen/CdlGrammar.checksum +1 -1
  216. package/node_modules/@sap/cds-compiler/lib/gen/CdlParser.js +1119 -1109
  217. package/node_modules/@sap/cds-compiler/lib/gen/Dictionary.json +19 -3
  218. package/node_modules/@sap/cds-compiler/lib/json/from-csn.js +10 -16
  219. package/node_modules/@sap/cds-compiler/lib/json/to-csn.js +58 -39
  220. package/node_modules/@sap/cds-compiler/lib/main.d.ts +8 -0
  221. package/node_modules/@sap/cds-compiler/lib/main.js +1 -0
  222. package/node_modules/@sap/cds-compiler/lib/model/csnUtils.js +4 -0
  223. package/node_modules/@sap/cds-compiler/lib/parsers/AstBuildingParser.js +2 -1
  224. package/node_modules/@sap/cds-compiler/lib/parsers/XprTree.js +8 -0
  225. package/node_modules/@sap/cds-compiler/lib/render/toCdl.js +22 -1
  226. package/node_modules/@sap/cds-compiler/lib/render/toSql.js +17 -2
  227. package/node_modules/@sap/cds-compiler/lib/render/utils/common.js +6 -1
  228. package/node_modules/@sap/cds-compiler/lib/render/utils/operators.js +27 -0
  229. package/node_modules/@sap/cds-compiler/lib/render/utils/standardDatabaseFunctions.js +125 -3
  230. package/node_modules/@sap/cds-compiler/lib/tool-lib/revealInternalProperties.js +2 -1
  231. package/node_modules/@sap/cds-compiler/lib/transform/db/applyTransformations.js +15 -0
  232. package/node_modules/@sap/cds-compiler/lib/transform/db/associations.js +10 -2
  233. package/node_modules/@sap/cds-compiler/lib/transform/db/backlinks.js +22 -6
  234. package/node_modules/@sap/cds-compiler/lib/transform/db/constraints.js +62 -1
  235. package/node_modules/@sap/cds-compiler/lib/transform/db/flattening.js +4 -3
  236. package/node_modules/@sap/cds-compiler/lib/transform/effective/annotations.js +40 -0
  237. package/node_modules/@sap/cds-compiler/lib/transform/effective/flattening.js +17 -1
  238. package/node_modules/@sap/cds-compiler/lib/transform/effective/main.js +20 -0
  239. package/node_modules/@sap/cds-compiler/lib/transform/fioriTreeViews.js +12 -1
  240. package/node_modules/@sap/cds-compiler/lib/transform/forRelationalDB.js +7 -2
  241. package/node_modules/@sap/cds-compiler/lib/transform/odata/adaptAnnotationRefs.js +11 -5
  242. package/node_modules/@sap/cds-compiler/lib/transform/translateAssocsToJoins.js +48 -10
  243. package/node_modules/@sap/cds-compiler/package.json +1 -1
  244. package/node_modules/@sap/cds-mtxs/CHANGELOG.md +49 -0
  245. package/node_modules/@sap/cds-mtxs/bin/cds-mtx-migrate.js +26 -5
  246. package/node_modules/@sap/cds-mtxs/bin/cds-mtx.js +37 -16
  247. package/node_modules/@sap/cds-mtxs/lib/utils.js +23 -7
  248. package/node_modules/@sap/cds-mtxs/package.json +1 -1
  249. package/node_modules/@sap/cds-mtxs/srv/cf/abstract-provisioning-service.js +63 -8
  250. package/node_modules/@sap/cds-mtxs/srv/cf/saas-provisioning-service.js +1 -1
  251. package/node_modules/@sap/cds-mtxs/srv/cf/sms-provisioning-service.js +23 -3
  252. package/node_modules/@sap/cds-mtxs/srv/extensibility/compat/update.js +15 -0
  253. package/node_modules/@sap/cds-mtxs/srv/extensibility/linter/annotations.js +2 -2
  254. package/node_modules/@sap/cds-mtxs/srv/extensibility/linter/config.js +2 -0
  255. package/node_modules/@sap/cds-mtxs/srv/extensibility/linter/index.js +2 -0
  256. package/node_modules/@sap/cds-mtxs/srv/extensibility/linter/protected-namespace.js +45 -0
  257. package/node_modules/@sap/cds-mtxs/srv/extensibility-service.js +30 -16
  258. package/node_modules/@sap/cds-mtxs/srv/jobs-service.js +49 -10
  259. package/node_modules/@sap/cds-mtxs/srv/model-provider.js +28 -4
  260. package/node_modules/@sap/cds-mtxs/srv/plugins/common.js +19 -12
  261. package/node_modules/@sap/cds-mtxs/srv/plugins/hana/ctnr-mgr-base.js +1 -1
  262. package/node_modules/@sap/cds-mtxs/srv/plugins/hana/hdi-ctnr-mgr.js +6 -3
  263. package/node_modules/@sap/cds-mtxs/srv/plugins/hana/serverless.js +271 -0
  264. package/node_modules/@sap/cds-mtxs/srv/plugins/hana.js +7 -10
  265. package/node_modules/@sap/cds-mtxs/srv/worker/job-worker-pool.js +212 -0
  266. package/node_modules/@sap/cds-mtxs/srv/worker/job-worker.js +61 -0
  267. package/node_modules/@sap/xsenv/CHANGELOG.md +7 -0
  268. package/node_modules/@sap/xsenv/package.json +10 -7
  269. package/node_modules/body-parser/node_modules/content-type/README.md +2 -0
  270. package/node_modules/body-parser/node_modules/content-type/dist/index.d.ts +20 -0
  271. package/node_modules/body-parser/node_modules/content-type/dist/index.js +20 -14
  272. package/node_modules/body-parser/node_modules/content-type/dist/index.js.map +1 -1
  273. package/node_modules/body-parser/node_modules/content-type/package.json +1 -1
  274. package/node_modules/hdb/cfg/VERSION +1 -1
  275. package/node_modules/hdb/lib/protocol/ClientInfo.js +16 -3
  276. package/node_modules/hdb/lib/protocol/Connection.js +1 -0
  277. package/node_modules/hdb/package.json +3 -2
  278. package/node_modules/negotiator/lib/accept.js +54 -0
  279. package/node_modules/negotiator/lib/charset.js +10 -34
  280. package/node_modules/negotiator/lib/encoding.js +9 -30
  281. package/node_modules/negotiator/lib/language.js +15 -36
  282. package/node_modules/negotiator/lib/mediaType.js +16 -134
  283. package/node_modules/negotiator/node_modules/content-type/LICENSE +22 -0
  284. package/node_modules/negotiator/node_modules/content-type/README.md +71 -0
  285. package/node_modules/negotiator/node_modules/content-type/dist/index.d.ts +46 -0
  286. package/node_modules/negotiator/node_modules/content-type/dist/index.js +176 -0
  287. package/node_modules/negotiator/node_modules/content-type/dist/index.js.map +1 -0
  288. package/node_modules/negotiator/node_modules/content-type/package.json +52 -0
  289. package/node_modules/negotiator/package.json +16 -10
  290. package/node_modules/qs/.github/THREAT_MODEL.md +3 -3
  291. package/node_modules/qs/CHANGELOG.md +15 -0
  292. package/node_modules/qs/README.md +19 -1
  293. package/node_modules/qs/dist/qs.js +21 -21
  294. package/node_modules/qs/eslint.config.mjs +1 -1
  295. package/node_modules/qs/lib/parse.js +3 -4
  296. package/node_modules/qs/lib/stringify.js +23 -8
  297. package/node_modules/qs/lib/utils.js +10 -3
  298. package/node_modules/qs/package.json +4 -4
  299. package/node_modules/qs/test/parse.js +192 -3
  300. package/node_modules/qs/test/stringify.js +317 -0
  301. package/node_modules/qs/test/utils.js +12 -0
  302. package/node_modules/type-is/node_modules/content-type/README.md +2 -0
  303. package/node_modules/type-is/node_modules/content-type/dist/index.d.ts +20 -0
  304. package/node_modules/type-is/node_modules/content-type/dist/index.js +20 -14
  305. package/node_modules/type-is/node_modules/content-type/dist/index.js.map +1 -1
  306. package/node_modules/type-is/node_modules/content-type/package.json +1 -1
  307. package/node_modules/ws/lib/permessage-deflate.js +3 -1
  308. package/node_modules/ws/package.json +1 -1
  309. package/package.json +2 -2
  310. package/bin/completion/scripts/cds-bash.sh +0 -20
  311. package/bin/completion/scripts/cds-fish.fish +0 -13
  312. package/bin/completion/scripts/cds-ps.ps1 +0 -19
  313. package/bin/completion/scripts/cds-zsh.sh +0 -13
  314. package/lib/deploy/index.js +0 -25
  315. package/lib/import/asyncapi/importAsyncAPI.js +0 -33
  316. package/lib/import/asyncapi/index.js +0 -18
  317. package/lib/import/asyncapi/parserContext.js +0 -454
  318. package/lib/import/asyncapi/utils.js +0 -49
  319. package/node_modules/@sap/cds/libx/_runtime/ucl/queries.js +0 -61
  320. package/node_modules/negotiator/HISTORY.md +0 -114
  321. /package/{lib → bin}/upgrade/context.js +0 -0
  322. /package/{lib → bin}/upgrade/migrations/cds10/migrations.json +0 -0
  323. /package/{lib → bin}/upgrade/preflight/cds-version.js +0 -0
  324. /package/{lib → bin}/upgrade/preflight/project-type.js +0 -0
  325. /package/{lib → bin}/upgrade/steps/analyze/classification.js +0 -0
  326. /package/{lib → bin}/upgrade/steps/analyze/detector.js +0 -0
  327. /package/{lib → bin}/upgrade/steps/analyze/evaluator.js +0 -0
  328. /package/{lib → bin}/upgrade/steps/analyze/reporter.js +0 -0
  329. /package/{lib → bin}/upgrade/steps/analyze/setup.js +0 -0
  330. /package/{lib → bin}/upgrade/steps/analyze/utils/detection.js +0 -0
  331. /package/{lib → bin}/upgrade/steps/analyze/utils/files.js +0 -0
  332. /package/{lib → bin}/upgrade/steps/analyze/utils/grep.js +0 -0
  333. /package/{lib → bin}/upgrade/steps/analyze/utils/yaml.js +0 -0
  334. /package/{lib → bin}/upgrade/util/workspaces.js +0 -0
  335. /package/{lib → bin}/watch/esm-resolve-registration.js +0 -0
  336. /package/{lib → bin}/watch/livereload-server.js +0 -0
  337. /package/{lib → bin}/watch/node-watch.js +0 -0
  338. /package/{lib → bin}/watch/open.js +0 -0
  339. /package/{lib → bin}/watch/watcher.js +0 -0
  340. /package/node_modules/@sap/cds-mtxs/srv/worker/{pool.js → model-worker-pool.js} +0 -0
  341. /package/node_modules/@sap/cds-mtxs/srv/worker/{load.js → model-worker.js} +0 -0
@@ -1,6 +1,6 @@
1
1
  'use strict';
2
2
 
3
- const { transformAnnotationExpression, implicitAs } = require('../../model/csnUtils');
3
+ const { transformAnnotationExpression, implicitAs, isSimpleAnnoExprRef } = require('../../model/csnUtils');
4
4
 
5
5
  /**
6
6
  * Used during annotating of foreign keys.
@@ -13,10 +13,10 @@ const { transformAnnotationExpression, implicitAs } = require('../../model/csnUt
13
13
  * @param {object} messageFunctions
14
14
  * @param {CSN.Path} elementPath
15
15
  */
16
- function adaptAnnotationsRefs(generatedForeignKeys, csnUtils, { error }, elementPath) {
16
+ function adaptAnnotationsRefs(generatedForeignKeys, csnUtils, { error }, elementPath, options) {
17
17
  if (Array.isArray(generatedForeignKeys?.[0])) {
18
18
  // ensure we are always called with an array of objects. TODO: Cleanup fk creation in for.effective to create array of objects
19
- adaptAnnotationsRefs(remapToArrayOfObjects(generatedForeignKeys), csnUtils, { error }, elementPath);
19
+ adaptAnnotationsRefs(remapToArrayOfObjects(generatedForeignKeys), csnUtils, { error }, elementPath, options);
20
20
  }
21
21
  else {
22
22
  const reportedErrorsForAnnoPath = {};
@@ -49,8 +49,14 @@ function adaptAnnotationsRefs(generatedForeignKeys, csnUtils, { error }, element
49
49
  if (gfkForRef.length === 1) {
50
50
  ref[0] = gfkForRef[0].prefix;
51
51
 
52
- if (ctx?.annoExpr?.['='])
53
- ctx.annoExpr['='] = true;
52
+ if (ctx?.annoExpr?.['=']) {
53
+ // TODO: Apply the same fix for OData once the OData behavior is aligned;
54
+ // when done, the `options` parameter can be removed from this function.
55
+ if (options?.transformation === 'effective')
56
+ ctx.annoExpr['='] = isSimpleAnnoExprRef(ctx.annoExpr) ? ctx.annoExpr.ref.join('.') : true;
57
+ else
58
+ ctx.annoExpr['='] = true;
59
+ }
54
60
  }
55
61
  else {
56
62
  // check if the annotation reference points to a structure that has been expanded,
@@ -24,6 +24,9 @@ function translateAssocsToJoinsCSN(csn, options) {
24
24
  const compileOptions = { ...options, $skipNameCheck: true };
25
25
  delete compileOptions.csnFlavor;
26
26
 
27
+ // DO NOT DELETE the `require('fs').writeFileSync(...)` lines in this function!
28
+ // Uncomment them to see the CSN input and output for A2J
29
+ // require('fs').writeFileSync('./a2j_in.csn', JSON.stringify(csn,null,2))
27
30
  const model = recompileX(csn, compileOptions);
28
31
  timetrace.stop('A2J: Recompiling model');
29
32
  timetrace.start('A2J: Translating associations to joins');
@@ -49,7 +52,9 @@ function translateAssocsToJoinsCSN(csn, options) {
49
52
  deduplicateMessages( options.messages );
50
53
  }
51
54
 
52
- return compactModel(model, compileOptions);
55
+ const newCsn = compactModel(model, compileOptions);
56
+ // require('fs').writeFileSync('./a2j_out.csn', JSON.stringify(newCsn,null,2))
57
+ return newCsn;
53
58
  }
54
59
 
55
60
  function translateAssocsToJoins(model, inputOptions = {}) {
@@ -607,11 +612,10 @@ function translateAssocsToJoins(model, inputOptions = {}) {
607
612
  args.push({
608
613
  op: { val: '=' },
609
614
  args: [ constructPathNode( [ srcAlias, prefixFK(assoc.$elementPrefix, assoc.$flatSrcFKs[i]) ] ),
610
- constructPathNode( [ tgtAlias, assoc.$flatTgtFKs[i] ] ) ],
615
+ constructPathNode( [ tgtAlias, resolveTgtFKToAlias(assoc.$flatTgtFKs[i], tgtAlias) ] ) ],
611
616
  });
612
617
  }
613
- // TODO: why inner "parenthesise" - comparison in `and`?
614
- return parenthesise((args.length > 1 ? { op: { val: 'and' }, args: [ ...args.map(parenthesise) ] } : args[0] ));
618
+ return parenthesise((args.length > 1 ? { op: { val: 'and' }, args } : args[0] ));
615
619
  }
616
620
  else if (assoc.on) {
617
621
  if (env.assocStack === undefined) {
@@ -707,7 +711,7 @@ function translateAssocsToJoins(model, inputOptions = {}) {
707
711
 
708
712
  function cloneOnCondExprStream(expr) {
709
713
  const { args } = expr;
710
- const result = { op: { val: expr.op.val }, args: [ ] };
714
+ const result = { op: { val: expr.op.val }, args: [ ], $parens: expr.$parens };
711
715
  for (let i = 0; i < args.length; i++) {
712
716
  const op = args[i].op?.val;
713
717
  if (op === 'xpr' || op === 'ixpr' || op === 'nary') {
@@ -727,7 +731,7 @@ function translateAssocsToJoins(model, inputOptions = {}) {
727
731
  args[i + 2].$check = false;
728
732
  }
729
733
  else {
730
- result.args.push(createOnCondition(fwdAssoc, ...swapTableAliasesForFwdAssoc(fwdAssoc, srcAlias, tgtAlias)));
734
+ result.args.push(createOnCondition(backlinkOf(assoc, fwdAssoc), ...swapTableAliasesForFwdAssoc(fwdAssoc, srcAlias, tgtAlias)));
731
735
  }
732
736
  i += 2; // skip next two tokens and continue with loop
733
737
  continue;
@@ -764,7 +768,7 @@ function translateAssocsToJoins(model, inputOptions = {}) {
764
768
  return expr;
765
769
  }
766
770
 
767
- return createOnCondition(fwdAssoc, ...swapTableAliasesForFwdAssoc(fwdAssoc, srcAlias, tgtAlias));
771
+ return createOnCondition(backlinkOf(assoc, fwdAssoc), ...swapTableAliasesForFwdAssoc(fwdAssoc, srcAlias, tgtAlias));
768
772
  }
769
773
 
770
774
  // If this is an ordinary expression, clone it and mangle its arguments
@@ -804,7 +808,19 @@ function translateAssocsToJoins(model, inputOptions = {}) {
804
808
  const assocSource = assoc._main;
805
809
  if (assoc.kind === 'mixin' && (i > 0 || fwdTarget !== assocSource) && fwdOrigin.kind !== 'mixin') {
806
810
  const tas = Object.values(env.lead.$tableAliases);
807
- const j = tas.findIndex(ta => ta._artifact === fwdOrigin.target._artifact);
811
+ // The backlink association's target may be reached through a view
812
+ // over it, whose alias is not the target artifact itself; fall back
813
+ // to the alias whose `_origin` chain reaches the target.
814
+ let j = tas.findIndex(ta => ta._artifact === fwdOrigin.target._artifact);
815
+ if (j < 0) {
816
+ j = tas.findIndex((ta) => {
817
+ for (let a = ta._artifact; a; a = a._origin) {
818
+ if (a === fwdOrigin.target._artifact)
819
+ return true;
820
+ }
821
+ return false;
822
+ });
823
+ }
808
824
  if (j >= 0 && tas[j].$QA) {
809
825
  newTgtAlias.id = tas[j].$QA.name.id;
810
826
  newTgtAlias._artifact = tas[j]._effectiveType;
@@ -1050,6 +1066,28 @@ function translateAssocsToJoins(model, inputOptions = {}) {
1050
1066
  }
1051
1067
  return undefined;
1052
1068
  }
1069
+
1070
+ // Build the backlink ON from the renaming-aware `$backlink` rather than the
1071
+ // forward assoc's base-entity keys/on, if it is available.
1072
+ function backlinkOf( backlinkAssoc, fwdAssoc ) {
1073
+ const backlink = backlinkAssoc.$backlink;
1074
+ return backlink && (backlink.foreignKeys || backlink.on) ? backlink : fwdAssoc;
1075
+ }
1076
+
1077
+ // Resolve the target foreign key to the alias of the target table.
1078
+ // If the target foreign key is not found in the scope of the target alias,
1079
+ // try to find it in the origin chain of the foreign key artifact.
1080
+ function resolveTgtFKToAlias( fk, tgtAlias ) {
1081
+ const env = tgtAlias && (tgtAlias._effectiveType || tgtAlias._artifact);
1082
+ const scope = env && env.elements;
1083
+ if (!scope || !fk || scope[fk.id] || !fk._artifact || !fk._artifact._origin)
1084
+ return fk;
1085
+ for (let o = fk._artifact._origin; o; o = o._origin) {
1086
+ if (o.name && scope[o.name.id])
1087
+ return { id: o.name.id, _artifact: fk._artifact, acc: fk.acc };
1088
+ }
1089
+ return fk;
1090
+ }
1053
1091
  } // createOnCondition
1054
1092
  } // createJoinQA
1055
1093
 
@@ -1706,9 +1744,9 @@ function translateAssocsToJoins(model, inputOptions = {}) {
1706
1744
  return newObj;
1707
1745
  }
1708
1746
 
1747
+ // to-csn now auto-wraps AND args that are OR-terms (see json/to-csn.js `case 'and'`),
1748
+ // so A2J no longer needs to pre-wrap sub-expressions to preserve SQL precedence.
1709
1749
  function parenthesise(expr) {
1710
- if (typeof expr === 'object' && expr.op && expr.args)
1711
- setProp(expr, '$parens', [ expr.$location ]);
1712
1750
  return expr;
1713
1751
  }
1714
1752
  }
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sap/cds-compiler",
3
- "version": "7.0.3",
3
+ "version": "7.1.1",
4
4
  "description": "CDS (Core Data Services) compiler and backends",
5
5
  "homepage": "https://cap.cloud.sap/",
6
6
  "author": "SAP SE (https://www.sap.com)",
@@ -6,6 +6,55 @@ This project adheres to [Semantic Versioning](https://semver.org/).
6
6
 
7
7
  The format is based on [Keep a Changelog](https://keepachangelog.com/).
8
8
 
9
+ ## Version 4.1.0 - 2026-09-04
10
+
11
+ ### Added
12
+
13
+ - Beta: Multitenant provisioning now supports HANA Cloud serverless, resolving one HDI container per tenant through the serverless Tenant API.
14
+ - Beta: The MTX job service can now use native javascript workers to make the MTX sidecar more responsive while jobs (e. g. tenant upgrade) are running.
15
+ Native workers can be enabled by `nativeWorkers` in the jobs configuration, e. g.
16
+ ```jsonc
17
+ "cds.xt.SaasProvisioningService": {
18
+ "jobs": {
19
+ "nativeWorkers": true,
20
+ "workerSize": 3,
21
+ "clusterSize": 3
22
+ }
23
+ },
24
+ ```
25
+ - With configuration `cds.requires['cds.xt.SaasProvisioning'].upgrade.skipAlreadyUpgraded: true` or `cds.requires['cds.xt.SmsProvisioning'].upgrade.skipAlreadyUpgraded: true`,
26
+ already upgraded tenants are skipped when running an upgrade of all tenants (`tenants: ['*']`). Tenants are considered as already upgraded if the last upgrade was run after the
27
+ last deployment of the application.
28
+
29
+ ### Fixed
30
+
31
+ - `SmsProvisioningService` certificate check is now better aligned with the Java implementation.
32
+ - Extension validation checks have been refined.
33
+ - `POST /-/cds/model-provider/getEdmx` now checks the requested service name more exactly.
34
+ - Callback url for Saas Registry and Subscription Manager is now determined in a more robust way.
35
+
36
+ ## Version 4.0.4 - 2026-08-21
37
+
38
+ ### Fixed
39
+
40
+ - Version bump
41
+
42
+ ## Version 4.0.3 - 2026-08-21
43
+
44
+ ### Added
45
+
46
+ - `cds-mtx subscribe`, `unsubscribe`, and `upgrade` now take more than one tenant at once, e.g. `cds-mtx upgrade t1 t2` or `cds-mtx upgrade t1,t2`.
47
+
48
+ ### Changed
49
+
50
+ - Login endpoints renamed to `/-/cds/login/grant` and `/-/cds/login/grant-metadata` with redirects from former paths.
51
+
52
+ ### Fixed
53
+
54
+ - HANA credential health-check now detects the available driver via `require.resolve()` instead of reading `package.json`, fixing `MODULE_NOT_FOUND` for apps that follow the "Plug & Play" guidance and don't declare `hdb` or `@sap/hana-client` directly.
55
+ - Tenant upgrade now reports a clear per-tenant `422` error when a stored extension is incompatible with the changed base model, instead of aborting with an uncaught compilation error.
56
+ - `cds-mtx-migrate` now runs more robust with uncommon model configurations.
57
+
9
58
  ## Version 4.0.2 - 2026-08-07
10
59
 
11
60
  ### Fixed
@@ -1,6 +1,9 @@
1
1
  #!/usr/bin/env node
2
2
  /* eslint-disable no-console */
3
3
 
4
+ const cds = require('@sap/cds')
5
+ const { _import, isfile, local, path } = cds.utils
6
+
4
7
  async function run(param, options) {
5
8
  const { dry, genTenantList, syncTenantList, cleanup, directory, tag, tagRule } = options
6
9
  const skipVerification = options['skip-verification'] || options.skipVerification
@@ -26,7 +29,6 @@ async function run(param, options) {
26
29
  _handleError(`The migration from @sap/cds-mtx to @sap/cds-mtxs has been removed. Only --init-tenant-list and --sync-tenant-list are still supported.`)
27
30
  }
28
31
 
29
- const cds = require('@sap/cds')
30
32
  const { syncTenantList: synchronizeTenantList, _hasMtEnv } = require('../lib/migration/migration')
31
33
 
32
34
  async function cds_mtx_migrate(tenants, options) {
@@ -45,11 +47,20 @@ async function run(param, options) {
45
47
  services.push('@sap/cds-mtxs/srv/extensibility-service')
46
48
  }
47
49
 
48
- await cds.connect.to('db')
49
- await cds.serve(services)
50
- cds.env.requires.multitenancy = cds.env.requires.multitenancy || true // needed for current sidecar config that only enables services
50
+ await _local_server_js()
51
+ cds.emit ('bootstrap')
52
+ const model = await cds.load([...services, '@sap/cds-mtxs/db/extensions'])
53
+ cds.model = cds.compile.for.nodejs(model)
54
+ const d = 'cds.xt.DeploymentService'
55
+ const unserved = !cds.services[d]
56
+ if (unserved) {
57
+ await cds.serve(services)
58
+ }
59
+ await cds.plugins
60
+
61
+ await cds.emit('served', cds.services)
51
62
 
52
- await cds.emit('served')
63
+ cds.env.requires.multitenancy ??= true // needed for current sidecar config that only enables services
53
64
 
54
65
  if (genTenantList) {
55
66
  const tenantListResult = await synchronizeTenantList(tenantList, options)
@@ -73,6 +84,16 @@ async function run(param, options) {
73
84
  await cds_mtx_migrate(param, options).catch(console.error)
74
85
  }
75
86
 
87
+ // copied from cds.serve
88
+ async function _local_server_js() {
89
+ const _local = file => isfile(file) || isfile (path.join(cds.env.folders.srv,file))
90
+ const cli_js = process.env.CDS_TYPESCRIPT && _local('cli.ts') || _local('cli.js')
91
+ if (cli_js) {
92
+ console.log ('[cds] - loading server from', { file: local(cli_js) })
93
+ await _import(cli_js)
94
+ }
95
+ }
96
+
76
97
  function getParameterValue(options, parameter) {
77
98
  const index = options.indexOf(parameter)
78
99
  const value = (index > -1) ? options[index + 1] : undefined
@@ -10,15 +10,16 @@ const SUPPORTED = ['subscribe', 'unsubscribe', 'upgrade']
10
10
  // This function must only be changed in a backward compatible way. It is used by DwC.
11
11
  async function cds_mtx(cmd, tenant, body) {
12
12
 
13
- let connectedTenants = [tenant]
13
+ const tenants = (Array.isArray(tenant) ? tenant : [tenant]).flatMap(t => t.split(','))
14
+ const one = tenants.length === 1 ? tenants[0] : undefined
15
+
16
+ let connectedTenants = []
14
17
 
15
18
  try {
16
19
  if (!cmd) return _usage()
17
20
  if (!SUPPORTED.includes(cmd)) return _usage(`Unknown command ${cmd}.`)
18
21
  if (!_hasMtEnv()) return _handleError(`cds-mtx ${cmd} operation can only be run inside a multitenant application environment using @sap/cds-mtxs.`)
19
- if (!tenant) return _handleError(`Please provide a tenant: cds-mtx ${cmd} <tenant>`)
20
- if (/.*,.*/.test(tenant)) return _handleError(`List of tenants not supported: ${tenant}`)
21
- // parse body and handle error
22
+ if (!tenants.length) return _handleError(`Please provide a tenant: cds-mtx ${cmd} <tenant>`)
22
23
  let parsedMetadata
23
24
  if (body) {
24
25
  try {
@@ -46,18 +47,27 @@ async function cds_mtx(cmd, tenant, body) {
46
47
  await cds.connect.to('db')
47
48
  }
48
49
 
49
- if (tenant === '*') {
50
+ if (tenants.includes('*')) {
50
51
  if (cmd !== 'upgrade') return _handleError('"*" only supported for upgrade command')
51
- const tenants = await sps.read('tenant')
52
- connectedTenants = tenants.map(t => t.subscribedTenantId)
52
+ if (tenants.length > 1) return _handleError('"*" cannot be combined with other tenants')
53
+ const all = await sps.read('tenant')
54
+ connectedTenants = all.map(t => t.subscribedTenantId)
53
55
  await sps.tx({ user: new cds.User.Privileged() }, async (tx) => {
54
- await tx.upgrade([tenant], parsedMetadata)
56
+ await tx.upgrade(['*'], parsedMetadata)
55
57
  })
56
58
  return
57
- } else if (tenant === 't0') {
59
+ } else if (one === 't0') {
58
60
  return
59
61
  }
60
- await ds[cmd](tenant, parsedMetadata, parsedMetadata)
62
+ connectedTenants = tenants
63
+
64
+ if (cmd === 'upgrade') {
65
+ await sps.tx({ user: new cds.User.Privileged() }, async (tx) => {
66
+ await tx.upgrade(tenants, parsedMetadata)
67
+ })
68
+ } else {
69
+ for (const t of tenants) await ds[cmd](t, parsedMetadata, parsedMetadata)
70
+ }
61
71
  } catch(e) {
62
72
  if (isCli) {
63
73
  console.error(e.message)
@@ -68,6 +78,9 @@ async function cds_mtx(cmd, tenant, body) {
68
78
  cds.db.disconnect(cds.env.requires.multitenancy.t0 ?? 't0')
69
79
  for (const t of connectedTenants) cds.db.disconnect(t)
70
80
  }
81
+ if (isCli) {
82
+ await cds.exit()
83
+ }
71
84
  }
72
85
  }
73
86
 
@@ -109,27 +122,35 @@ async function _usage(message = '') {
109
122
  USAGE
110
123
 
111
124
  cds-mtx <command> <tenant> [--body <json>]
125
+ cds-mtx <command> <tenant1> [<tenant2> ...] [--body <json>]
126
+ cds-mtx <command> <t1,t2,...> [--body <json>]
112
127
 
113
128
  COMMANDS
114
129
 
115
- subscribe subscribe a tenant
116
- unsubscribe unsubscribe a tenant
117
- upgrade upgrade a tenant
130
+ subscribe subscribe one or more tenants
131
+ unsubscribe unsubscribe one or more tenants
132
+ upgrade upgrade one or more tenants
118
133
 
119
134
  EXAMPLES
120
135
 
121
136
  cds-mtx subscribe t1
137
+ cds-mtx subscribe t1 t2 t3
122
138
  cds-mtx subscribe t1 --body '{ "_": { "hdi": { "create": { "database_id": "<database id>" } } } }'
123
139
  cds-mtx unsubscribe t1
124
140
  cds-mtx upgrade t1
141
+ cds-mtx upgrade t1 t2 t3
142
+ cds-mtx upgrade t1,t2,t3
125
143
  cds-mtx upgrade "*"
126
144
  `
127
145
  )
128
146
  }
129
147
 
130
148
  if (isCli) {
131
- const [, , cmd, tenant, option, json] = process.argv
132
- if (option && option !== '--body') _usage(`Invalid option ${option}`)
133
- ;(async () => await cds_mtx(cmd, tenant, option ? json : undefined))()
149
+ const [, , cmd, ...rest] = process.argv
150
+ const bodyIdx = rest.indexOf('--body')
151
+ const tenants = bodyIdx === -1 ? rest : rest.slice(0, bodyIdx)
152
+ const body = bodyIdx !== -1 ? rest[bodyIdx + 1] : undefined
153
+ if (bodyIdx !== -1 && !body) _usage(`Missing value for --body`)
154
+ ;(async () => await cds_mtx(cmd, tenants, body))()
134
155
  }
135
156
  module.exports = { cds_mtx }
@@ -89,15 +89,30 @@ const t0_ = async (query) => retry(async () => {
89
89
  return cds.tx({ tenant: t0 }, tx => { tx.model = _t0Csn; return tx.run(query) })
90
90
  })
91
91
 
92
- async function token(credentials, { query = {}, form = {}, subdomain } = {}) {
92
+ const SUBDOMAIN_RE = /^[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?$/i
93
+
94
+ function authUrl(baseUrl, subdomain, pathname) {
95
+ const u = new URL(baseUrl)
96
+ if (subdomain) {
97
+ if (typeof subdomain !== 'string' || !SUBDOMAIN_RE.test(subdomain)) {
98
+ throw cds.error('invalid subdomain', { status: 400 })
99
+ }
100
+ const dot = u.hostname.indexOf('.')
101
+ u.hostname = subdomain + (dot >= 0 ? u.hostname.slice(dot) : '')
102
+ }
103
+ u.pathname = pathname
104
+ return u
105
+ }
106
+
107
+ async function token(credentials, { query = {}, form = {}, subdomain, path = '/oauth/token' } = {}) {
93
108
  const { clientid, clientsecret, certurl, url, certificate, key } = credentials ?? {}
94
- const u = new URL(certurl || url)
95
- if (subdomain) u.hostname = u.hostname.replace(/^[^.]+/, subdomain)
96
- u.pathname = '/oauth/token'
109
+ if (!form.grant_type) throw cds.error('grant_type is required', { status: 400 })
110
+ const u = authUrl(certurl || url, subdomain, path)
97
111
  Object.entries(query).forEach(([k, v]) => u.searchParams.append(k, v))
98
- const body = new URLSearchParams({ grant_type: 'client_credentials', client_id: clientid, ...form })
112
+ const body = new URLSearchParams({ client_id: clientid, ...form })
99
113
  const headers = { 'Content-Type': 'application/x-www-form-urlencoded', Accept: 'application/json' }
100
114
  let raw
115
+ const _fail = (statusCode, respBody) => new Error(`token request to ${u.origin}${u.pathname} for client ${clientid} failed (${statusCode}): ${respBody}`, { status: statusCode })
101
116
  if (certificate) {
102
117
  raw = await new Promise((resolve, reject) => {
103
118
  const req = require('node:https').request({
@@ -112,7 +127,7 @@ async function token(credentials, { query = {}, form = {}, subdomain } = {}) {
112
127
  res.on('data', x => chunks.push(x))
113
128
  res.on('end', () => {
114
129
  if (res.statusCode >= 200 && res.statusCode < 300) resolve(Buffer.concat(chunks).toString())
115
- else reject(new Error(Buffer.concat(chunks).toString(), { status: res.statusCode }))
130
+ else reject(_fail(res.statusCode, Buffer.concat(chunks).toString()))
116
131
  })
117
132
  })
118
133
  req.on('error', reject)
@@ -162,7 +177,7 @@ async function fetchResiliently(url, req, { retriesLeft = maxRetries, maxRetryAf
162
177
  const data = contentType.includes('application/json') ? await res.json() : await res.text()
163
178
 
164
179
  if (!res.ok) {
165
- let msg = [status, statusText, data?.message, typeof data?.error === 'object' ? JSON.stringify(data.error) : data?.error, data?.description, data?.cause].filter(Boolean).join(' – ')
180
+ let msg = [status, statusText, data?.message, typeof data?.error === 'object' ? JSON.stringify(data.error) : data?.error, data?.description, data?.cause, typeof data?.details === 'object' ? JSON.stringify(data.details) : data?.details].filter(Boolean).join(' – ')
166
181
  if (res.status === 429 && res.headers.get('retry-after')) msg += ` – retry after ${res.headers.get('retry-after')}`
167
182
  throw Object.assign(new Error(msg), { status, statusText, headers, code: data?.error?.code })
168
183
  }
@@ -249,6 +264,7 @@ module.exports = {
249
264
  readData,
250
265
  readDataCsv,
251
266
  token,
267
+ authUrl,
252
268
  fetchResiliently,
253
269
  getAppId,
254
270
  deepMerge
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sap/cds-mtxs",
3
- "version": "4.0.2",
3
+ "version": "4.1.0",
4
4
  "description": "SAP Cloud Application Programming Model - Multitenancy library",
5
5
  "homepage": "https://cap.cloud.sap/",
6
6
  "keywords": [
@@ -5,6 +5,7 @@ const { fetchResiliently, token } = require('../../lib/utils')
5
5
  const DeploymentService = 'cds.xt.DeploymentService'
6
6
  const JobsService = 'cds.xt.JobsService'
7
7
  const Tenants = 'cds.xt.Tenants'
8
+ const { SKIPPED_DB } = require('../jobs-service')
8
9
 
9
10
  // we need to run in multitenancy mode for t0 ops
10
11
  if (!cds.env.requires.multitenancy) cds.env.requires.multitenancy = true
@@ -112,9 +113,9 @@ module.exports = class ProvisioningService extends cds.ApplicationService {
112
113
  )).map(({ ID, metadata, createdAt, modifiedAt }) => ({ subscribedTenantId: ID, ...this._toMetadataJson(metadata, ID), createdAt, modifiedAt }))
113
114
  }
114
115
 
115
- async _getTenants() {
116
+ async _getTenants(options) {
116
117
  const ds = await cds.connect.to(DeploymentService)
117
- const all = await ds.getTenants()
118
+ const all = await ds.getTenants(options)
118
119
  if (cds.env.requires[this.name]?.upgrade?.ignoreNonExistingContainers) {
119
120
  const containers = await ds.getContainers()
120
121
  return all.filter(t => containers.includes(t))
@@ -129,9 +130,37 @@ module.exports = class ProvisioningService extends cds.ApplicationService {
129
130
  const all = tenantIds.includes('*')
130
131
  const sharedGenDir = !main.requires.extensibility
131
132
  if (sharedGenDir) options.skipResources ??= sharedGenDir
132
- const tenants = all ? await this._getTenants() : tenantIds
133
+
134
+ // Merge static upgrade config with dynamic options (dynamic takes precedence)
135
+ const upgradeConfig = cds.env.requires[this.name]?.upgrade
136
+ if (upgradeConfig) {
137
+ options.upgrade = cds.utils.merge({ ...upgradeConfig }, options.upgrade)
138
+ }
139
+
140
+ // Determine skipAfter: from explicit option or derived from package.json mtime
141
+ let skipAfter = options?.upgrade?.skipAfter
142
+ if (!skipAfter && options?.upgrade?.skipAlreadyUpgraded) {
143
+ const { stat } = require('fs/promises')
144
+ const { mtime } = await stat(cds.root + '/package.json')
145
+ skipAfter = mtime.toISOString()
146
+ }
147
+
148
+ const tenantsOptions = skipAfter ? { skipAfter } : undefined
149
+ let tenants
150
+ let skipped = []
151
+ if (all) {
152
+ tenants = await this._getTenants(tenantsOptions)
153
+ } else if (skipAfter) {
154
+ const upgradeable = await this._getTenants(tenantsOptions)
155
+ tenants = tenantIds.filter(t => upgradeable.includes(t))
156
+ // Requested tenants filtered out by skipAfter are considered already upgraded.
157
+ skipped = tenantIds.filter(t => !upgradeable.includes(t))
158
+ } else {
159
+ tenants = tenantIds
160
+ }
161
+
133
162
  const { isSync } = this._parseHeaders(cds.context.http?.req.headers)
134
- if (!tenants.length && isSync) return
163
+ if (!tenants.length && !skipped.length && isSync) return
135
164
  if (sharedGenDir && cds.requires.db.kind === 'hana') { // REVISIT: Ideally part of HANA plugin
136
165
  const { resources4, csvs4, directory4 } = require('../plugins/hana')
137
166
  const out = await directory4('base', true)
@@ -141,13 +170,26 @@ module.exports = class ProvisioningService extends cds.ApplicationService {
141
170
  const { 'cds.xt.DeploymentService': ds } = cds.services
142
171
  const js = await cds.connect.to(JobsService)
143
172
  const { clusterSize = 3 } = js._config()
144
- const dbToTenants = (clusterSize > 1 && ds) ? await ds.tenantsByDb(tenants) : [new Set(tenants)]
145
- LOG.info('upgrading', { tenants })
173
+ const dbToTenants = (tenants.length && clusterSize > 1 && ds) ? await ds.tenantsByDb(tenants) : [new Set(tenants)]
174
+ // Skipped tenants go into a separate pseudo-db cluster so JobsService records them (and their
175
+ // tasks) as FINISHED without upgrading.
176
+ let clusters = dbToTenants
177
+ if (skipped.length) {
178
+ // Normalize array-form to a db-id-keyed object so skipped tenants get their own cluster.
179
+ clusters = { ...dbToTenants }
180
+ // Drop skipped tenants from regular clusters (some tenantsByDb impls ignore the tenant list).
181
+ const skippedSet = new Set(skipped)
182
+ for (const db of Object.keys(clusters)) {
183
+ clusters[db] = new Set([...clusters[db]].filter(t => tenants.includes(t) && !skippedSet.has(t)))
184
+ }
185
+ clusters[SKIPPED_DB] = skippedSet
186
+ }
187
+ LOG.info('upgrading', { tenants, skipped })
146
188
 
147
189
 
148
190
  return await new Promise((resolve, reject) => {
149
191
  const tx = js.tx({ tenant: cds.context.tenant, user: new cds.User.Privileged() })
150
- const job = tx.enqueue(DeploymentService, 'upgrade', dbToTenants, { options }, async error => {
192
+ const job = tx.enqueue(DeploymentService, 'upgrade', clusters, { options }, async error => {
151
193
  if (error) {
152
194
  await this._sendCallback('FAILED', `Tenant upgrade failed with error '${error}'`)
153
195
  if (isSync) reject(new Error(error))
@@ -291,7 +333,7 @@ module.exports = class ProvisioningService extends cds.ApplicationService {
291
333
  async _token() {
292
334
  const serviceName = 'cds.xt.' + this.constructor.name
293
335
  const cred = this._getCredentials()
294
- const raw = await token(cred, { query: { response_type: 'token' } })
336
+ const raw = await token(cred, { query: { response_type: 'token' }, form: { grant_type: 'client_credentials' } })
295
337
  const { access_token } = JSON.parse(raw)
296
338
  if (!access_token) cds.error(`could not get token for ${serviceName}: token is empty`)
297
339
  return access_token
@@ -303,4 +345,17 @@ module.exports = class ProvisioningService extends cds.ApplicationService {
303
345
  const cred = s?.credentials ?? multitenancy?.credentials ?? {}
304
346
  return cred
305
347
  }
348
+
349
+ _resolveCallbackUrl(callbackPath, baseUrl) {
350
+ if (!callbackPath || !baseUrl) return undefined
351
+ if (typeof callbackPath !== 'string' || !/^\/(?![/\\])/.test(callbackPath) || callbackPath.includes('\\')) {
352
+ throw cds.error(`Invalid callback path: ${callbackPath}`, { status: 400 })
353
+ }
354
+ const base = new URL(baseUrl)
355
+ const resolved = new URL(callbackPath, base)
356
+ if (resolved.origin !== base.origin) {
357
+ throw cds.error(`Invalid callback path: ${callbackPath}`, { status: 400 })
358
+ }
359
+ return resolved.toString()
360
+ }
306
361
  }
@@ -28,7 +28,7 @@ module.exports = class SaasProvisioningService extends require('./abstract-provi
28
28
  _parseHeaders(headers) {
29
29
  const { prefer, status_callback, mtx_status_callback } = headers ?? {}
30
30
  const { saas_registry_url } = this._getCredentials()
31
- const callbackUrl = mtx_status_callback ?? (status_callback && saas_registry_url && new URL(status_callback, saas_registry_url).toString())
31
+ const callbackUrl = mtx_status_callback ?? this._resolveCallbackUrl(status_callback, saas_registry_url)
32
32
  return {
33
33
  callbackUrl,
34
34
  isCustomCallback: !!mtx_status_callback,
@@ -53,7 +53,7 @@ module.exports = class SmsProvisioningService extends require('./abstract-provis
53
53
  _parseHeaders(headers) {
54
54
  const { prefer, status_callback } = headers ?? {}
55
55
  const { subscription_manager_url } = this._getCredentials()
56
- const callbackUrl = (status_callback && subscription_manager_url && new URL(status_callback, subscription_manager_url).toString())
56
+ const callbackUrl = this._resolveCallbackUrl(status_callback, subscription_manager_url)
57
57
  return {
58
58
  callbackUrl,
59
59
  isSync: !(prefer?.includes('respond-async') || callbackUrl)
@@ -102,11 +102,31 @@ module.exports = class SmsProvisioningService extends require('./abstract-provis
102
102
  const isIssuerValid = isValid(callback_certificate_issuer, cert.issuer)
103
103
  const isSubjectValid = isValid(callback_certificate_subject, cert.subject)
104
104
 
105
+ function parseDN(dn) {
106
+ // Node's X509Certificate.issuer/subject uses newline as RDN separator.
107
+ // Each line is a single "attribute=value" pair. Multiple lines with the
108
+ // same attribute (e.g. multiple OU= entries) are all collected into an array.
109
+ const fields = {}
110
+ for (const line of dn.split('\n')) {
111
+ const eq = line.indexOf('=')
112
+ if (eq === -1) continue
113
+ const attr = line.slice(0, eq).trim().toUpperCase()
114
+ const val = line.slice(eq + 1).trim()
115
+ if (fields[attr]) fields[attr].push(val)
116
+ else fields[attr] = [val]
117
+ }
118
+ return fields
119
+ }
120
+
105
121
  function isValid(expected, fromCert) {
122
+ const certFields = parseDN(fromCert)
106
123
  return Object.entries(JSON.parse(expected)).every(([k, v]) => {
107
124
  if (v === '*') return true
108
- if (Array.isArray(v)) return v.some(v => fromCert.includes(`${k}=${v}`))
109
- else return fromCert.includes(`${k}=${v}`)
125
+ const certVals = certFields[k.toUpperCase()] ?? []
126
+ // For array expected values every element must be present in the cert.
127
+ // For a scalar expected value the cert must contain that exact value.
128
+ if (Array.isArray(v)) return v.every(allowed => certVals.includes(allowed))
129
+ else return certVals.includes(v)
110
130
  })
111
131
  }
112
132