@sap/cds-dk 10.1.0 → 10.1.2

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 (114) hide show
  1. package/CHANGELOG.md +24 -0
  2. package/bin/bind.js +4 -0
  3. package/bin/build.js +1 -0
  4. package/bin/compile/index.js +12 -11
  5. package/bin/completion/index.js +21 -10
  6. package/bin/completion/scripts/cds.fish +179 -127
  7. package/bin/completion/scripts/cds.ps1 +88 -14
  8. package/bin/completion/scripts/cds.sh +71 -5
  9. package/bin/completion/scripts/cds.zsh +81 -15
  10. package/bin/completion/templates/cds-tmpl.ps1 +15 -6
  11. package/bin/deploy/index.js +7 -3
  12. package/bin/env.js +4 -1
  13. package/bin/export.js +9 -1
  14. package/bin/import.js +4 -1
  15. package/bin/init.js +1 -0
  16. package/bin/inspect.js +3 -1
  17. package/bin/lint.js +5 -3
  18. package/bin/minify.js +6 -1
  19. package/bin/repl.js +11 -2
  20. package/bin/run.js +12 -1
  21. package/bin/serve.js +13 -2
  22. package/bin/up.js +4 -1
  23. package/bin/util/containerize.js +2 -0
  24. package/bin/watch/index.js +8 -1
  25. package/lib/build/plugins/internal.js +0 -26
  26. package/lib/build/plugins/nodejs/index.js +8 -3
  27. package/lib/build/util.js +2 -4
  28. package/lib/cds.js +8 -2
  29. package/lib/init/constants.js +1 -1
  30. package/lib/init/merge.js +21 -17
  31. package/lib/init/template/cloud-logging/index.js +3 -3
  32. package/lib/init/template/data/as-json.js +3 -0
  33. package/lib/init/template/destination/index.js +3 -3
  34. package/lib/init/template/enterprise-messaging/index.js +4 -1
  35. package/lib/init/template/event-mesh/index.js +3 -0
  36. package/lib/init/template/multitenancy/index.js +2 -2
  37. package/lib/init/template/workzone-standard/index.js +1 -1
  38. package/node_modules/@cap-js/db-service/CHANGELOG.md +30 -0
  39. package/node_modules/@cap-js/db-service/lib/common/generic-pool.js +4 -7
  40. package/node_modules/@cap-js/db-service/lib/cqn2pql.js +1 -1
  41. package/node_modules/@cap-js/db-service/lib/cqn2sql.js +15 -11
  42. package/node_modules/@cap-js/db-service/lib/cqn4sql.js +125 -21
  43. package/node_modules/@cap-js/db-service/lib/infer/index.js +27 -16
  44. package/node_modules/@cap-js/db-service/lib/recursion-guard.js +40 -0
  45. package/node_modules/@cap-js/db-service/lib/search.js +1 -1
  46. package/node_modules/@cap-js/db-service/package.json +1 -1
  47. package/node_modules/@cap-js/sqlite/CHANGELOG.md +28 -0
  48. package/node_modules/@cap-js/sqlite/lib/SQLiteService.js +110 -14
  49. package/node_modules/@cap-js/sqlite/package.json +3 -3
  50. package/node_modules/@sap/cds/CHANGELOG.md +19 -0
  51. package/node_modules/@sap/cds/common.cds +3 -3
  52. package/node_modules/@sap/cds/lib/compile/minify.js +3 -1
  53. package/node_modules/@sap/cds/lib/env/cds-env.js +1 -1
  54. package/node_modules/@sap/cds/lib/env/cds-requires.js +1 -0
  55. package/node_modules/@sap/cds/libx/_runtime/common/generic/crud.js +7 -1
  56. package/node_modules/@sap/cds/libx/_runtime/common/utils/cqn.js +1 -1
  57. package/node_modules/@sap/cds/libx/_runtime/fiori/lean-draft.js +1 -1
  58. package/node_modules/@sap/cds/libx/_runtime/messaging/common-utils/normalizeIncomingMessage.js +1 -1
  59. package/node_modules/@sap/cds/libx/_runtime/messaging/kafka.js +1 -1
  60. package/node_modules/@sap/cds/libx/http/location.js +10 -11
  61. package/node_modules/@sap/cds/libx/odata/middleware/read.js +1 -1
  62. package/node_modules/@sap/cds/libx/queue/SchedulingService.js +4 -0
  63. package/node_modules/@sap/cds/libx/queue/index.js +44 -10
  64. package/node_modules/@sap/cds/libx/queue/processing.js +47 -16
  65. package/node_modules/@sap/cds/libx/queue/utils.js +3 -0
  66. package/node_modules/@sap/cds/package.json +1 -1
  67. package/node_modules/@sap/cds/tasks/enterprise-messaging-deploy.js +13 -5
  68. package/node_modules/@sap/cds-compiler/CHANGELOG.md +15 -0
  69. package/node_modules/@sap/cds-compiler/lib/compiler/resolve.js +7 -3
  70. package/node_modules/@sap/cds-compiler/lib/model/csnUtils.js +2 -0
  71. package/node_modules/@sap/cds-compiler/lib/transform/db/applyTransformations.js +12 -0
  72. package/node_modules/@sap/cds-compiler/lib/transform/db/associations.js +5 -3
  73. package/node_modules/@sap/cds-compiler/lib/transform/db/flattening.js +4 -3
  74. package/node_modules/@sap/cds-compiler/lib/transform/effective/flattening.js +17 -1
  75. package/node_modules/@sap/cds-compiler/lib/transform/effective/main.js +19 -0
  76. package/node_modules/@sap/cds-compiler/lib/transform/fioriTreeViews.js +12 -1
  77. package/node_modules/@sap/cds-compiler/lib/transform/forRelationalDB.js +1 -1
  78. package/node_modules/@sap/cds-compiler/lib/transform/odata/adaptAnnotationRefs.js +11 -5
  79. package/node_modules/@sap/cds-compiler/lib/transform/translateAssocsToJoins.js +29 -2
  80. package/node_modules/@sap/cds-compiler/package.json +1 -1
  81. package/node_modules/@sap/cds-mtxs/CHANGELOG.md +34 -0
  82. package/node_modules/@sap/cds-mtxs/bin/cds-mtx-migrate.js +26 -5
  83. package/node_modules/@sap/cds-mtxs/bin/cds-mtx.js +3 -0
  84. package/node_modules/@sap/cds-mtxs/lib/utils.js +5 -4
  85. package/node_modules/@sap/cds-mtxs/package.json +1 -1
  86. package/node_modules/@sap/cds-mtxs/srv/cf/abstract-provisioning-service.js +62 -7
  87. package/node_modules/@sap/cds-mtxs/srv/cf/saas-provisioning-service.js +1 -1
  88. package/node_modules/@sap/cds-mtxs/srv/cf/sms-provisioning-service.js +23 -3
  89. package/node_modules/@sap/cds-mtxs/srv/extensibility/compat/update.js +15 -0
  90. package/node_modules/@sap/cds-mtxs/srv/extensibility/linter/annotations.js +2 -2
  91. package/node_modules/@sap/cds-mtxs/srv/extensibility/linter/config.js +2 -0
  92. package/node_modules/@sap/cds-mtxs/srv/extensibility/linter/index.js +2 -0
  93. package/node_modules/@sap/cds-mtxs/srv/extensibility/linter/protected-namespace.js +45 -0
  94. package/node_modules/@sap/cds-mtxs/srv/extensibility-service.js +8 -3
  95. package/node_modules/@sap/cds-mtxs/srv/jobs-service.js +49 -10
  96. package/node_modules/@sap/cds-mtxs/srv/model-provider.js +11 -2
  97. package/node_modules/@sap/cds-mtxs/srv/plugins/common.js +19 -12
  98. package/node_modules/@sap/cds-mtxs/srv/plugins/hana/hdi-ctnr-mgr.js +6 -3
  99. package/node_modules/@sap/cds-mtxs/srv/plugins/hana/serverless.js +271 -0
  100. package/node_modules/@sap/cds-mtxs/srv/plugins/hana.js +3 -2
  101. package/node_modules/@sap/cds-mtxs/srv/worker/job-worker-pool.js +212 -0
  102. package/node_modules/@sap/cds-mtxs/srv/worker/job-worker.js +61 -0
  103. package/node_modules/proxy-addr/HISTORY.md +60 -54
  104. package/node_modules/proxy-addr/README.md +1 -1
  105. package/node_modules/proxy-addr/index.js +32 -0
  106. package/node_modules/proxy-addr/package.json +8 -4
  107. package/node_modules/yaml/browser/dist/compose/resolve-flow-scalar.js +19 -18
  108. package/node_modules/yaml/browser/dist/nodes/Alias.js +25 -23
  109. package/node_modules/yaml/dist/compose/resolve-flow-scalar.js +19 -18
  110. package/node_modules/yaml/dist/nodes/Alias.js +25 -23
  111. package/node_modules/yaml/package.json +1 -1
  112. package/package.json +2 -2
  113. /package/node_modules/@sap/cds-mtxs/srv/worker/{pool.js → model-worker-pool.js} +0 -0
  114. /package/node_modules/@sap/cds-mtxs/srv/worker/{load.js → model-worker.js} +0 -0
