@sap/cds-dk 10.0.4 → 10.0.5
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 +22 -0
- package/bin/debug.js +6 -2
- package/bin/up.js +1 -1
- package/bin/watch.js +1 -0
- package/lib/build/plugins/helm/files/templates/_deployment_helpers.tpl +2 -0
- package/lib/build/plugins/nodejs/index.js +6 -3
- package/lib/build/util.js +1 -1
- package/lib/init/template/ams/files/containerize.yaml.hbs +1 -0
- package/lib/init/template/approuter/files/values.yaml.hbs +9 -1
- package/lib/init/template/data/as-csv.js +4 -2
- package/lib/init/template/data/as-json.js +4 -1
- package/lib/init/template/github-actions/files/workflows/cf.yaml.hbs +6 -6
- package/lib/init/template/ias/files/values.yaml.hbs +4 -4
- package/lib/init/template/portal/files/values.yaml.hbs +12 -0
- package/lib/init/template/sample/files/app/browse/fiori-service.cds +2 -0
- package/lib/init/template/typer/files/xsconfig.json +1 -1
- package/lib/init/template/typescript/files/package.json +1 -1
- package/lib/lint/eslintHelp.json +1 -1
- package/lib/watch/esm-resolve-registration.js +27 -0
- package/lib/watch/vite-watch.js +1 -1
- package/node_modules/@sap/cds-compiler/CHANGELOG.md +14 -0
- package/node_modules/@sap/cds-compiler/bin/cdsse.js +1 -1
- package/node_modules/@sap/cds-compiler/bin/cdsv2m.js +1 -1
- package/node_modules/@sap/cds-compiler/doc/IncompatibleChanges_v7.md +20 -13
- package/node_modules/@sap/cds-compiler/lib/base/builtins.js +4 -1
- package/node_modules/@sap/cds-compiler/lib/base/message-registry.js +15 -2
- package/node_modules/@sap/cds-compiler/lib/checks/defaultValues.js +0 -1
- package/node_modules/@sap/cds-compiler/lib/compiler/resolve.js +6 -5
- package/node_modules/@sap/cds-compiler/lib/compiler/tweak-assocs.js +27 -6
- package/node_modules/@sap/cds-compiler/lib/edm/annotations/edmJson.js +5 -0
- package/node_modules/@sap/cds-compiler/lib/json/from-csn.js +3 -3
- package/node_modules/@sap/cds-compiler/lib/json/to-csn.js +5 -4
- package/node_modules/@sap/cds-compiler/lib/parsers/AstBuildingParser.js +14 -3
- package/node_modules/@sap/cds-compiler/lib/tool-lib/enrichCsn.js +1 -1
- package/node_modules/@sap/cds-compiler/lib/transform/db/cdsPersistence.js +3 -2
- package/node_modules/@sap/cds-compiler/lib/transform/db/views.js +2 -2
- package/node_modules/@sap/cds-compiler/lib/transform/transformUtils.js +1 -2
- package/node_modules/@sap/cds-compiler/package.json +1 -1
- package/node_modules/ws/lib/receiver.js +15 -32
- package/node_modules/ws/lib/websocket-server.js +4 -4
- package/node_modules/ws/lib/websocket.js +4 -4
- package/node_modules/ws/package.json +5 -1
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -6,6 +6,28 @@ 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 10.0.5 - 2026-07-16
|
|
10
|
+
|
|
11
|
+
### Fixed
|
|
12
|
+
|
|
13
|
+
- `cds add typer` now generates a `jsconfig.json` file that works with VS Code.
|
|
14
|
+
- `cds debug <app> --force` on Kyma now preserves existing `NODE_OPTIONS` flags (e.g. `--max-old-space-size`) instead of overwriting them when enabling the inspector.
|
|
15
|
+
- `cds add sample` now selects `stock` in the Books list and object pages, so the overstock discount shows up in the UI.
|
|
16
|
+
- `cds add data` now generates correct foreign-key columns for nested compositions of aspects, e.g. `up__up__ID,up__ID,ID,name` instead of a broken `up__up_` header.
|
|
17
|
+
- `cds build` no longer fails with `npm ls -ws: unknown option` on npm 12, which dropped the short `-ws` flag.
|
|
18
|
+
- `cds build` now packages transitive npm workspace dependencies again on npm 12, which changed `npm pack --json` to emit an object keyed by package name instead of an array.
|
|
19
|
+
- `cds up` uses `helm --rollback-on-failure` instead of the deprecated `--atomic`.
|
|
20
|
+
- `cds add portal` on Java + IAS disables JWT proof-of-possession on `srv` (`SAP_SPRING_SECURITY_IDENTITY_PROOFTOKEN=false`) so Portal-proxied tile OData works.
|
|
21
|
+
- `cds add ias` forwards client certificates from the approuter to `srv-api` (`backendDestinations.srv-api.forwardAuthCertificates`).
|
|
22
|
+
- `cds add ias` always enables `xsuaa-cross-consumption` in the identity service parameters.
|
|
23
|
+
- `cds add ias` no longer sets an mTLS `expose.host` on the approuter, restoring a plain approuter host for the browser flow.
|
|
24
|
+
- `cds add portal` on Java multitenant now binds the Portal service instance to `srv` so CAP Java's `getDependencies` reports Portal to the SaaS Registry.
|
|
25
|
+
- `cds add kyma` uses a tenant-agnostic (tcpSocket) startup probe for the multitenant approuter, avoiding 400s from `TENANT_HOST_PATTERN` on kubelet probes.
|
|
26
|
+
- `cds add kyma` no longer emits the dead `Authentication: OAuth2UserTokenExchange` key on `backendDestinations.srv-api`.
|
|
27
|
+
- `cds add github-actions` uses updated versions.
|
|
28
|
+
- `cds watch` now works in ESM scenarios where only a global `cds-dk` is installed, where it would formerly fail with `ERR_MODULE_NOT_FOUND` for `@sap/cds`.
|
|
29
|
+
- `cds watch`no longer shows a `'server.hmr.protocol/host/port/path/clientPort/timeout/server' is deprecated` warning when running a Vite application.
|
|
30
|
+
|
|
9
31
|
## Version 10.0.4 - 2026-07-08
|
|
10
32
|
|
|
11
33
|
### Fixed
|
package/bin/debug.js
CHANGED
|
@@ -101,7 +101,11 @@ async function debug() {
|
|
|
101
101
|
|
|
102
102
|
if (force) {
|
|
103
103
|
console.log(`\nEnabling Node inspector on deployment/${appName} at 0.0.0.0:${portLocal} ...`)
|
|
104
|
-
_execSyncSafe(`kubectl ${ns}
|
|
104
|
+
const existingOpts = _execSyncSafe(`kubectl ${ns} get deploy ${appName} -o jsonpath="{.spec.template.spec.containers[0].env[?(@.name=='NODE_OPTIONS')].value}"`)
|
|
105
|
+
const inspectFlag = `--inspect=0.0.0.0:${portLocal}`
|
|
106
|
+
const stripped = existingOpts ? existingOpts.replace(/--inspect(-brk)?(=\S+)?\s*/g, '').trim() : ''
|
|
107
|
+
const newOpts = stripped ? `${stripped} ${inspectFlag}` : inspectFlag
|
|
108
|
+
_execSyncSafe(`kubectl ${ns} set env deployment/${appName} NODE_OPTIONS="${newOpts.replace(/"/g, '\\"')}"`, { stdio: 'inherit' })
|
|
105
109
|
_execSyncSafe(`kubectl ${ns} rollout restart deployment/${appName}`, { stdio: 'inherit' })
|
|
106
110
|
k(`rollout status deployment/${appName} --timeout=180s`, { stdio: 'inherit' })
|
|
107
111
|
} else {
|
|
@@ -281,7 +285,7 @@ async function debug() {
|
|
|
281
285
|
return _execSync(...args)
|
|
282
286
|
} catch { /* ignore */ }
|
|
283
287
|
}
|
|
284
|
-
function _execSync(cmd, options={ encoding: 'utf-8', shell: true }) {
|
|
288
|
+
function _execSync(cmd, options={ encoding: 'utf-8', shell: true, maxBuffer: 10 * 1024 * 1024 }) {
|
|
285
289
|
try {
|
|
286
290
|
return execSync(cmd, options)?.trim()
|
|
287
291
|
} catch (err) {
|
package/bin/up.js
CHANGED
|
@@ -346,7 +346,7 @@ async function up() {
|
|
|
346
346
|
}
|
|
347
347
|
})
|
|
348
348
|
|
|
349
|
-
let helmUpgrade = `helm upgrade --install ${appName} ./gen/chart ${nsHelm} --
|
|
349
|
+
let helmUpgrade = `helm upgrade --install ${appName} ./gen/chart ${nsHelm} --rollback-on-failure --wait --wait-for-jobs --timeout=20m`
|
|
350
350
|
if (hasXsuaa) helmArgs.push(' --set-file xsuaa.jsonParameters=xs-security.json')
|
|
351
351
|
if (helmArgs.length > 0) helmUpgrade += ` ${helmArgs.join(' ')}`
|
|
352
352
|
run(helmUpgrade)
|
package/bin/watch.js
CHANGED
|
@@ -148,6 +148,7 @@ async function watch ([cwd], {
|
|
|
148
148
|
// if sap/cds is not installed locally, run with cds-dk's deps. (sap/cds, sqlite etc)
|
|
149
149
|
if (!installed('@sap/cds', cwd)) {
|
|
150
150
|
env.NODE_PATH = [...paths(cwd), ...paths(__dirname)].join(delimiter)
|
|
151
|
+
env.NODE_OPTIONS = [env.NODE_OPTIONS, `--import ${require.resolve('../lib/watch/esm-resolve-registration.js')}`].filter(Boolean).join(' ')
|
|
151
152
|
}
|
|
152
153
|
|
|
153
154
|
const delay = parseInt(process.env.CDS_WATCH_DELAY) || 200
|
|
@@ -88,6 +88,8 @@ Backend Destinations
|
|
|
88
88
|
{{- end -}}
|
|
89
89
|
{{- $currentDestination := dict $nameKey $destination.name $urlKey (print $protocol "://" $destinationHost ) -}}
|
|
90
90
|
{{- $currentDestination := merge $currentDestination $.defaultParameters -}}
|
|
91
|
+
{{- $extras := omit $destination "name" "service" "external" -}}
|
|
92
|
+
{{- $currentDestination := merge $extras $currentDestination -}}
|
|
91
93
|
{{- $destinations = (append $destinations $currentDestination) -}}
|
|
92
94
|
{{- end -}}
|
|
93
95
|
{{- $destinations | toJson }}
|
|
@@ -296,14 +296,17 @@ module.exports = class NodejsBuildPlugin extends require('../edmx') {
|
|
|
296
296
|
* @param {import('fs').PathLike} dest - destination folder
|
|
297
297
|
* @param {import('fs').PathLike} wsRoot - workspace root folder
|
|
298
298
|
* @param {string[]} workspaces - list of workspace names
|
|
299
|
-
* @returns {Promise<
|
|
299
|
+
* @returns {Promise<object[]>} - array of pack descriptors ({ name, filename, ... })
|
|
300
300
|
* @private
|
|
301
301
|
*/
|
|
302
302
|
static async _execNpmPack(dest, wsRoot, workspaces) {
|
|
303
|
-
const args = ['pack', '
|
|
303
|
+
const args = ['pack', '--ws', '--json', `--pack-destination=${dest}`]
|
|
304
304
|
workspaces.forEach(w => args.push(`-w=${w}`))
|
|
305
305
|
const result = await cmd.spawnCommand('npm', args, { cwd: wsRoot }, true, !!LOG._debug)
|
|
306
306
|
DEBUG?.(result)
|
|
307
|
-
|
|
307
|
+
const parsed = JSON.parse(result)
|
|
308
|
+
// `npm pack --json` returns an array up to npm 11, but an object keyed by
|
|
309
|
+
// package name as of npm 12 — normalize to an array of descriptors.
|
|
310
|
+
return Array.isArray(parsed) ? parsed : Object.values(parsed)
|
|
308
311
|
}
|
|
309
312
|
}
|
package/lib/build/util.js
CHANGED
|
@@ -300,7 +300,7 @@ async function _execNpmWs(wsRoot) {
|
|
|
300
300
|
/** @type {string[]} */
|
|
301
301
|
let result = []
|
|
302
302
|
try {
|
|
303
|
-
const cmdLine = 'npm ls
|
|
303
|
+
const cmdLine = 'npm ls --ws --json'
|
|
304
304
|
DEBUG?.(`execute ${cmdLine}`)
|
|
305
305
|
// @ts-expect-error - TODO: shell should be a string. But can probably be removed altogether?
|
|
306
306
|
const { stdout } = await execAsync(cmdLine, { shell: IS_WIN, stdio: ['inherit', 'pipe', 'inherit'], cwd: wsRoot })
|
|
@@ -16,11 +16,20 @@ approuter:
|
|
|
16
16
|
ephemeral-storage: 1G
|
|
17
17
|
cpu: 500m
|
|
18
18
|
memory: 500M
|
|
19
|
+
{{^hasMultitenancy}}
|
|
19
20
|
health:
|
|
20
21
|
liveness:
|
|
21
22
|
path: /
|
|
22
23
|
readiness:
|
|
23
24
|
path: /
|
|
25
|
+
{{/hasMultitenancy}}
|
|
26
|
+
{{#hasMultitenancy}}
|
|
27
|
+
health:
|
|
28
|
+
liveness:
|
|
29
|
+
path: ""
|
|
30
|
+
readiness:
|
|
31
|
+
path: ""
|
|
32
|
+
{{/hasMultitenancy}}
|
|
24
33
|
{{=<% %>=}}
|
|
25
34
|
envFrom:
|
|
26
35
|
- configMapRef:
|
|
@@ -29,7 +38,6 @@ approuter:
|
|
|
29
38
|
backendDestinations:
|
|
30
39
|
srv-api:
|
|
31
40
|
service: srv
|
|
32
|
-
Authentication: OAuth2UserTokenExchange
|
|
33
41
|
{{#hasMultitenancy}}
|
|
34
42
|
mtx-api:
|
|
35
43
|
service: {{#isJava}}srv{{/isJava}}{{^isJava}}sidecar{{/isJava}}
|
|
@@ -37,10 +37,12 @@ module.exports = async (data={}, csn, { headerOnly=false, separator=',' }={}) =>
|
|
|
37
37
|
if (element.on) {
|
|
38
38
|
continue // skip unmanaged associations with explicit on condition
|
|
39
39
|
}
|
|
40
|
+
// flatten nested foreign keys (composite keys of nested aspects) into up__up__ID columns
|
|
41
|
+
const {headers: fkHeaders, records: fkRecords} = resolveStruct(value, name)
|
|
40
42
|
if (i === 0) {
|
|
41
|
-
header.push(...
|
|
43
|
+
header.push(...fkHeaders)
|
|
42
44
|
}
|
|
43
|
-
record.push(...
|
|
45
|
+
record.push(...fkRecords)
|
|
44
46
|
}
|
|
45
47
|
else if (element.is_struct && element.type !== 'cds.Map') {
|
|
46
48
|
const {headers: structHeaders, records: structRecords} = resolveStruct(value, name);
|
|
@@ -257,7 +257,10 @@ function dataForField(element, artifact, csn, record, data, parent, depth, refer
|
|
|
257
257
|
|
|
258
258
|
const [ownKey, ownKeyStructured, targetKey] = findKeysInOnCondition(element, target)
|
|
259
259
|
if (ownKey && targetKey) {
|
|
260
|
-
|
|
260
|
+
// carry over all parent keys, not just the first, so composite/nested keys survive
|
|
261
|
+
const value = ownKeyStructured
|
|
262
|
+
? Object.fromEntries(Object.keys(artifact.keys).map(k => [k, record[k]]))
|
|
263
|
+
: record[ownKey]
|
|
261
264
|
targetRecords.forEach(targetRecord => {
|
|
262
265
|
// Set parent in target as non-enumerable. Json serialization doesn't need it, but csv does.
|
|
263
266
|
Object.defineProperty(targetRecord, targetKey, { value, enumerable: false })
|
|
@@ -44,13 +44,13 @@ jobs:
|
|
|
44
44
|
echo "${PWD}/apache-maven-${MAVEN_VERSION}/bin" >> "$GITHUB_PATH"
|
|
45
45
|
{{/sap}}
|
|
46
46
|
{{/isJava}}
|
|
47
|
-
- uses: cap-js/cf-setup@
|
|
47
|
+
- uses: cap-js/cf-setup@v2
|
|
48
48
|
with:
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
49
|
+
api: {{#literal}}${{ vars.CF_API }}{{/literal}}
|
|
50
|
+
org: {{#literal}}${{ vars.CF_ORG }}{{/literal}}
|
|
51
|
+
space: {{#literal}}${{ vars.CF_SPACE }}{{/literal}}
|
|
52
|
+
username: {{#literal}}${{ vars.CF_USERNAME }}{{/literal}}
|
|
53
|
+
password: {{#literal}}${{ secrets.CF_PASSWORD }}{{/literal}}
|
|
54
54
|
- run: npm install
|
|
55
55
|
- run: npx cds up
|
|
56
56
|
|
|
@@ -13,14 +13,16 @@ sidecar:
|
|
|
13
13
|
{{#hasApprouter}}
|
|
14
14
|
approuter:
|
|
15
15
|
expose:
|
|
16
|
-
|
|
17
|
-
host: {{#literal}}"{{ .Release.Name }}-approuter-{{ .Release.Namespace }}.cert"{{/literal}}
|
|
16
|
+
host: {{#literal}}"{{ .Release.Name }}-approuter-{{ .Release.Namespace }}"{{/literal}}
|
|
18
17
|
bindings:
|
|
19
18
|
auth:
|
|
20
19
|
serviceInstanceName: identity
|
|
21
20
|
parameters:
|
|
22
21
|
credential-type: X509_GENERATED
|
|
23
22
|
app-identifier: approuter
|
|
23
|
+
backendDestinations:
|
|
24
|
+
srv-api:
|
|
25
|
+
forwardAuthCertificates: true
|
|
24
26
|
{{/hasApprouter}}
|
|
25
27
|
srv:
|
|
26
28
|
expose:
|
|
@@ -41,9 +43,7 @@ identity:
|
|
|
41
43
|
{{#hasMultitenancy}}
|
|
42
44
|
multi-tenant: true
|
|
43
45
|
{{/hasMultitenancy}}
|
|
44
|
-
{{#hasXsuaa}}
|
|
45
46
|
xsuaa-cross-consumption: true
|
|
46
|
-
{{/hasXsuaa}}
|
|
47
47
|
provided-apis:
|
|
48
48
|
- name: {{appName}}-ias-api
|
|
49
49
|
description: API exposed by the application
|
|
@@ -6,9 +6,21 @@ approuter:
|
|
|
6
6
|
portal:
|
|
7
7
|
serviceOfferingName: portal
|
|
8
8
|
servicePlanName: standard
|
|
9
|
+
{{#isJava}}{{#hasIas}}
|
|
10
|
+
srv:
|
|
11
|
+
env:
|
|
12
|
+
# Portal Service proxies tile OData without forwarding the client cert.
|
|
13
|
+
SAP_SPRING_SECURITY_IDENTITY_PROOFTOKEN: "false"
|
|
14
|
+
{{/hasIas}}{{/isJava}}
|
|
9
15
|
{{#hasMultitenancy}}
|
|
10
16
|
sidecar:
|
|
11
17
|
bindings:
|
|
12
18
|
portal:
|
|
13
19
|
serviceInstanceName: portal
|
|
20
|
+
{{#isJava}}
|
|
21
|
+
srv:
|
|
22
|
+
bindings:
|
|
23
|
+
portal:
|
|
24
|
+
serviceInstanceName: portal
|
|
25
|
+
{{/isJava}}
|
|
14
26
|
{{/hasMultitenancy}}
|
|
@@ -27,6 +27,7 @@ annotate CatalogService.Books with @(UI : {
|
|
|
27
27
|
Value : currency.symbol,
|
|
28
28
|
Label : '{i18n>Currency}'
|
|
29
29
|
},
|
|
30
|
+
{Value : stock},
|
|
30
31
|
]},
|
|
31
32
|
});
|
|
32
33
|
|
|
@@ -53,5 +54,6 @@ annotate CatalogService.Books with @(UI : {
|
|
|
53
54
|
{Value : genre.name},
|
|
54
55
|
{Value : price},
|
|
55
56
|
{Value : currency.symbol},
|
|
57
|
+
{Value : stock},
|
|
56
58
|
]
|
|
57
59
|
});
|
package/lib/lint/eslintHelp.json
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
{
|
|
2
|
-
"version": "10.
|
|
2
|
+
"version": "10.7.0",
|
|
3
3
|
"help": "\n# SYNOPSIS\n\n *cds lint* [options] file.js [file.js] [dir]\n\n Runs environment checks and/or checks the specified models\n based on the ESLint framework\n\n# OPTIONS\n \n Basic configuration:\n --no-config-lookup Disable look up for eslint.config.js\n -c, --config path::String Use this configuration instead of eslint.config.js, eslint.config.mjs, or eslint.config.cjs\n --inspect-config Open the config inspector with the current configuration\n --ext [String] Specify additional file extensions to lint\n --global [String] Define global variables\n --parser String Specify the parser to be used\n --parser-options Object Specify parser options\n \n Specify Rules and Plugins:\n --plugin [String] Specify plugins\n --rule Object Specify rules\n \n Fix Problems:\n --fix Automatically fix problems\n --fix-dry-run Automatically fix problems without saving the changes to the file system\n --fix-type Array Specify the types of fixes to apply (directive, problem, suggestion, layout)\n \n Ignore Files:\n --no-ignore Disable use of ignore files and patterns\n --ignore-pattern [String] Patterns of files to ignore\n \n Use stdin:\n --stdin Lint code provided on <STDIN> - default: false\n --stdin-filename String Specify filename to process STDIN as\n \n Handle Warnings:\n --quiet Report errors only - default: false\n --max-warnings Int Number of warnings to trigger nonzero exit code - default: -1\n \n Output:\n -o, --output-file path::String Specify file to write report to\n -f, --format String Use a specific output format - default: stylish\n --color, --no-color Force enabling/disabling of color\n \n Inline configuration comments:\n --no-inline-config Prevent comments from changing config or rules\n --report-unused-disable-directives Adds reported errors for unused eslint-disable and eslint-enable directives\n --report-unused-disable-directives-severity String Chooses severity level for reporting unused eslint-disable and eslint-enable directives - either: off, warn, error, 0, 1, or 2\n --report-unused-inline-configs String Adds reported errors for unused eslint inline config comments - either: off, warn, error, 0, 1, or 2\n \n Caching:\n --cache Only check changed files - default: false\n --cache-file path::String Path to the cache file. Deprecated: use --cache-location - default: .eslintcache\n --cache-location path::String Path to the cache file or directory\n --cache-strategy String Strategy to use for detecting changed files in the cache - either: metadata or content - default: metadata\n \n Suppressing Violations:\n --suppress-all Suppress all violations - default: false\n --suppress-rule [String] Suppress specific rules\n --suppressions-location path::String Specify the location of the suppressions file\n --prune-suppressions Prune unused suppressions - default: false\n --pass-on-unpruned-suppressions Ignore unused suppressions - default: false\n \n Miscellaneous:\n --init Run config initialization wizard - default: false\n --env-info Output execution environment information - default: false\n --no-error-on-unmatched-pattern Prevent errors when pattern is unmatched\n --exit-on-fatal-error Exit with exit code 2 in case of fatal error - default: false\n --no-warn-ignored Suppress warnings when the file list includes ignored files\n --pass-on-no-patterns Exit with exit code 0 in case no file patterns are passed\n --debug Output debugging information\n -h, --help Show help\n -v, --version Output the version number\n --print-config path::String Print the configuration for the given file\n --stats Add statistics to the lint report - default: false\n --flag [String] Enable a feature flag\n --mcp Start the ESLint MCP server\n --concurrency Int|String Number of linting threads, auto to choose automatically, off for no multithreading - default: off\n \n",
|
|
4
4
|
"options": [
|
|
5
5
|
"--config",
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
// we want to allow starting projects with having nothing but a global cds-dk installed in a watched context.
|
|
2
|
+
// In that case, we want to fall back to resolving internal packages from the global cds-dk's own node_modules.
|
|
3
|
+
// Enriching NODE_PATH can be used in CJS (require(...)), but not in ESM (import ... from and import(...)),
|
|
4
|
+
// which requires a custom loader instead.
|
|
5
|
+
// https://nodejs.org/api/esm.html#no-node_path
|
|
6
|
+
const { createRequire, registerHooks } = require('node:module')
|
|
7
|
+
const { pathToFileURL } = require('node:url')
|
|
8
|
+
|
|
9
|
+
const ourModules = new Set(['@sap/cds'])
|
|
10
|
+
|
|
11
|
+
const requireFromHere = createRequire(__filename)
|
|
12
|
+
const resolveFromHere = url => pathToFileURL(requireFromHere.resolve(url)).href
|
|
13
|
+
|
|
14
|
+
registerHooks({
|
|
15
|
+
resolve(specifier, context, nextResolve) {
|
|
16
|
+
try {
|
|
17
|
+
// use default Node resolver first.
|
|
18
|
+
// Note: registering more hooks is UB!
|
|
19
|
+
return nextResolve(specifier, context)
|
|
20
|
+
} catch (error) {
|
|
21
|
+
if (error.code === 'ERR_MODULE_NOT_FOUND' && ourModules.has(specifier)) {
|
|
22
|
+
return { url: resolveFromHere(specifier), shortCircuit: true }
|
|
23
|
+
}
|
|
24
|
+
throw error
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
})
|
package/lib/watch/vite-watch.js
CHANGED
|
@@ -79,7 +79,7 @@ cds.on('bootstrap', app => {
|
|
|
79
79
|
|
|
80
80
|
for (const viteApp of viteApps) {
|
|
81
81
|
const base = viteApp.root ? '/' : normalizeBase(viteApp.name)
|
|
82
|
-
const server = await createServer({ base, root: viteApp.path, server: { middlewareMode: true,
|
|
82
|
+
const server = await createServer({ base, root: viteApp.path, server: { middlewareMode: true, ws: { port: hmrPort++ } }})
|
|
83
83
|
viteServers.push({ base, root: viteApp.root, middlewares: server.middlewares })
|
|
84
84
|
}
|
|
85
85
|
|
|
@@ -15,6 +15,20 @@ might use a deprecated flag only for a limited period of time.
|
|
|
15
15
|
|
|
16
16
|
|
|
17
17
|
|
|
18
|
+
## Version 7.0.2 - 2026-07-14
|
|
19
|
+
|
|
20
|
+
### Bug Fixes
|
|
21
|
+
|
|
22
|
+
- **compiler:**
|
|
23
|
+
+ Avoid dump for a new association in a projection with backlink comparison while reporting a missing backlink target element.
|
|
24
|
+
+ Soften incompatibility concerning backlink comparisons: missing target elements which are referred to in the foreign keys or ON-condition of the backlink
|
|
25
|
+
association now only lead to a warning.
|
|
26
|
+
+ Soften incompatibility concerning structure values for annotations: a structure property like `list` won't be rejected; we avoid an ambiguity with an
|
|
27
|
+
_expression_ `(1, 2, 3)` by putting `'=': true` next to `list: …` in the CSN, like in compiler v6. A `xpr` expression is rendered without a sibling `=`,
|
|
28
|
+
though.
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
|
|
18
32
|
## Version 7.0.1 - 2026-06-18
|
|
19
33
|
|
|
20
34
|
### ⚠ BREAKING CHANGES
|
|
@@ -201,7 +201,7 @@ function offset( buf, alsoSuffix ) { // for line and column
|
|
|
201
201
|
return false;
|
|
202
202
|
}
|
|
203
203
|
let cursor = pos + column - 1;
|
|
204
|
-
// eslint-disable-next-line sonarjs/
|
|
204
|
+
// eslint-disable-next-line sonarjs/super-linear-regex
|
|
205
205
|
const prefix = /[a-z_0-9$]*$/i.exec( buf.substring( pos, cursor ) ).index + pos;
|
|
206
206
|
const col = column + prefix - cursor;
|
|
207
207
|
if (alsoSuffix)
|
|
@@ -39,7 +39,7 @@ function ria() {
|
|
|
39
39
|
// 'Choose via $(ANNO) one of $(SORTED_ARTS) as redirection target for $(TARGET) in … $(ART) otherwise'
|
|
40
40
|
// NOTE: regex match on message text not for productive code!
|
|
41
41
|
for (const msgObj of msgs) {
|
|
42
|
-
// eslint-disable-next-line sonarjs/
|
|
42
|
+
// eslint-disable-next-line sonarjs/super-linear-regex
|
|
43
43
|
const matches = msgObj.message.match( /["“][^"”]+["”]/ug );
|
|
44
44
|
matches.slice( 1, -2 ).forEach( (name) => {
|
|
45
45
|
annotates[name.slice( 1, -1 )] = true;
|
|
@@ -173,7 +173,7 @@ In cds-compiler v7, we make `name-deprecated-$self` a non-configurable error.
|
|
|
173
173
|
Before cds-compiler v7:
|
|
174
174
|
The CSN representation of an expression-like annotation value is an object with
|
|
175
175
|
properties `=` and one of `ref`, `xpr`, `val`, `func`, `SELECT`, `list`.
|
|
176
|
-
In `=` we capture the (space-normalized, comments removed) string representation of the expression,
|
|
176
|
+
In `=` we capture the (space-normalized, comments removed) string CDL representation of the expression,
|
|
177
177
|
the other one holds the parsed expression as a token stream.
|
|
178
178
|
|
|
179
179
|
When the annotation is propagated ...
|
|
@@ -222,25 +222,32 @@ Resulting CSN:
|
|
|
222
222
|
```
|
|
223
223
|
|
|
224
224
|
In cds-compiler v7:
|
|
225
|
-
* For anno-xpr that are a simple ref (simple: can be path, but no filter, no parameters)
|
|
226
|
-
|
|
225
|
+
* For anno-xpr that are a simple ref (simple: can be path, but no filter, no parameters)
|
|
226
|
+
in the compiler and runtime CSN:
|
|
227
|
+
- In `=`, render the `.`-concatenated `ref` items without CDL escaping
|
|
228
|
+
(like for unchecked references, but unlike compiler v6);
|
|
229
|
+
e.g. for a reference, written as `(![in])` in CDL, use `{ '=': 'in', ref: ['in'] }` in CSN.
|
|
227
230
|
- When rewriting is necessary, also adapt the `=`.
|
|
231
|
+
* For expression which are a simple enum symbols, with or without surrounding parentheses:
|
|
232
|
+
- Just render the `#`, omit the `=` property.
|
|
233
|
+
* For expressions which are represented in CSN by a simple `xpr`:
|
|
234
|
+
- Do not render a `=` property anymore, just render the `xpr`.
|
|
228
235
|
* For all other expressions:
|
|
229
|
-
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
236
|
+
- Use `=` property with value `true`, do not use the CDL source as value,
|
|
237
|
+
both in original and in propagated annotations.
|
|
238
|
+
|
|
239
|
+
With the removal of `=`, any object in a CSN with just a `xpr`
|
|
240
|
+
is regarded as an expression value. With that, a structure inside an array
|
|
241
|
+
with just a `xpr` property could not be represented without ambiguity.
|
|
242
|
+
|
|
243
|
+
Therefore, the parser in v7 reports an error for such invalid structure values:
|
|
237
244
|
```cds
|
|
238
245
|
@A_xpr: [(2*foo)]
|
|
239
246
|
@A_str: [{xpr: [{val: 2}, '*', {ref: ['foo']}]}]
|
|
240
247
|
entity E { foo : Integer; }
|
|
241
248
|
```
|
|
242
|
-
|
|
243
|
-
|
|
249
|
+
The compiler also reports an error for structures inside arrays containing a `=` property and
|
|
250
|
+
a primary expression property (`ref`, `xpr`, `list`, `val`, `#`, `func`, `SELECT` and `SET`).
|
|
244
251
|
|
|
245
252
|
### Default for structures
|
|
246
253
|
|
|
@@ -107,7 +107,10 @@ const exprProperties = [
|
|
|
107
107
|
* Return whether JSON object `val` is a representation for an annotation expression
|
|
108
108
|
*/
|
|
109
109
|
function isAnnotationExpression( val ) {
|
|
110
|
-
return val &&
|
|
110
|
+
return val && (val['=']
|
|
111
|
+
? primaryExprProperties.some( prop => val[prop] !== undefined )
|
|
112
|
+
: val.xpr !== undefined && Object.keys( val ).length === 1 || val['#'] !== undefined
|
|
113
|
+
);
|
|
111
114
|
}
|
|
112
115
|
|
|
113
116
|
module.exports = {
|
|
@@ -147,6 +147,8 @@ const centralMessages = {
|
|
|
147
147
|
'ref-invalid-source': { severity: 'Error' },
|
|
148
148
|
'ref-invalid-target': { severity: 'Error' },
|
|
149
149
|
'ref-invalid-backlink': { severity: 'Error', configurableFor: 'before_v8' },
|
|
150
|
+
'ref-missing-backlink-element': { severity: 'Warning', errorFor: [ 'v8' ] },
|
|
151
|
+
'ref-deprecated-backlink': { severity: 'Warning', errorFor: [ 'v8' ] },
|
|
150
152
|
'ref-missing-self-counterpart': { severity: 'Error', configurableFor: true },
|
|
151
153
|
'ref-sloppy-target': { severity: 'Error', configurableFor: 'deprecated_before_v8' },
|
|
152
154
|
|
|
@@ -546,8 +548,10 @@ const centralMessageTexts = {
|
|
|
546
548
|
colon: 'Use a $(NEWCODE), not a $(CODE) between the element names in a reference',
|
|
547
549
|
},
|
|
548
550
|
'syntax-invalid-anno-struct': {
|
|
549
|
-
std: '
|
|
550
|
-
|
|
551
|
+
std: 'Objects with properties $(PROP) and $(SIBLINGPROP) are reserved for expressions and can\'t be used as structures',
|
|
552
|
+
xpr: 'Objects with $(PROP) as only property are reserved for expressions and can\'t be used as structures',
|
|
553
|
+
'#': 'Objects with a property $(PROP) are reserved for enum values and can\'t be used as structures',
|
|
554
|
+
ref: 'The value of property $(PROP) in structures must be a non-empty string',
|
|
551
555
|
},
|
|
552
556
|
// 'syntax-ignoring-doc-comment' (Info)
|
|
553
557
|
'syntax-unexpected-reserved-word': '$(CODE) is a reserved word - write $(DELIMITED) instead if you want to use it as name',
|
|
@@ -917,6 +921,7 @@ const centralMessageTexts = {
|
|
|
917
921
|
'rewrite-undefined-key': {
|
|
918
922
|
std: 'Can\'t redirect association $(NAME), as redirection target $(TARGET) does not project element $(ID), which is required to match foreign key of $(NAME)',
|
|
919
923
|
backlink: 'Can\'t project association, as the current entity does not project element $(ID), which is required to match foreign key of the backlink association',
|
|
924
|
+
// TODO: delete text variant 'backlink' again
|
|
920
925
|
},
|
|
921
926
|
'type-unexpected-typeof': {
|
|
922
927
|
std: 'Unexpected $(KEYWORD) for the type reference here',
|
|
@@ -1180,6 +1185,14 @@ const centralMessageTexts = {
|
|
|
1180
1185
|
include: 'The open backlink target $(TARGET) of the aspect is unrelated to the current entity',
|
|
1181
1186
|
backlink: 'Can\'t use $(ART) as backlink association, because its ON-condition contains a backlink comparison',
|
|
1182
1187
|
},
|
|
1188
|
+
'ref-deprecated-backlink': {
|
|
1189
|
+
std: 'The target $(TARGET) of the backlink association is just distantly related to the current entity; do not use a comparison with $(ID)',
|
|
1190
|
+
inferred: 'The target $(TARGET) of the backlink association $(NAME) is just distantly related to the current entity; provide an ON-condition not using a comparison with $(ID)',
|
|
1191
|
+
},
|
|
1192
|
+
'ref-missing-backlink-element': {
|
|
1193
|
+
std: 'Element $(ID) must also be projected when projecting this association containing a backlink comparison',
|
|
1194
|
+
def: 'Element $(ID) must be projected when defining this association containing a backlink comparison',
|
|
1195
|
+
},
|
|
1183
1196
|
'extend-columns': {
|
|
1184
1197
|
std: 'Artifact $(ART) can\'t be extended with columns, only simple views/projections without JOINs and UNIONs can',
|
|
1185
1198
|
join: 'Artifact $(ART) can\'t be extended with columns, because it contains a JOIN',
|
|
@@ -23,7 +23,6 @@ function validateDefaultValues( member, memberName, prop, path ) {
|
|
|
23
23
|
// TODO: This check only counts the number of leading signs, not inbetween (e.g. 1 - - 1).
|
|
24
24
|
// The message also needs to be improved.
|
|
25
25
|
if (i > 1)
|
|
26
|
-
// eslint-disable-next-line cds-compiler/message-no-quotes
|
|
27
26
|
this.error(null, path, {}, 'Illegal number of unary \'+\'/\'-\' operators');
|
|
28
27
|
}
|
|
29
28
|
}
|
|
@@ -1229,7 +1229,8 @@ function resolve( model ) {
|
|
|
1229
1229
|
function checkBacklinkPartner( assoc ) {
|
|
1230
1230
|
checkBareDollarSelf( assoc );
|
|
1231
1231
|
// console.log('CBP:',assoc.kind,assoc._main?.name?.id,assoc.name.id,!!assoc.$backlink)
|
|
1232
|
-
const
|
|
1232
|
+
const origin = assoc.$backlink?._origin;
|
|
1233
|
+
const target = origin?.target?._artifact;
|
|
1233
1234
|
if (!target)
|
|
1234
1235
|
return;
|
|
1235
1236
|
const main = assoc._main;
|
|
@@ -1254,10 +1255,10 @@ function resolve( model ) {
|
|
|
1254
1255
|
// TODO: we should do the "simple projection ancestor" probably only if no
|
|
1255
1256
|
// include is involved in the redirection chain
|
|
1256
1257
|
if (redirectionChain( null, main, ancestor, true )) {
|
|
1257
|
-
|
|
1258
|
-
|
|
1259
|
-
// eslint-disable-next-line @stylistic/
|
|
1260
|
-
'
|
|
1258
|
+
const msg = (assoc.on.$inferred ? 'inferred' : 'std');
|
|
1259
|
+
message( 'ref-deprecated-backlink', [ assoc.$backlink.location, assoc ],
|
|
1260
|
+
// eslint-disable-next-line @stylistic/object-curly-newline
|
|
1261
|
+
{ '#': msg, target, id: '$self', name: origin.name.id } );
|
|
1261
1262
|
return;
|
|
1262
1263
|
}
|
|
1263
1264
|
}
|
|
@@ -450,13 +450,24 @@ function tweakAssocs( model ) {
|
|
|
450
450
|
if (projectedKey.index > 0)
|
|
451
451
|
targetElement.path.splice(0, projectedKey.index);
|
|
452
452
|
}
|
|
453
|
+
else if (elem._outer?.$backlink === elem) {
|
|
454
|
+
const undef = targetElement.path[0];
|
|
455
|
+
setArtifactLink( undef, null );
|
|
456
|
+
setArtifactLink( targetElement, null );
|
|
457
|
+
const outer = elem._outer;
|
|
458
|
+
message( 'ref-missing-backlink-element',
|
|
459
|
+
[ (outer.value || outer.name).location, outer ],
|
|
460
|
+
{ '#': (outer.value ? 'std' : 'def'), id: undef.id } );
|
|
461
|
+
// TODO: more info if element was already missing in "intermediate" projection
|
|
462
|
+
// TODO: collect all missing elements?
|
|
463
|
+
}
|
|
453
464
|
else {
|
|
454
465
|
setArtifactLink( targetElement.path[0], null );
|
|
455
466
|
setArtifactLink( targetElement, null );
|
|
456
467
|
|
|
457
468
|
const culprit = !elem.target.$inferred && elem.target ||
|
|
458
|
-
|
|
459
|
-
|
|
469
|
+
elem.value?.path?.[elem.value.path.length - 1] ||
|
|
470
|
+
elem;
|
|
460
471
|
// TODO: probably better to collect the non-projected foreign keys
|
|
461
472
|
// and have one message for all
|
|
462
473
|
error( 'rewrite-undefined-key', [ weakLocation( culprit.location ), elem ], {
|
|
@@ -825,8 +836,16 @@ function tweakAssocs( model ) {
|
|
|
825
836
|
if (!item)
|
|
826
837
|
return; // just $self
|
|
827
838
|
// console.log('REB:',assoc.name?.id,expr.path.map(i=>i.id))
|
|
828
|
-
if (item.id === assoc.name.id
|
|
829
|
-
|
|
839
|
+
if (item.id === assoc.name.id &&
|
|
840
|
+
rewritePath( expr, item, assoc, assoc, null )) {
|
|
841
|
+
const undef = expr.path.find( i => !i._artifact );
|
|
842
|
+
const outer = assoc._outer;
|
|
843
|
+
message( 'ref-missing-backlink-element',
|
|
844
|
+
[ (outer.value || outer.name).location, outer ],
|
|
845
|
+
{ '#': (outer.value ? 'std' : 'def'), id: undef.id } );
|
|
846
|
+
// TODO: more info if element was already missing in "intermediate" projection
|
|
847
|
+
// TODO: collect all missing elements?
|
|
848
|
+
}
|
|
830
849
|
}
|
|
831
850
|
else if (assoc._main.query) { // from ON cond of mixin element in query
|
|
832
851
|
// here also $calc
|
|
@@ -877,7 +896,7 @@ function tweakAssocs( model ) {
|
|
|
877
896
|
element: 'This element is not originally referred to in the ON-condition of association $(MEMBER) of $(ART)',
|
|
878
897
|
} );
|
|
879
898
|
}
|
|
880
|
-
rewritePath( expr, item, assoc, elem, null );
|
|
899
|
+
rewritePath( expr, item, assoc, elem, null ); // TODO: really no error?
|
|
881
900
|
}
|
|
882
901
|
}
|
|
883
902
|
|
|
@@ -1044,6 +1063,8 @@ function tweakAssocs( model ) {
|
|
|
1044
1063
|
* Rewrite the reference `ref` with first elem/mixin ref item `item` for user
|
|
1045
1064
|
* `assoc` (the query element). `elem` is usually the first (or preferred)
|
|
1046
1065
|
* query element for item.
|
|
1066
|
+
*
|
|
1067
|
+
* If `location` is falsy, do not issue an error.
|
|
1047
1068
|
*/
|
|
1048
1069
|
function rewritePath( ref, item, assoc, elem, location ) {
|
|
1049
1070
|
const { path } = ref;
|
|
@@ -1092,7 +1113,7 @@ function tweakAssocs( model ) {
|
|
|
1092
1113
|
if (state && state !== true)
|
|
1093
1114
|
continue;
|
|
1094
1115
|
if (!state && !location)
|
|
1095
|
-
return true;
|
|
1116
|
+
return true; // no rewrite possible
|
|
1096
1117
|
break;
|
|
1097
1118
|
}
|
|
1098
1119
|
}
|
|
@@ -978,6 +978,11 @@ function xpr2edmJson( carrier, anno, location, options, messageFunctions, generi
|
|
|
978
978
|
return xpr;
|
|
979
979
|
}
|
|
980
980
|
|
|
981
|
+
// "'=': true" has no meaning as an OData operator here and must be removed
|
|
982
|
+
// before transform runs, otherwise transform['='] misinterprets it as $Eq.
|
|
983
|
+
if (xpr['='] === true)
|
|
984
|
+
delete xpr['='];
|
|
985
|
+
|
|
981
986
|
if (xpr.list)
|
|
982
987
|
xpr.list.forEach( preTransformExpression );
|
|
983
988
|
|
|
@@ -117,7 +117,7 @@
|
|
|
117
117
|
|
|
118
118
|
const { dictAdd } = require('../compiler/dictionaries');
|
|
119
119
|
const { quotedLiteralPatterns } = require('../compiler/builtins');
|
|
120
|
-
const { exprProperties,
|
|
120
|
+
const { exprProperties, isAnnotationExpression } = require('../base/builtins');
|
|
121
121
|
const { CompilerAssertion } = require('../base/error');
|
|
122
122
|
const { Location } = require('../base/location');
|
|
123
123
|
const { XsnSource } = require('../compiler/xsn-model');
|
|
@@ -1490,10 +1490,10 @@ function annoValue( val, spec ) {
|
|
|
1490
1490
|
}
|
|
1491
1491
|
return retval;
|
|
1492
1492
|
}
|
|
1493
|
-
else if (
|
|
1493
|
+
else if (isAnnotationExpression( val )) {
|
|
1494
1494
|
const s = schema['@'].schema['-expr'];
|
|
1495
1495
|
const r = object( val, s );
|
|
1496
|
-
r.$tokenTexts = true;
|
|
1496
|
+
r.$tokenTexts = true;
|
|
1497
1497
|
return r;
|
|
1498
1498
|
}
|
|
1499
1499
|
else if (val['...'] !== undefined && Object.keys( val ).length === 1) {
|
|
@@ -1149,10 +1149,11 @@ function annoValue( node ) {
|
|
|
1149
1149
|
if (!node.$tokenTexts)
|
|
1150
1150
|
return value( node );
|
|
1151
1151
|
const csn = expression( node ); // expressions in annotation values
|
|
1152
|
-
|
|
1153
|
-
|
|
1154
|
-
|
|
1155
|
-
|
|
1152
|
+
if (!csn || csn['#'] || csn.xpr && Object.keys( csn ).length === 1)
|
|
1153
|
+
return csn; // '#' and 'xpr' without sibling '='
|
|
1154
|
+
const asTrue = !csn?.ref || gensrcFlavor || universalCsn ||
|
|
1155
|
+
csn.cast || csn.param || csn.ref.some( id => typeof id !== 'string' );
|
|
1156
|
+
return Object.assign({ '=': !!asTrue || csn.ref.join( '.' ) }, csn );
|
|
1156
1157
|
}
|
|
1157
1158
|
|
|
1158
1159
|
function value( node ) {
|
|
@@ -1190,15 +1190,26 @@ class AstBuildingParser extends BaseParser {
|
|
|
1190
1190
|
}
|
|
1191
1191
|
|
|
1192
1192
|
checkStructProps( dict ) {
|
|
1193
|
+
// see also isAnnotationExpression()
|
|
1194
|
+
if (!dict['=']) {
|
|
1195
|
+
const prop = dict['#'] && '#' ||
|
|
1196
|
+
dict.xpr && Object.keys( dict ).length === 1 && 'xpr';
|
|
1197
|
+
if (!prop)
|
|
1198
|
+
return;
|
|
1199
|
+
this.error( 'syntax-invalid-anno-struct', dict[prop].name.location,
|
|
1200
|
+
{ '#': prop, prop } );
|
|
1201
|
+
return;
|
|
1202
|
+
}
|
|
1193
1203
|
const prop = primaryExprProperties.find( p => dict[p] !== undefined );
|
|
1194
1204
|
if (prop) {
|
|
1195
1205
|
this.error( 'syntax-invalid-anno-struct', dict[prop].name.location,
|
|
1196
|
-
{ '#': 'std', prop } );
|
|
1206
|
+
{ '#': 'std', prop, siblingprop: '=' } );
|
|
1197
1207
|
}
|
|
1198
|
-
else if (dict['=']
|
|
1208
|
+
else if (dict['='].literal !== 'string' || !dict['='].val) {
|
|
1199
1209
|
this.error( 'syntax-invalid-anno-struct', dict['='].location,
|
|
1200
1210
|
{ '#': 'ref', prop: '=' } );
|
|
1201
1211
|
}
|
|
1212
|
+
// TODO: also check '...' ?
|
|
1202
1213
|
}
|
|
1203
1214
|
|
|
1204
1215
|
// TODO: remove the check from the parser; move it to shared.js
|
|
@@ -1635,7 +1646,7 @@ function addOneForDefinition( count, ext ) {
|
|
|
1635
1646
|
|
|
1636
1647
|
// Significant digits (before exponent) without leading and trailing zeros
|
|
1637
1648
|
function relevantDigits( val ) {
|
|
1638
|
-
// eslint-disable-next-line sonarjs/
|
|
1649
|
+
// eslint-disable-next-line sonarjs/super-linear-regex
|
|
1639
1650
|
val = val.replace( /e.+$/i, '' ); // this regex has no newlines -> is not slow
|
|
1640
1651
|
|
|
1641
1652
|
const init = /^[-+0.]+/g; // global flag to have lastIndex
|
|
@@ -143,7 +143,7 @@ function enrichCsn( csn, options = {} ) {
|
|
|
143
143
|
}
|
|
144
144
|
else {
|
|
145
145
|
const record = !isAnnotationExpression( obj ) && assignment;
|
|
146
|
-
// is record without primary expression property
|
|
146
|
+
// is record without primary expression property and usually '='
|
|
147
147
|
for (const name of Object.getOwnPropertyNames( obj ) ) {
|
|
148
148
|
const trans = record || transformers[name] || transformers[name.charAt(0)] || standard;
|
|
149
149
|
trans( obj, name, obj[name] );
|
|
@@ -51,11 +51,12 @@ function getAssocToSkippedIgnorer( csn, options, messageFunctions, csnUtils ) {
|
|
|
51
51
|
*
|
|
52
52
|
* @param {CSN.Artifact} artifact
|
|
53
53
|
* @param {string} artifactName
|
|
54
|
+
* @param {string} _prop
|
|
55
|
+
* @param {CSN.Path} _path
|
|
54
56
|
*/
|
|
55
57
|
function ignoreAssociationToSkippedTarget( artifact, artifactName, _prop, _path ) {
|
|
56
|
-
if (isPersistedOnDatabase(artifact))
|
|
58
|
+
if (isPersistedOnDatabase(artifact))
|
|
57
59
|
forEachMemberRecursively(artifact, ignore, [ 'definitions', artifactName ]);
|
|
58
|
-
}
|
|
59
60
|
}
|
|
60
61
|
|
|
61
62
|
/**
|
|
@@ -130,6 +130,7 @@ function getViewTransformer( csn, options, _messageFunctions ) {
|
|
|
130
130
|
* @param {CSN.Element} elem
|
|
131
131
|
* @param {string} elemName
|
|
132
132
|
* @param {CSN.Path} elementsPath Path pointing to elements
|
|
133
|
+
* @param {CSN.Path} _queryPath
|
|
133
134
|
*/
|
|
134
135
|
function handleAssociationElement( query, elements, columnMap, publishedMixins, elem, elemName, elementsPath, _queryPath ) {
|
|
135
136
|
const isNotMixinByItself = checkIsNotMixinByItself(query, columnMap, elemName);
|
|
@@ -391,9 +392,8 @@ function getColumnMap( query, csnUtils ) {
|
|
|
391
392
|
function ensureColumnNames( query, csnUtils, _options ) {
|
|
392
393
|
const select = query.SELECT || query.projection;
|
|
393
394
|
for (const col of select?.columns || []) {
|
|
394
|
-
if (col !== '*' && !columnAlias(col))
|
|
395
|
+
if (col !== '*' && !columnAlias(col))
|
|
395
396
|
setProp(col, 'as', csnUtils.getColumnName(col));
|
|
396
|
-
}
|
|
397
397
|
}
|
|
398
398
|
}
|
|
399
399
|
|
|
@@ -218,9 +218,8 @@ function getTransformers(model, options, msgFunctions, pathDelimiter = '_') {
|
|
|
218
218
|
if (flattenedNames.length === 1) {
|
|
219
219
|
// Single element: propagate default if leaf doesn't have one
|
|
220
220
|
const singleElem = result[flattenedNames[0]];
|
|
221
|
-
if (singleElem.default?.val === undefined)
|
|
221
|
+
if (singleElem.default?.val === undefined)
|
|
222
222
|
singleElem.default = elem.default; // Copy entire default object {val, location}
|
|
223
|
-
}
|
|
224
223
|
}
|
|
225
224
|
else if (flattenedNames.length > 1) {
|
|
226
225
|
// Multi-element structure: warn that default is ignored
|
|
@@ -77,6 +77,7 @@ class Receiver extends Writable {
|
|
|
77
77
|
|
|
78
78
|
this._totalPayloadLength = 0;
|
|
79
79
|
this._messageLength = 0;
|
|
80
|
+
this._numFragments = 0;
|
|
80
81
|
this._fragments = [];
|
|
81
82
|
|
|
82
83
|
this._errored = false;
|
|
@@ -500,6 +501,19 @@ class Receiver extends Writable {
|
|
|
500
501
|
return;
|
|
501
502
|
}
|
|
502
503
|
|
|
504
|
+
if (this._maxFragments > 0 && ++this._numFragments > this._maxFragments) {
|
|
505
|
+
const error = this.createError(
|
|
506
|
+
RangeError,
|
|
507
|
+
'Too many message fragments',
|
|
508
|
+
false,
|
|
509
|
+
1008,
|
|
510
|
+
'WS_ERR_TOO_MANY_BUFFERED_PARTS'
|
|
511
|
+
);
|
|
512
|
+
|
|
513
|
+
cb(error);
|
|
514
|
+
return;
|
|
515
|
+
}
|
|
516
|
+
|
|
503
517
|
if (this._compressed) {
|
|
504
518
|
this._state = INFLATING;
|
|
505
519
|
this.decompress(data, cb);
|
|
@@ -507,22 +521,6 @@ class Receiver extends Writable {
|
|
|
507
521
|
}
|
|
508
522
|
|
|
509
523
|
if (data.length) {
|
|
510
|
-
if (
|
|
511
|
-
this._maxFragments > 0 &&
|
|
512
|
-
this._fragments.length >= this._maxFragments
|
|
513
|
-
) {
|
|
514
|
-
const error = this.createError(
|
|
515
|
-
RangeError,
|
|
516
|
-
'Too many message fragments',
|
|
517
|
-
false,
|
|
518
|
-
1008,
|
|
519
|
-
'WS_ERR_TOO_MANY_BUFFERED_PARTS'
|
|
520
|
-
);
|
|
521
|
-
|
|
522
|
-
cb(error);
|
|
523
|
-
return;
|
|
524
|
-
}
|
|
525
|
-
|
|
526
524
|
//
|
|
527
525
|
// This message is not compressed so its length is the sum of the payload
|
|
528
526
|
// length of all fragments.
|
|
@@ -562,22 +560,6 @@ class Receiver extends Writable {
|
|
|
562
560
|
return;
|
|
563
561
|
}
|
|
564
562
|
|
|
565
|
-
if (
|
|
566
|
-
this._maxFragments > 0 &&
|
|
567
|
-
this._fragments.length >= this._maxFragments
|
|
568
|
-
) {
|
|
569
|
-
const error = this.createError(
|
|
570
|
-
RangeError,
|
|
571
|
-
'Too many message fragments',
|
|
572
|
-
false,
|
|
573
|
-
1008,
|
|
574
|
-
'WS_ERR_TOO_MANY_BUFFERED_PARTS'
|
|
575
|
-
);
|
|
576
|
-
|
|
577
|
-
cb(error);
|
|
578
|
-
return;
|
|
579
|
-
}
|
|
580
|
-
|
|
581
563
|
this._fragments.push(buf);
|
|
582
564
|
}
|
|
583
565
|
|
|
@@ -604,6 +586,7 @@ class Receiver extends Writable {
|
|
|
604
586
|
this._totalPayloadLength = 0;
|
|
605
587
|
this._messageLength = 0;
|
|
606
588
|
this._fragmented = 0;
|
|
589
|
+
this._numFragments = 0;
|
|
607
590
|
this._fragments = [];
|
|
608
591
|
|
|
609
592
|
if (this._opcode === 2) {
|
|
@@ -43,9 +43,9 @@ class WebSocketServer extends EventEmitter {
|
|
|
43
43
|
* called
|
|
44
44
|
* @param {Function} [options.handleProtocols] A hook to handle protocols
|
|
45
45
|
* @param {String} [options.host] The hostname where to bind the server
|
|
46
|
-
* @param {Number} [options.maxBufferedChunks=
|
|
46
|
+
* @param {Number} [options.maxBufferedChunks=262144] The maximum number of
|
|
47
47
|
* buffered data chunks
|
|
48
|
-
* @param {Number} [options.maxFragments=
|
|
48
|
+
* @param {Number} [options.maxFragments=16384] The maximum number of message
|
|
49
49
|
* fragments
|
|
50
50
|
* @param {Number} [options.maxPayload=104857600] The maximum allowed message
|
|
51
51
|
* size
|
|
@@ -69,8 +69,8 @@ class WebSocketServer extends EventEmitter {
|
|
|
69
69
|
options = {
|
|
70
70
|
allowSynchronousEvents: true,
|
|
71
71
|
autoPong: true,
|
|
72
|
-
maxBufferedChunks:
|
|
73
|
-
maxFragments:
|
|
72
|
+
maxBufferedChunks: 256 * 1024,
|
|
73
|
+
maxFragments: 16 * 1024,
|
|
74
74
|
maxPayload: 100 * 1024 * 1024,
|
|
75
75
|
skipUTF8Validation: false,
|
|
76
76
|
perMessageDeflate: false,
|
|
@@ -646,9 +646,9 @@ module.exports = WebSocket;
|
|
|
646
646
|
* masking key
|
|
647
647
|
* @param {Number} [options.handshakeTimeout] Timeout in milliseconds for the
|
|
648
648
|
* handshake request
|
|
649
|
-
* @param {Number} [options.maxBufferedChunks=
|
|
649
|
+
* @param {Number} [options.maxBufferedChunks=262144] The maximum number of
|
|
650
650
|
* buffered data chunks
|
|
651
|
-
* @param {Number} [options.maxFragments=
|
|
651
|
+
* @param {Number} [options.maxFragments=16384] The maximum number of message
|
|
652
652
|
* fragments
|
|
653
653
|
* @param {Number} [options.maxPayload=104857600] The maximum allowed message
|
|
654
654
|
* size
|
|
@@ -670,8 +670,8 @@ function initAsClient(websocket, address, protocols, options) {
|
|
|
670
670
|
autoPong: true,
|
|
671
671
|
closeTimeout: CLOSE_TIMEOUT,
|
|
672
672
|
protocolVersion: protocolVersions[1],
|
|
673
|
-
maxBufferedChunks:
|
|
674
|
-
maxFragments:
|
|
673
|
+
maxBufferedChunks: 256 * 1024,
|
|
674
|
+
maxFragments: 16 * 1024,
|
|
675
675
|
maxPayload: 100 * 1024 * 1024,
|
|
676
676
|
skipUTF8Validation: false,
|
|
677
677
|
perMessageDeflate: true,
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "ws",
|
|
3
|
-
"version": "8.21.
|
|
3
|
+
"version": "8.21.1",
|
|
4
4
|
"description": "Simple to use, blazing fast and thoroughly tested websocket client and server for Node.js",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"HyBi",
|
|
@@ -66,5 +66,9 @@
|
|
|
66
66
|
"nyc": "^15.0.0",
|
|
67
67
|
"prettier": "^3.0.0",
|
|
68
68
|
"utf-8-validate": "^6.0.0"
|
|
69
|
+
},
|
|
70
|
+
"allowScripts": {
|
|
71
|
+
"bufferutil": true,
|
|
72
|
+
"utf-8-validate": true
|
|
69
73
|
}
|
|
70
74
|
}
|
package/package.json
CHANGED