package/bin/import.js CHANGED
@@ -1,9 +1,12 @@
1
+ const { FILE, DIR } = require('./completion')
1
2
  module.exports = Object.assign(_import, {
2
3
  _name: 'import',
3
4
  staticCompletions: () => ({
4
5
  '--from': ['asyncapi', 'edmx', 'openapi'],
5
6
  '--as': ['cds', 'csn', 'json'],
6
- '_': { type: ':file', ext: ['json', 'xml', 'edmx'] },
7
+ '--out': DIR(),
8
+ '-o': DIR(),
9
+ '_': FILE('json,xml,edmx'),
7
10
  }),
8
11
  options: [ '--services', '--for', '--out', '--as', '--name', '--include-namespaces', '--dependencies', '--from', '--config', '--destination', '--group', '--into-namespace' ],
9
12
  shortcuts: [ '-s', '-4', '-o', '-as', '-n', '-ns', '-d',undefined, '-c', undefined, undefined, '-2', '-', '-f', '-b'],
package/bin/init.js CHANGED
@@ -10,6 +10,7 @@ module.exports = Object.assign(init, {
10
10
  staticCompletions: () => ({
11
11
  '--add': COMMA(require('../bin/completion/completionFs').readTemplates([])),
12
12
  '--java:package': DIR(),
13
+ '--base': DIR(),
13
14
  }),
14
15
  options: ['--add', '--java:mvn', '--java:package', '--lint:custom', '--lint:custom:example', '--no-db', '--base'],
15
16
  shortcuts: ['-a'],
package/bin/inspect.js CHANGED
@@ -9,7 +9,9 @@ const { FILE } = require('./completion');
9
9
  module.exports = Object.assign (inspect, {
10
10
  staticCompletions: () => ({
11
11
  '--kind': ['entity', 'aspect', 'type', 'service'],
12
- '_': FILE('cds,json'),
12
+ '--list': ['names', 'elements', 'keys', 'associations', 'compositions', 'ids', 'sources', 'all'],
13
+ '-l': ['names', 'elements', 'keys', 'associations', 'compositions', 'ids', 'sources', 'all'],
14
+ '_': FILE('cds,json', { dirs: true }),
13
15
  }),
14
16
  options: [ '--stats', '--list', '--kind' ],
15
17
  shortcuts: [ '-s', '-l', '-k' ],
package/bin/lint.js CHANGED
@@ -1,13 +1,15 @@
1
+ const { FILE } = require('./completion')
1
2
  const Linter = require('../lib/lint/Linter');
2
3
  const linter = new Linter();
3
4
 
4
- // cds cli exec expects an executable object with various additional properties.
5
- // As most of the linting functionality is bound to a class instance, we need to wrap the instance
6
- // to not lose the this-context and attach the additional properties to the function.
7
5
  const lint = (...args) => linter.lint(...args)
8
6
  const additionalProperties = Object.entries(linter.init())
9
7
  .map(([key, value]) => [key, { value }])
10
8
 
11
9
  Object.defineProperties(lint, Object.fromEntries(additionalProperties))
12
10
 
11
+ lint.staticCompletions = () => ({
12
+ '_': FILE('js,ts,cds', { dirs: true }),
13
+ })
14
+
13
15
  module.exports = lint
package/bin/minify.js CHANGED
@@ -119,4 +119,9 @@ class Dryifier extends Minifier {
119
119
  }
120
120
  }
121
121
 
122
- module.exports = Object.assign (Minify._for_cds_dk(), { Minify })
122
+ const { FILE } = require('./completion')
123
+ module.exports = Object.assign (Minify._for_cds_dk(), { Minify,
124
+ staticCompletions: () => ({
125
+ '_': FILE('cds,json', { dirs: true }),
126
+ })
127
+ })
package/bin/repl.js CHANGED
@@ -1,6 +1,15 @@
1
+ const { DIR } = require('./completion');
1
2
  module.exports = Object.assign (repl, {
2
- options: [ '--run', '--use', '--ql' ],
3
- shortcuts: [ '-r', '-u' ],
3
+ options: [ '--run', '--use' ],
4
+ flags: [ '--ql' ],
5
+ shortcuts: ['-r', '-u'],
6
+ staticCompletions: () => ({
7
+ '_': DIR(),
8
+ '--run': DIR(),
9
+ '-r': DIR(),
10
+ '--use': ['xl', 'ql', 'entities', 'services'],
11
+ '-u': ['xl', 'ql', 'entities', 'services'],
12
+ }),
4
13
  help: `
5
14
  # SYNOPSIS
6
15
 
package/bin/run.js CHANGED
@@ -1,6 +1,17 @@
1
+ const { DIR } = require('./completion');
1
2
  const serve = require ('./serve')
2
3
  module.exports = Object.assign (
3
- cds_run, serve, {help: `
4
+ cds_run, serve, {
5
+ staticCompletions: () => ({
6
+ '_': DIR(),
7
+ '--from': DIR(),
8
+ '-f': DIR(),
9
+ '--with': DIR(),
10
+ '-w': DIR(),
11
+ '--to': ['odata', 'fiori', 'rest'],
12
+ '-2': ['odata', 'fiori', 'rest'],
13
+ }),
14
+ help: `
4
15
  # SYNOPSIS
5
16
 
6
17
  *cds run* [<project>] [<options>]
package/bin/serve.js CHANGED
@@ -1,6 +1,17 @@
1
1
  // allows executing `cds serve` even when `cds` executable is removed from `@sap/cds`
2
-
3
- module.exports = require(_local('@sap/cds/bin/serve'))
2
+ const { DIR } = require('./completion');
3
+ const serve = require(_local('@sap/cds/bin/serve'))
4
+ module.exports = Object.assign(serve, {
5
+ staticCompletions: () => ({
6
+ '_': DIR(),
7
+ '--from': DIR(),
8
+ '-f': DIR(),
9
+ '--with': DIR(),
10
+ '-w': DIR(),
11
+ '--to': ['odata', 'fiori', 'rest'],
12
+ '-2': ['odata', 'fiori', 'rest'],
13
+ }),
14
+ })
4
15
 
5
16
  function _local (id) {
6
17
  return require.resolve (id, { paths: [ process.cwd(), __dirname ] })
package/bin/up.js CHANGED
@@ -1,6 +1,9 @@
1
+ const { FILE } = require('./completion');
2
+
1
3
  module.exports = Object.assign(up, {
2
4
  staticCompletions: () => ({
3
- '--to': ['cf', 'k8s'],
5
+ '--to': ['cf', 'k8s'],
6
+ '--overlay': FILE('mtaext')
4
7
  }),
5
8
  options: ['--to', '--namespace', '--overlay'],
6
9
  shortcuts: ['-2', '-n'],
@@ -59,6 +59,7 @@ function processCommands(bp) {
59
59
  }
60
60
  function processBuildpack(bp, name, tag, clearCache = false, image, publish = false) {
61
61
  let { type, path, env, builder } = bp.buildpack
62
+ const dependencyMirrors = Object.keys(process.env).filter(key => key.startsWith('BP_DEPENDENCY_MIRROR_') && process.env[key])
62
63
  type = type ? type.split(',').map(t => buildpacks.has(t.trim()) ? `paketo-buildpacks/${t.trim()}` : t.trim()) : []
63
64
  if (builders.has(builder)) builder = `paketobuildpacks/${builder}`
64
65
  const cacheArgs = clearCache ? ['--clear-cache'] : []
@@ -69,6 +70,7 @@ function processBuildpack(bp, name, tag, clearCache = false, image, publish = fa
69
70
  ...type.flatMap(t => appendSubcommand(t, '--buildpack')),
70
71
  ...appendSubcommand(builder, '--builder'),
71
72
  ...appendSubcommand(env, '--env'),
73
+ ...dependencyMirrors.flatMap(key => ['--env', key]),
72
74
  ...(publish ? ['--publish'] : []),
73
75
  ...cacheArgs
74
76
  ]]
@@ -3,10 +3,17 @@ const { DIR } = require('../completion')
3
3
 
4
4
  module.exports = Object.assign ( watch, {
5
5
  staticCompletions: () => ({
6
+ '_': DIR(),
7
+ '--from': DIR(),
8
+ '-f': DIR(),
9
+ '--with': DIR(),
10
+ '-w': DIR(),
11
+ '--to': ['odata', 'fiori', 'rest'],
12
+ '-2': ['odata', 'fiori', 'rest'],
6
13
  '--experimental-hot-reload': ['true', 'false'],
7
14
  '--livereload': ['false'],
8
15
  '--include': DIR(),
9
- '--exclude': DIR(),
16
+ '--exclude': DIR()
10
17
  }),
11
18
  flags: [ '--in-memory', '--in-memory?', '--with-mocks', '--with-mtx' ],
12
19
  options: [ '--port', ...watchOnlyOptions ],
@@ -132,36 +132,10 @@ module.exports = class InternalBuildPlugin extends require('./plugin') {
132
132
  // bundleDest might be null
133
133
  if (bundleDest && Object.keys(bundles).length > 0) {
134
134
  await this.write(bundles).to(file)
135
- // also collect and write messages from plugins
136
- await this.collectMessageBundles(bundleDest, model)
137
135
  return { file, bundles }
138
136
  }
139
137
  return null
140
138
  }
141
-
142
- /**
143
- * Collect and write message bundles from CAP plugins into messages.properties files.
144
- * @param {string} bundleDest
145
- * @param {Object} model - passed for signature consistency with subclass overrides; not used here
146
- * as Java does not require neighborhood resolution of plugin i18n files.
147
- */
148
- // eslint-disable-next-line no-unused-vars
149
- async collectMessageBundles(bundleDest, model) {
150
- const messageBundle = cds.i18n.bundle4('messages')
151
- const messagesData = messageBundle.translations4?.(cds.env.i18n.languages) ?? {}
152
-
153
- // Write messages.properties files for each locale
154
- for (const [locale, messages] of Object.entries(messagesData)) {
155
- if (Object.keys(messages).length > 0) {
156
- const fileName = locale ? `messages_${locale}.properties` : 'messages.properties'
157
- const filePath = path.join(bundleDest, fileName)
158
- const content = Object.entries(messages)
159
- .map(([key, value]) => `${key}=${value}`)
160
- .join('\n') + '\n'
161
- await this.write(content).to(filePath)
162
- }
163
- }
164
- }
165
139
  }
166
140
 
167
141
  // Emits the 'compile.for.runtime' event, as the produced CSN is for runtime usage.
@@ -10,6 +10,7 @@ const DEBUG = cds.debug('cli|build')
10
10
  const NODE_ENGINE_VERSION = '^24'
11
11
  const { exec } = require('node:child_process')
12
12
  const pexec = require('node:util').promisify(exec)
13
+ const escapeLineBreaks = value => value.replace(/\r/g, '\\u000D').replace(/\n/g, '\\u000A')
13
14
 
14
15
  module.exports = class NodejsBuildPlugin extends require('../edmx') {
15
16
 
@@ -353,15 +354,19 @@ module.exports = class NodejsBuildPlugin extends require('../edmx') {
353
354
  return Array.isArray(parsed) ? parsed : Object.values(parsed)
354
355
  }
355
356
 
356
- // Override to pass model for neighborhood resolution of plugin i18n files.
357
- // Cannot be done in InternalBuildPlugin because Java relies on the model-less behaviour.
357
+ async collectLanguageBundles(model, bundleDest) {
358
+ const result = await super.collectLanguageBundles(model, bundleDest)
359
+ if (result) await this.collectMessageBundles(bundleDest, model)
360
+ return result
361
+ }
362
+
358
363
  async collectMessageBundles(bundleDest, model) {
359
364
  const messageBundle = cds.i18n.bundle4('messages', { model })
360
365
  const messagesData = messageBundle.translations4?.(cds.env.i18n.languages) ?? {}
361
366
  for (const [locale, messages] of Object.entries(messagesData)) {
362
367
  if (Object.keys(messages).length > 0) {
363
368
  const fileName = locale ? `messages_${locale}.properties` : 'messages.properties'
364
- const content = Object.entries(messages).map(([k, v]) => `${k}=${v}`).join('\n') + '\n'
369
+ const content = Object.entries(messages).map(([k, v]) => `${k}=${escapeLineBreaks(v)}`).join('\n') + '\n'
365
370
  await this.write(content).to(path.join(bundleDest, fileName))
366
371
  }
367
372
  }
package/lib/build/util.js CHANGED
@@ -8,7 +8,6 @@ const { exists } = cds.utils
8
8
  const execAsync = require('node:util').promisify(cp.exec)
9
9
  const { isRoot } = require('../util/fs')
10
10
 
11
- const IS_WIN = require('node:os').platform() === 'win32'
12
11
  const { SEVERITY_ERROR, FILE_EXT_CDS, NODEJS_MODEL_EXCLUDE_LIST } = require('./constants');
13
12
  const DEBUG = cds.debug('cli|build')
14
13
 
@@ -288,7 +287,7 @@ const _cachedWsRoots = new Map()
288
287
  * could contain globs. Glob resolution will be supported.
289
288
  * The result is cached by workspace root!
290
289
  * natively in the future (fs.glob is in experimental state as of Node23).
291
- * @param {import('fs').PathLike} wsRoot - the root directory of the workspace
290
+ * @param {string} wsRoot - the root directory of the workspace
292
291
  * @returns {Promise<string[]>} list of package paths, or an empty array if either
293
292
  * no workspaces are defined or have not been installed.
294
293
  */
@@ -302,8 +301,7 @@ async function _execNpmWs(wsRoot) {
302
301
  try {
303
302
  const cmdLine = 'npm ls --ws --json'
304
303
  DEBUG?.(`execute ${cmdLine}`)
305
- // @ts-expect-error - TODO: shell should be a string. But can probably be removed altogether?
306
- const { stdout } = await execAsync(cmdLine, { shell: IS_WIN, stdio: ['inherit', 'pipe', 'inherit'], cwd: wsRoot })
304
+ const { stdout } = await execAsync(cmdLine, { cwd: wsRoot })
307
305
  const jsonResult = JSON.parse(stdout)
308
306
  if (jsonResult.dependencies) {
309
307
  result = Object.keys(jsonResult.dependencies)
package/lib/cds.js CHANGED
@@ -11,10 +11,16 @@ module.exports = Object.defineProperties (cds, {
11
11
 
12
12
  function _local (id, ...morePaths) {
13
13
  return require(require.resolve (id, {paths:[
14
- process.cwd(), // project-local module is preferred
14
+ _cwd(), // project-local module is preferred
15
15
  __dirname, // otherwise from our own dependencies
16
16
  ...morePaths
17
- ]}))
17
+ ].filter(Boolean)})) // drop empty paths (e.g. _cwd()/_npmRoot() fallbacks); require.resolve expects real paths
18
+ }
19
+
20
+ function _cwd() {
21
+ // process.cwd() throws ENOENT (uv_cwd) when the cwd was deleted
22
+ try { return process.cwd() }
23
+ catch (e) { if (e.code === 'ENOENT') return '' }
18
24
  }
19
25
 
20
26
  function _npmRoot() {
@@ -5,7 +5,7 @@ module.exports = {
5
5
  COMMAND_ADD: 'add',
6
6
 
7
7
  JAVA_LTS_VERSIONS: [25],
8
- MAVEN_ARCHETYPE_VERSION: '5.0.1',
8
+ MAVEN_ARCHETYPE_VERSION: '5.1.1',
9
9
 
10
10
  OPTIONS: Object.freeze({
11
11
  COMPLETION: 'completion',
package/lib/init/merge.js CHANGED
@@ -165,8 +165,28 @@ async function _mergeYAML(into, from, project, semantics = {}) {
165
165
  t.value = s.value
166
166
  }
167
167
  }
168
+ const deleteEntries = entries => {
169
+ for (const { item, key, relationships: refs } of entries) {
170
+ const seq = locate(target, item.in)
171
+ const node = locate(target, item)
172
+ if (!YAML.isSeq(seq) || !node) continue
173
+ const removed = node.toJSON()
174
+ key ? node.deleteIn(path(key)) : seq.delete(seq.items.indexOf(node))
175
+ for (const { removeProperty, allWithin } of refs ?? []) {
176
+ const [parentPath, childPath, prop] = allWithin
177
+ for (const parent of locate(target, parentPath)?.items ?? []) {
178
+ const list = parent.getIn(path(childPath))
179
+ const i = list?.items?.findIndex(n => n.get?.(prop) === removed[removeProperty]) ?? -1
180
+ if (i > -1) list.delete(i)
181
+ }
182
+ }
183
+ }
184
+ }
168
185
  mergeNode(target.contents, source.contents)
169
186
 
187
+ // Deletions: remove the matched node (or a sub-key), then cascade-remove references to it.
188
+ deleteEntries(deletions.filter(({ key }) => !key))
189
+
170
190
  // Placement: add matched (and relationship-referenced) template entries not already present.
171
191
  const isEntry = e => e && typeof e === 'object' && !Array.isArray(e) && e.in !== undefined
172
192
  const insertNode = ins => Array.isArray(ins) ? (ins.length === 1 ? ins[0] : undefined) : ins
@@ -208,22 +228,7 @@ async function _mergeYAML(into, from, project, semantics = {}) {
208
228
  }
209
229
  }
210
230
 
211
- // Deletions: remove the matched node (or a sub-key), then cascade-remove references to it.
212
- for (const { item, key, relationships: refs } of deletions) {
213
- const seq = locate(target, item.in)
214
- const node = locate(target, item)
215
- if (!YAML.isSeq(seq) || !node) continue
216
- const removed = node.toJSON()
217
- key ? node.deleteIn(path(key)) : seq.delete(seq.items.indexOf(node))
218
- for (const { removeProperty, allWithin } of refs ?? []) {
219
- const [parentPath, childPath, prop] = allWithin
220
- for (const parent of locate(target, parentPath)?.items ?? []) {
221
- const list = parent.getIn(path(childPath))
222
- const i = list?.items?.findIndex(n => n.get?.(prop) === removed[removeProperty]) ?? -1
223
- if (i > -1) list.delete(i)
224
- }
225
- }
226
- }
231
+ deleteEntries(deletions.filter(({ key }) => key))
227
232
 
228
233
  typeof into === 'string' && await writeYAML(into, target)
229
234
  return target
@@ -349,4 +354,3 @@ module.exports.merge = (...src) => {
349
354
  * @property {Deletion[]} [deletions] - matched nodes (or sub-keys) to delete
350
355
  * @property {Relationship[]} [relationships] - relationships to wire into target sequences
351
356
  */
352
-
@@ -40,7 +40,7 @@ module.exports = class extends require('../../plugin') {
40
40
 
41
41
  async combine(facet) {
42
42
  const project = readProject(facet)
43
- const { hasMta, addMta, addKyma, addApprouter, isJava, addMultitenancy, srv } = project
43
+ const { hasMta, addMta, addKyma, addApprouter, hasApprouter, isJava, addMultitenancy, hasMultitenancy, srv } = project
44
44
 
45
45
  if (addMta || (addMultitenancy || addApprouter) && hasMta) {
46
46
  const cloudLogging = {
@@ -58,14 +58,14 @@ module.exports = class extends require('../../plugin') {
58
58
  into: [srvModule, 'requires', 'name']
59
59
  })
60
60
  }
61
- if (addMultitenancy) {
61
+ if (hasMultitenancy) {
62
62
  const mtxSidecar = mtxSidecar4(isJava ? 'mtx/sidecar' : 'gen/mtx/sidecar')
63
63
  relationships.push({
64
64
  insert: [cloudLogging, 'name'],
65
65
  into: [mtxSidecar, 'requires', 'name']
66
66
  })
67
67
  }
68
- if (addApprouter) {
68
+ if (hasApprouter) {
69
69
  relationships.push({
70
70
  insert: [cloudLogging, 'name'],
71
71
  into: [approuter, 'requires', 'name']
@@ -247,6 +247,9 @@ function dataForField(element, artifact, csn, record, data, parent, depth, refer
247
247
  const range = { from: 0, to: prec }
248
248
  annotatedIntRange(element, range)
249
249
  if (scale === 0) return randomIntInRange(range)
250
+ const factor = Math.pow(10, scale)
251
+ range.from = Math.ceil(range.from * factor) / factor
252
+ range.to = Math.floor(range.to * factor) / factor
250
253
  // draw uniformly across the range and round to scale, so annotated bounds are never exceeded
251
254
  const val = +randomDecimalInRange(range).toFixed(scale)
252
255
  return +Math.min(Math.max(val, range.from), range.to).toFixed(scale)
@@ -25,7 +25,7 @@ module.exports = class DestinationTemplate extends require('../../plugin') {
25
25
 
26
26
  async combine(facet) {
27
27
  const project = readProject(facet)
28
- const { addApprouter, addMultitenancy, addKyma, addMta, hasMta, srv, isJava } = project
28
+ const { addApprouter, hasApprouter, addMultitenancy, hasMultitenancy, addKyma, addMta, hasMta, srv, isJava } = project
29
29
 
30
30
  if (addMta || (addApprouter || addMultitenancy) && hasMta) {
31
31
  const srvModule = srv4(srv.path)
@@ -34,13 +34,13 @@ module.exports = class DestinationTemplate extends require('../../plugin') {
34
34
  insert: [destination, 'name'],
35
35
  into: [srvModule, 'requires', 'name']
36
36
  }]
37
- if (addApprouter) {
37
+ if (hasApprouter) {
38
38
  relationships.push({
39
39
  insert: [destination, 'name'],
40
40
  into: [approuter, 'requires', 'name']
41
41
  })
42
42
  }
43
- if (addMultitenancy) {
43
+ if (hasMultitenancy) {
44
44
  relationships.push({
45
45
  insert: [destination, 'name'],
46
46
  into: [mtxSidecar4(isJava ? 'mtx/sidecar' : 'gen/mtx/sidecar'), 'requires', 'name']
@@ -6,6 +6,9 @@ const { srv4, enterpriseMessaging, xsuaa } = require('../../registries/mta')
6
6
  const KymaTemplate = require('../kyma')
7
7
 
8
8
  module.exports = class EnterpriseMessagingTemplate extends require('../../plugin') {
9
+ static get kind() { return 'enterprise-messaging-http' }
10
+ static get vcap() { return 'enterprise-messaging' }
11
+
9
12
  constructor(...args) {
10
13
  super(...args)
11
14
  this._name = 'enterprise-messaging'
@@ -37,7 +40,7 @@ module.exports = class EnterpriseMessagingTemplate extends require('../../plugin
37
40
 
38
41
  static hasInProduction(env) {
39
42
  const { kind = 'dummy', vcap } = env.requires?.messaging ?? {}
40
- return kind === this._kind && vcap?.label === this._vcap
43
+ return kind === this.kind && vcap?.label === this.vcap
41
44
  }
42
45
 
43
46
  async run() {
@@ -1,6 +1,9 @@
1
1
  const { eventMesh } = require('../../registries/mta')
2
2
 
3
3
  module.exports = class EventMeshTemplate extends require('../enterprise-messaging') {
4
+ static get kind() { return 'enterprise-messaging-http' }
5
+ static get vcap() { return 'event-mesh-message-client' }
6
+
4
7
  constructor(...args) {
5
8
  super(...args)
6
9
  this._name = 'event-mesh'
@@ -44,11 +44,11 @@ module.exports = class MultitenancyTemplate extends require('../../plugin') {
44
44
  const project = readProject(facet)
45
45
  const {
46
46
  isNodejs, isJava, addMta, hasMta,
47
- addXsuaa, hasXsuaa, addIas, hasIas, hasHana, hasHanaServerless, addKyma, hasApprouter,
47
+ addXsuaa, hasXsuaa, addIas, hasIas, addMultitenancy, hasHana, hasHanaServerless, addKyma, hasApprouter,
48
48
  srv, appName
49
49
  } = project
50
50
 
51
- if (addMta || (addIas || addXsuaa) && hasMta) {
51
+ if (addMta || (addMultitenancy || addIas || addXsuaa) && hasMta) {
52
52
  const srvModule = srv4(srv.path)
53
53
  const mtxSidecar = mtxSidecar4(isJava ? 'mtx/sidecar' : 'gen/mtx/sidecar')
54
54
  const modules = [srvModule, mtxSidecar]
@@ -93,7 +93,7 @@ module.exports = class extends require('../../plugin') {
93
93
  }
94
94
  if (addXsuaa) {
95
95
  relationships.push({
96
- insert: [xsuaa],
96
+ insert: [xsuaa, 'name'],
97
97
  into: [destinations, 'requires']
98
98
  })
99
99
  additions.push({
@@ -4,6 +4,36 @@
4
4
  - The format is based on [Keep a Changelog](http://keepachangelog.com/).
5
5
  - This project adheres to [Semantic Versioning](http://semver.org/).
6
6
 
7
+ ## [3.1.1](https://github.com/cap-js/cds-dbs/compare/db-service-v3.1.0...db-service-v3.1.1) (2026-09-15)
8
+
9
+
10
+ ### Fixed
11
+
12
+ * **cqn4sql:** correctly expand `not exists <assoc>` alongside a null-check branch ([#1736](https://github.com/cap-js/cds-dbs/issues/1736)) ([bfb0a01](https://github.com/cap-js/cds-dbs/commit/bfb0a010cce2cea089f4fabf92a3ecea43486a09))
13
+
14
+
15
+ ### Performance Improvements
16
+
17
+ * do not use ranked search for static values ([#1727](https://github.com/cap-js/cds-dbs/issues/1727)) ([33fb36b](https://github.com/cap-js/cds-dbs/commit/33fb36bbf0f86291e5fc0b9e2ff5b7244b49d19e))
18
+ * if explicit order by is specified, do not add ranked search as a secondary clause ([33fb36b](https://github.com/cap-js/cds-dbs/commit/33fb36bbf0f86291e5fc0b9e2ff5b7244b49d19e))
19
+
20
+ ## [3.1.0](https://github.com/cap-js/cds-dbs/compare/db-service-v3.0.1...db-service-v3.1.0) (2026-09-07)
21
+
22
+
23
+ ### Added
24
+
25
+ * rank $search results by relevance ([#1725](https://github.com/cap-js/cds-dbs/issues/1725)) ([bc93160](https://github.com/cap-js/cds-dbs/commit/bc931601a721f7c9e00b89e6844ba064352b541d))
26
+
27
+
28
+ ### Fixed
29
+
30
+ * **cqn4sql:** reject overly nested query expressions instead of crashing ([#1712](https://github.com/cap-js/cds-dbs/issues/1712)) ([29a45b7](https://github.com/cap-js/cds-dbs/commit/29a45b7daadbc77433b882148700d73c00efae62))
31
+ * don't throw when releasing/destroying a resource that's no longer in the pool ([#1696](https://github.com/cap-js/cds-dbs/issues/1696)) ([5d3b277](https://github.com/cap-js/cds-dbs/commit/5d3b277a1cf7cac02e41f2f24975a8e9473e5b5e))
32
+ * exclude cds.Vector from wildcard select expansion ([#1716](https://github.com/cap-js/cds-dbs/issues/1716)) ([398bacc](https://github.com/cap-js/cds-dbs/commit/398bacc2a74883dcd93acba288deae362570a4c1))
33
+ * INSERT.from(SELECT) used non-existent `value` ([#1565](https://github.com/cap-js/cds-dbs/issues/1565)) ([fd98844](https://github.com/cap-js/cds-dbs/commit/fd98844c83a78d6cf2b0b71344d3ee1d88528106))
34
+ * **pool:** recover lost connection after stale acquire timeout ([#1724](https://github.com/cap-js/cds-dbs/issues/1724)) ([cf6af99](https://github.com/cap-js/cds-dbs/commit/cf6af991d02be7cf5f861e2a424d4d733b8f9238))
35
+ * pql groupby path expression ([#1700](https://github.com/cap-js/cds-dbs/issues/1700)) ([82b5e03](https://github.com/cap-js/cds-dbs/commit/82b5e0385e680321f30c352f0f78ade6d25259a6))
36
+
7
37
  ## [3.0.1](https://github.com/cap-js/cds-dbs/compare/db-service-v3.0.0...db-service-v3.0.1) (2026-06-29)
8
38
 
9
39
 
@@ -150,7 +150,7 @@ constructor (factory, options = {}) {
150
150
  return
151
151
  }
152
152
  const loan = this._loans.get(resource)
153
- if (!loan) throw new Error('Resource not currently part of this pool')
153
+ if (!loan) return
154
154
  this._loans.delete(resource)
155
155
  const pooledResource = loan.pooledResource
156
156
  pooledResource.idle()
@@ -164,7 +164,7 @@ constructor (factory, options = {}) {
164
164
  return
165
165
  }
166
166
  const loan = this._loans.get(resource)
167
- if (!loan) throw new Error('Resource not currently part of this pool')
167
+ if (!loan) return
168
168
  this._loans.delete(resource)
169
169
  const pooledResource = loan.pooledResource
170
170
  await this.#destroy(pooledResource)
@@ -212,16 +212,13 @@ constructor (factory, options = {}) {
212
212
  for (let i = 0; i < needed; i++) this.#createResource()
213
213
  }
214
214
  const dispense = async resource => {
215
- const request = this._queue.shift()
215
+ let request = this._queue.shift()
216
+ while (request && request.state !== RequestState.PENDING) request = this._queue.shift()
216
217
  if (!request) {
217
218
  resource.idle()
218
219
  this._available.add(resource)
219
220
  return false
220
221
  }
221
- if (request.state !== RequestState.PENDING) {
222
- this.#dispense()
223
- return false
224
- }
225
222
  this._loans.set(resource.obj, { pooledResource: resource })
226
223
  resource.update(ResourceState.ALLOCATED)
227
224
  request.resolve(resource.obj)
@@ -18,7 +18,7 @@ class CQN2PQLRenderer extends CQN2SQL {
18
18
 
19
19
  column_expr(x, q) {
20
20
  // omit alias when target is a single source
21
- if (q.SELECT.from.ref && x?.ref) x.ref = x.ref.slice(-1)
21
+ if (q.SELECT.from?.ref && x?.ref) x.ref = x.ref.slice(-1)
22
22
  return super.column_expr(x, q)
23
23
  }
24
24
 
@@ -1088,7 +1088,7 @@ class CQN2SQLRenderer {
1088
1088
 
1089
1089
  const extractions = this._managed = this.managed(columns.map(c => ({ name: c, sql: `NEW.${this.quote(c)}` })), elements)
1090
1090
  const sql = extractions.length > columns.length
1091
- ? `SELECT ${extractions.map(c => `${c.insert} AS ${this.quote(c.name)}`)} FROM (${this.SELECT(src)}) AS NEW`
1091
+ ? `SELECT ${extractions.map((c, i) => `${i < columns.length ? c.insert : c.onInsert} AS ${this.quote(c.name)}`)} FROM (${this.SELECT(src)}) AS NEW`
1092
1092
  : this.SELECT(src)
1093
1093
  if (extractions.length > columns.length) columns = this.columns = extractions.map(c => c.name)
1094
1094
  this.sql = `INSERT INTO ${this.quote(entity)}${alias ? ' as ' + this.quote(alias) : ''} (${columns.map(c => this.quote(transitions.mapping.get(c)?.ref?.[0] || c))}) ${sql}`
@@ -1161,12 +1161,12 @@ class CQN2SQLRenderer {
1161
1161
  const extractions = this._managed
1162
1162
  if (this.values) this.values = [] // Clear previously computed values
1163
1163
  const src = this.cqn4sql(UPSERT.from || UPSERT.as)
1164
- const aliasedQuery = cds.ql.SELECT
1165
- .columns(src.SELECT.columns
1166
- .map((c, i) => ({ ref: [this.column_name(c)], as: this.columns[i] }))
1167
- )
1168
- .from(src)
1169
- sql = `SELECT ${extractions.map(c => `${c.upsert}`)} FROM (${this.SELECT(aliasedQuery)}) AS NEW LEFT JOIN ${this.quote(entity)} AS OLD ON ${keyCompare}`
1164
+ const aliasedQuery = `SELECT ${[
1165
+ ...src.SELECT.columns.map((c, i) => this.column_expr({ ref: [this.column_name(c)], as: this.columns[i] })),
1166
+ ...extractions.slice(src.SELECT.columns.length).map(c => `${elements[c.name].key ? c.onInsert : 'NULL'} AS ${this.quote(c.name)}`), // fill in missing default values
1167
+ ]} FROM (${this.SELECT(src)})`
1168
+
1169
+ sql = `SELECT ${extractions.map(c => elements[c.name].key ? `NEW.${this.quote(c.name)}` : c.upsert)} FROM(${aliasedQuery}) AS NEW LEFT JOIN ${this.quote(entity)} AS OLD ON ${keyCompare} `
1170
1170
  if (extractions.length > columns.length) columns = this.columns = extractions.map(c => c.name)
1171
1171
  this.entries = [this.values]
1172
1172
  }
@@ -1574,9 +1574,11 @@ class CQN2SQLRenderer {
1574
1574
 
1575
1575
  managed_extract(name, element, converter) {
1576
1576
  const { UPSERT, INSERT } = this.cqn
1577
- const extract = !(INSERT?.entries || UPSERT?.entries) && (INSERT?.rows || UPSERT?.rows)
1578
- ? `value->>${this.string(`$[${this.columns.indexOf(name)}]`)}`
1579
- : `value->>${this.string(`$.${JSON.stringify(name)}`)}`
1577
+ const extract = (INSERT?.entries || UPSERT?.entries)
1578
+ ? `value->>${this.string(`$.${JSON.stringify(name)}`)}`
1579
+ : (INSERT?.rows || UPSERT?.rows)
1580
+ ? `value->>${this.string(`$[${this.columns.indexOf(name)}]`)}`
1581
+ : `NEW.${this.quote(name)}` // in case of (INSERT?.from || UPSERT?.from)
1580
1582
  const sql = converter?.(extract) || extract
1581
1583
  return { extract, sql }
1582
1584
  }
@@ -1587,7 +1589,9 @@ class CQN2SQLRenderer {
1587
1589
  }
1588
1590
 
1589
1591
  managed_default(name, managed, src) {
1590
- return `(CASE WHEN json_type(value,${this.managed_extract(name).extract.slice(8)}) IS NULL THEN ${managed} ELSE ${src} END)`
1592
+ const { UPSERT, INSERT } = this.cqn
1593
+ const isJson = INSERT?.entries || UPSERT?.entries || INSERT?.rows || UPSERT?.rows
1594
+ return `(CASE WHEN ${isJson ? `json_type(value,${this.managed_extract(name).extract.slice(8)})` : `NEW.${this.quote(name)}`} IS NULL THEN ${managed} ELSE ${src} END)`
1591
1595
  }
1592
1596
  }
1593
1597