@sap/cds 9.7.1 → 9.8.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +40 -0
- package/_i18n/i18n_en_US_saptrc.properties +1 -56
- package/_i18n/messages_en_US_saptrc.properties +1 -92
- package/eslint.config.mjs +1 -1
- package/lib/compile/for/lean_drafts.js +12 -0
- package/lib/compile/to/json.js +4 -2
- package/lib/env/defaults.js +1 -0
- package/lib/env/serviceBindings.js +15 -5
- package/lib/index.js +1 -1
- package/lib/log/cds-error.js +33 -20
- package/lib/req/spawn.js +2 -2
- package/lib/srv/bindings.js +6 -13
- package/lib/srv/cds.Service.js +8 -36
- package/lib/srv/protocols/hcql.js +19 -2
- package/lib/utils/cds-utils.js +25 -16
- package/lib/utils/tar-win.js +106 -0
- package/lib/utils/tar.js +23 -158
- package/libx/_runtime/common/generic/crud.js +8 -7
- package/libx/_runtime/common/generic/sorting.js +7 -3
- package/libx/_runtime/common/utils/resolveView.js +47 -40
- package/libx/_runtime/common/utils/rewriteAsterisks.js +1 -0
- package/libx/_runtime/fiori/lean-draft.js +11 -2
- package/libx/_runtime/messaging/kafka.js +6 -5
- package/libx/_runtime/remote/Service.js +3 -0
- package/libx/_runtime/remote/utils/client.js +2 -4
- package/libx/_runtime/remote/utils/query.js +4 -4
- package/libx/odata/middleware/batch.js +316 -339
- package/libx/odata/middleware/create.js +0 -5
- package/libx/odata/middleware/delete.js +0 -5
- package/libx/odata/middleware/operation.js +10 -8
- package/libx/odata/middleware/read.js +0 -10
- package/libx/odata/middleware/stream.js +1 -0
- package/libx/odata/middleware/update.js +0 -6
- package/libx/odata/parse/afterburner.js +47 -22
- package/libx/odata/parse/cqn2odata.js +6 -1
- package/libx/odata/parse/grammar.peggy +14 -2
- package/libx/odata/parse/multipartToJson.js +2 -1
- package/libx/odata/parse/parser.js +1 -1
- package/package.json +2 -2
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
const cds = require('../../index'), {inspect} = cds.utils
|
|
2
2
|
const express = require('express')
|
|
3
|
+
const { pipeline } = require('node:stream/promises')
|
|
3
4
|
|
|
4
5
|
const LOG = cds.log('hcql')
|
|
5
6
|
const PROD = process.env.NODE_ENV === 'production'
|
|
@@ -69,7 +70,18 @@ class HCQLAdapter extends require('./http') {
|
|
|
69
70
|
* The ultimate handler for all CRUD requests.
|
|
70
71
|
*/
|
|
71
72
|
crud (req, res, next) {
|
|
72
|
-
let query = this.query4
|
|
73
|
+
let query = this.query4(req)
|
|
74
|
+
|
|
75
|
+
if (query.stream && can_stream(req))
|
|
76
|
+
return this.service
|
|
77
|
+
.tx(() =>
|
|
78
|
+
query.stream().then(results => {
|
|
79
|
+
res.set('content-type', 'application/octet-stream')
|
|
80
|
+
return pipeline(results, res)
|
|
81
|
+
})
|
|
82
|
+
)
|
|
83
|
+
.catch(next)
|
|
84
|
+
|
|
73
85
|
return this.service.run (query)
|
|
74
86
|
.then (results => this.reply (results, res))
|
|
75
87
|
.catch (next)
|
|
@@ -81,7 +93,9 @@ class HCQLAdapter extends require('./http') {
|
|
|
81
93
|
* which is expected to be a plain CQN object or a CQL string.
|
|
82
94
|
*/
|
|
83
95
|
query4 (/** @type express.Request */ req) {
|
|
84
|
-
let q = req.body = cds.ql(req.body ?? {})
|
|
96
|
+
let q = req.body = cds.ql(req.body ?? {})
|
|
97
|
+
if (!q.bind) this.error(400, 'Invalid query', { query: req.body })
|
|
98
|
+
q.bind(this.service)
|
|
85
99
|
// handle request headers
|
|
86
100
|
if (q.SELECT) {
|
|
87
101
|
if (req.get('Accept-Language')) q.SELECT.localized = true
|
|
@@ -148,4 +162,7 @@ const ql_fragment = x => {
|
|
|
148
162
|
}
|
|
149
163
|
return x
|
|
150
164
|
}
|
|
165
|
+
const can_stream = req =>
|
|
166
|
+
req.headers.accept?.split?.(',').find(h => h.split(';')[0].trim() === 'application/octet-stream')
|
|
167
|
+
|
|
151
168
|
module.exports = HCQLAdapter
|
package/lib/utils/cds-utils.js
CHANGED
|
@@ -1,10 +1,6 @@
|
|
|
1
1
|
const cwd = process.env._original_cwd || process.cwd()
|
|
2
2
|
const cds = require('../index')
|
|
3
3
|
|
|
4
|
-
/* eslint no-empty: ["error", { "allowEmptyCatch": true }] */
|
|
5
|
-
// eslint-disable-next-line no-unused-vars
|
|
6
|
-
const _tarLib = () => { try { return require('tar') } catch(_) {} }
|
|
7
|
-
|
|
8
4
|
exports = module.exports = new class {
|
|
9
5
|
get colors() { return super.colors = require('./colors') }
|
|
10
6
|
get inflect() { return super.inflect = require('./inflect') }
|
|
@@ -17,9 +13,19 @@ exports = module.exports = new class {
|
|
|
17
13
|
const {format} = require('node:util')
|
|
18
14
|
return super.format = format
|
|
19
15
|
}
|
|
16
|
+
get yaml() {
|
|
17
|
+
const yaml = require('js-yaml')
|
|
18
|
+
return super.yaml = Object.assign(yaml,{parse:yaml.load})
|
|
19
|
+
}
|
|
20
|
+
get tar() {
|
|
21
|
+
if (process.platform === 'win32') try { require.resolve('tar')
|
|
22
|
+
return super.tar = require('./tar-lib')
|
|
23
|
+
} catch {
|
|
24
|
+
return super.tar = require('./tar-win')
|
|
25
|
+
}
|
|
26
|
+
else return super.tar = require('./tar')
|
|
27
|
+
}
|
|
20
28
|
get uuid() { return super.uuid = require('crypto').randomUUID }
|
|
21
|
-
get yaml() { const yaml = require('js-yaml'); return super.yaml = Object.assign(yaml,{parse:yaml.load}) }
|
|
22
|
-
get tar() { return super.tar = process.platform === 'win32' && _tarLib() ? require('./tar-lib') : require('./tar') }
|
|
23
29
|
get semver() { return super.semver = require('./version') }
|
|
24
30
|
}
|
|
25
31
|
|
|
@@ -309,17 +315,20 @@ exports.csv = require('./csv-reader')
|
|
|
309
315
|
* Loads a file through ESM or CommonJs.
|
|
310
316
|
* @returns { Promise<any> }
|
|
311
317
|
*/
|
|
312
|
-
// TODO find a better place.
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
if (err.code
|
|
318
|
-
|
|
319
|
-
const { pathToFileURL } = require('url')
|
|
320
|
-
return import (pathToFileURL(id).href) // must use a file: URL, esp. on Windows for C:\... paths
|
|
318
|
+
exports._import = id => { // TODO find a better place.
|
|
319
|
+
if (id.endsWith('.mjs')) return _import (id)
|
|
320
|
+
if (id.endsWith('.cjs')) return require (id)
|
|
321
|
+
else try { return require(id) } catch (err) {
|
|
322
|
+
if (err.message === 'Cannot use import statement outside a module') return _import (id) // for jest
|
|
323
|
+
if (err.code === 'ERR_REQUIRE_ESM') return _import (id)
|
|
324
|
+
else throw err
|
|
321
325
|
}
|
|
322
326
|
}
|
|
327
|
+
const _import = process.platform === 'win32' ? (()=>{
|
|
328
|
+
const url = require('url') // On Windows we must use a file: URL, esp. for C:\... paths
|
|
329
|
+
return id => import (url.pathToFileURL(id).href)
|
|
330
|
+
})() : id => import (id)
|
|
331
|
+
|
|
323
332
|
|
|
324
333
|
const SECRETS = /(passw)|(cert)|(ca)|(secret)|(key)/i
|
|
325
334
|
/**
|
|
@@ -349,7 +358,7 @@ exports.redacted = function _redacted(cred) {
|
|
|
349
358
|
|
|
350
359
|
|
|
351
360
|
/**
|
|
352
|
-
* A variant of child_process.exec that returns a promise,
|
|
361
|
+
* A variant of child_process.exec that returns a promise,
|
|
353
362
|
* which resolves with the command's stdout split into lines.
|
|
354
363
|
* @example
|
|
355
364
|
* await cds.utils.sh `npm ls -lp --depth=0`
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
// This module monkey patches ./tar.js to work on Windows, where tar does not work properly w/o these changes.
|
|
2
|
+
|
|
3
|
+
exports = module.exports = require('./tar')
|
|
4
|
+
|
|
5
|
+
exports._spawn_tar_c = (dir, args) => {
|
|
6
|
+
args.push('.')
|
|
7
|
+
if (Array.isArray(args[0])) return winSpawnTempDir(dir, args)
|
|
8
|
+
else return winSpawnDir(dir, args)
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
exports._path = path => {
|
|
12
|
+
if (!path) return path
|
|
13
|
+
if (typeof path === 'string') return path.replace('C:', '//localhost/c$').replace(/\\+/g, '/')
|
|
14
|
+
if (Array.isArray(path)) return path.map(el => exports._path(el))
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
const child_process = require('child_process')
|
|
19
|
+
const spawn = /\btar\b/.test(process.env.DEBUG) ? (cmd, args, options) => {
|
|
20
|
+
Error.captureStackTrace(spawn,spawn)
|
|
21
|
+
process.stderr.write(cmd +' ', args.join(' ') +' '+ spawn.stack.slice(7) + '\n')
|
|
22
|
+
return child_process.spawn(cmd, args, options)
|
|
23
|
+
} : child_process.spawn
|
|
24
|
+
|
|
25
|
+
const cds = require('../index'), { fs, path, exists, rimraf } = cds.utils
|
|
26
|
+
const { PassThrough } = require('stream')
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
// spawn tar on Windows, using the cli version
|
|
31
|
+
const winSpawnDir = (dir, args) => {
|
|
32
|
+
if (args.some(arg => arg === '-f')) return spawn ('tar', ['c', '-C', exports._path(dir), ...exports._path(args)])
|
|
33
|
+
else return spawn ('tar', ['cf', '-', '-C', exports._path(dir), ...exports._path(args)])
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
// copy a directory recursively on Windows, using fs.promises
|
|
37
|
+
async function winCopyDir(src, dest) {
|
|
38
|
+
if ((await fs.promises.stat(src)).isDirectory()) {
|
|
39
|
+
const entries = await fs.promises.readdir(src)
|
|
40
|
+
return Promise.all(entries.map(async each => winCopyDir(path.join(src, each), path.join(dest, each))))
|
|
41
|
+
} else {
|
|
42
|
+
await fs.promises.mkdir(path.dirname(dest), { recursive: true })
|
|
43
|
+
return fs.promises.copyFile(src, dest)
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
// copy resources containing files and folders to temp dir on Windows
|
|
48
|
+
// cli tar has a size limit on Windows
|
|
49
|
+
const winCreateTemp = async (root, resources) => {
|
|
50
|
+
// Asynchronously copies the entire content from src to dest.
|
|
51
|
+
const temp = await fs.promises.mkdtemp(`${fs.realpathSync(require('os').tmpdir())}${path.sep}tar-`)
|
|
52
|
+
for (let resource of resources) {
|
|
53
|
+
const destination = path.join(temp, path.relative(root, resource))
|
|
54
|
+
if ((await fs.promises.stat(resource)).isFile()) {
|
|
55
|
+
const dirName = path.dirname(destination)
|
|
56
|
+
if (!await exists(dirName)) {
|
|
57
|
+
await fs.promises.mkdir(dirName, { recursive: true })
|
|
58
|
+
}
|
|
59
|
+
await fs.promises.copyFile(resource, destination)
|
|
60
|
+
} else {
|
|
61
|
+
if (fs.promises.cp) {
|
|
62
|
+
await fs.promises.cp(resource, destination, { recursive: true })
|
|
63
|
+
} else {
|
|
64
|
+
// node < 16
|
|
65
|
+
await winCopyDir(resource, destination)
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
return temp
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
// spawn tar on Windows, using a temp dir, which is copied from the original dir
|
|
74
|
+
// cli tar has a size limit on Windows
|
|
75
|
+
const winSpawnTempDir = (dir, args) => {
|
|
76
|
+
// Synchronous trick: use a PassThrough as placeholder
|
|
77
|
+
const stdout = new PassThrough()
|
|
78
|
+
const stderr = new PassThrough()
|
|
79
|
+
const c = {
|
|
80
|
+
stdout,
|
|
81
|
+
stderr,
|
|
82
|
+
on: (...a) => { stdout.on(...a); stderr.on(...a); return c },
|
|
83
|
+
once: (...a) => { stdout.once(...a); stderr.once(...a); return c },
|
|
84
|
+
kill: () => {},
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
// async copy, then swap streams/events
|
|
88
|
+
winCreateTemp(dir, args.shift()).then(tempPath => {
|
|
89
|
+
const real = winSpawnDir(tempPath, args)
|
|
90
|
+
real.stdout.pipe(stdout)
|
|
91
|
+
real.stderr && real.stderr.pipe(stderr)
|
|
92
|
+
const cleanup = () => exists(tempPath) && rimraf(tempPath)
|
|
93
|
+
real.on('close', (...ev) => {
|
|
94
|
+
stdout.emit('close', ...ev)
|
|
95
|
+
stderr.emit('close', ...ev)
|
|
96
|
+
cleanup()
|
|
97
|
+
})
|
|
98
|
+
real.on('error', (...ev) => {
|
|
99
|
+
stdout.emit('error', ...ev)
|
|
100
|
+
stderr.emit('error', ...ev)
|
|
101
|
+
cleanup()
|
|
102
|
+
})
|
|
103
|
+
c.kill = (...ev) => real.kill(...ev)
|
|
104
|
+
})
|
|
105
|
+
return c
|
|
106
|
+
}
|
package/lib/utils/tar.js
CHANGED
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
const { PassThrough } = require('stream')
|
|
2
1
|
const child_process = require('child_process')
|
|
3
2
|
const spawn = /\btar\b/.test(process.env.DEBUG) ? (cmd, args, options) => {
|
|
4
3
|
Error.captureStackTrace(spawn,spawn)
|
|
@@ -6,137 +5,9 @@ const spawn = /\btar\b/.test(process.env.DEBUG) ? (cmd, args, options) => {
|
|
|
6
5
|
return child_process.spawn(cmd, args, options)
|
|
7
6
|
} : child_process.spawn
|
|
8
7
|
|
|
9
|
-
const cds = require('../index'), { fs, path, mkdirp
|
|
8
|
+
const cds = require('../index'), { fs, path, mkdirp } = cds.utils
|
|
10
9
|
const _resolve = (...x) => path.resolve (cds.root,...x)
|
|
11
10
|
|
|
12
|
-
// ======= ONLY_FOR_WINDOWS ======
|
|
13
|
-
// This section contains logic relevant for Windows OS.
|
|
14
|
-
|
|
15
|
-
// tar does not work properly on Windows w/o this change
|
|
16
|
-
const win = path => {
|
|
17
|
-
if (!path) return path
|
|
18
|
-
if (typeof path === 'string') return path.replace('C:', '//localhost/c$').replace(/\\+/g, '/')
|
|
19
|
-
if (Array.isArray(path)) return path.map(el => win(el))
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
// spawn tar on Windows, using the cli version
|
|
23
|
-
const winSpawnDir = (dir, args) => {
|
|
24
|
-
if (args.some(arg => arg === '-f')) return spawn ('tar', ['c', '-C', win(dir), ...win(args)])
|
|
25
|
-
else return spawn ('tar', ['cf', '-', '-C', win(dir), ...win(args)])
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
// copy a directory recursively on Windows, using fs.promises
|
|
29
|
-
async function winCopyDir(src, dest) {
|
|
30
|
-
if ((await fs.promises.stat(src)).isDirectory()) {
|
|
31
|
-
const entries = await fs.promises.readdir(src)
|
|
32
|
-
return Promise.all(entries.map(async each => winCopyDir(path.join(src, each), path.join(dest, each))))
|
|
33
|
-
} else {
|
|
34
|
-
await fs.promises.mkdir(path.dirname(dest), { recursive: true })
|
|
35
|
-
return fs.promises.copyFile(src, dest)
|
|
36
|
-
}
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
// copy resources containing files and folders to temp dir on Windows
|
|
40
|
-
// cli tar has a size limit on Windows
|
|
41
|
-
const winCreateTemp = async (root, resources) => {
|
|
42
|
-
// Asynchronously copies the entire content from src to dest.
|
|
43
|
-
const temp = await fs.promises.mkdtemp(`${fs.realpathSync(require('os').tmpdir())}${path.sep}tar-`)
|
|
44
|
-
for (let resource of resources) {
|
|
45
|
-
const destination = path.join(temp, path.relative(root, resource))
|
|
46
|
-
if ((await fs.promises.stat(resource)).isFile()) {
|
|
47
|
-
const dirName = path.dirname(destination)
|
|
48
|
-
if (!await exists(dirName)) {
|
|
49
|
-
await fs.promises.mkdir(dirName, { recursive: true })
|
|
50
|
-
}
|
|
51
|
-
await fs.promises.copyFile(resource, destination)
|
|
52
|
-
} else {
|
|
53
|
-
if (fs.promises.cp) {
|
|
54
|
-
await fs.promises.cp(resource, destination, { recursive: true })
|
|
55
|
-
} else {
|
|
56
|
-
// node < 16
|
|
57
|
-
await winCopyDir(resource, destination)
|
|
58
|
-
}
|
|
59
|
-
}
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
return temp
|
|
63
|
-
}
|
|
64
|
-
|
|
65
|
-
// spawn tar on Windows, using a temp dir, which is copied from the original dir
|
|
66
|
-
// cli tar has a size limit on Windows
|
|
67
|
-
const winSpawnTempDir = (dir, args) => {
|
|
68
|
-
// Synchronous trick: use a PassThrough as placeholder
|
|
69
|
-
const stdout = new PassThrough()
|
|
70
|
-
const stderr = new PassThrough()
|
|
71
|
-
const c = {
|
|
72
|
-
stdout,
|
|
73
|
-
stderr,
|
|
74
|
-
on: (...a) => { stdout.on(...a); stderr.on(...a); return c },
|
|
75
|
-
once: (...a) => { stdout.once(...a); stderr.once(...a); return c },
|
|
76
|
-
kill: () => {},
|
|
77
|
-
}
|
|
78
|
-
|
|
79
|
-
// async copy, then swap streams/events
|
|
80
|
-
winCreateTemp(dir, args.shift()).then(tempPath => {
|
|
81
|
-
const real = winSpawnDir(tempPath, args)
|
|
82
|
-
real.stdout.pipe(stdout)
|
|
83
|
-
real.stderr && real.stderr.pipe(stderr)
|
|
84
|
-
const cleanup = () => exists(tempPath) && rimraf(tempPath)
|
|
85
|
-
real.on('close', (...ev) => {
|
|
86
|
-
stdout.emit('close', ...ev)
|
|
87
|
-
stderr.emit('close', ...ev)
|
|
88
|
-
cleanup()
|
|
89
|
-
})
|
|
90
|
-
real.on('error', (...ev) => {
|
|
91
|
-
stdout.emit('error', ...ev)
|
|
92
|
-
stderr.emit('error', ...ev)
|
|
93
|
-
cleanup()
|
|
94
|
-
})
|
|
95
|
-
c.kill = (...ev) => real.kill(...ev)
|
|
96
|
-
})
|
|
97
|
-
return c
|
|
98
|
-
}
|
|
99
|
-
|
|
100
|
-
// ====== END ONLY_FOR_WINDOWS ======
|
|
101
|
-
|
|
102
|
-
const tarInfo = async (info) => {
|
|
103
|
-
let cmd, param
|
|
104
|
-
if (info === 'version') {
|
|
105
|
-
cmd = 'tar'
|
|
106
|
-
param = ['--version']
|
|
107
|
-
} else {
|
|
108
|
-
cmd = process.platform === 'win32' ? 'where' : 'which'
|
|
109
|
-
param = ['tar']
|
|
110
|
-
}
|
|
111
|
-
|
|
112
|
-
const c = spawn (cmd, param)
|
|
113
|
-
|
|
114
|
-
return {__proto__:c,
|
|
115
|
-
then (resolve, reject) {
|
|
116
|
-
let data=[], stderr=''
|
|
117
|
-
c.stdout.on('data', d => {
|
|
118
|
-
data.push(d)
|
|
119
|
-
})
|
|
120
|
-
c.stderr.on('data', d => stderr += d)
|
|
121
|
-
c.on('close', code => {
|
|
122
|
-
code ? reject(new Error(stderr)) : resolve(Buffer.concat(data).toString().replace(/\n/g,'').replace(/\r/g,''))
|
|
123
|
-
})
|
|
124
|
-
c.on('error', reject)
|
|
125
|
-
}
|
|
126
|
-
}
|
|
127
|
-
}
|
|
128
|
-
|
|
129
|
-
const logDebugTar = async () => {
|
|
130
|
-
const LOG = cds.log('tar')
|
|
131
|
-
if (!LOG?._debug) return
|
|
132
|
-
try {
|
|
133
|
-
LOG (`tar path: ${await tarInfo('path')}`)
|
|
134
|
-
LOG (`tar version: ${await tarInfo('version')}`)
|
|
135
|
-
} catch (err) {
|
|
136
|
-
LOG('tar error', err)
|
|
137
|
-
}
|
|
138
|
-
}
|
|
139
|
-
|
|
140
11
|
/**
|
|
141
12
|
* Creates a tar archive, to an in-memory Buffer, or piped to write stream or file.
|
|
142
13
|
* @example ```js
|
|
@@ -162,26 +33,12 @@ const logDebugTar = async () => {
|
|
|
162
33
|
* - `.to()` is a convenient shortcut to pipe the output into a write stream
|
|
163
34
|
*/
|
|
164
35
|
exports.create = (dir='.', ...args) => {
|
|
165
|
-
|
|
36
|
+
|
|
166
37
|
if (typeof dir === 'string') dir = _resolve(dir)
|
|
167
38
|
if (Array.isArray(dir)) [ dir, ...args ] = [ cds.root, dir, ...args ]
|
|
168
|
-
|
|
169
|
-
let c
|
|
170
39
|
args = args.filter(el => el)
|
|
171
|
-
if (process.platform === 'win32') {
|
|
172
|
-
args.push('.')
|
|
173
|
-
if (Array.isArray(args[0])) c = winSpawnTempDir(dir, args)
|
|
174
|
-
else c = winSpawnDir(dir, args)
|
|
175
|
-
} else {
|
|
176
|
-
if (Array.isArray(args[0])) {
|
|
177
|
-
args.push (...args.shift().map (f => path.isAbsolute(f) ? path.relative(dir,f) : f))
|
|
178
|
-
} else {
|
|
179
|
-
args.push('.')
|
|
180
|
-
}
|
|
181
|
-
|
|
182
|
-
c = spawn ('tar', ['c', '-C', dir, ...args], { env: { COPYFILE_DISABLE: 1 }})
|
|
183
|
-
}
|
|
184
40
|
|
|
41
|
+
const c = exports._spawn_tar_c (dir, args)
|
|
185
42
|
return {__proto__:c, // returning a thenable + fluent ChildProcess...
|
|
186
43
|
|
|
187
44
|
/**
|
|
@@ -219,6 +76,22 @@ exports.create = (dir='.', ...args) => {
|
|
|
219
76
|
}
|
|
220
77
|
}
|
|
221
78
|
|
|
79
|
+
|
|
80
|
+
// Extracted to allow os-specific implementations, e.g. for win32
|
|
81
|
+
exports._spawn_tar_c = (dir, args) => {
|
|
82
|
+
if (Array.isArray(args[0])) {
|
|
83
|
+
args.push (...args.shift().map (f => path.isAbsolute(f) ? path.relative(dir,f) : f))
|
|
84
|
+
} else {
|
|
85
|
+
args.push ('.')
|
|
86
|
+
}
|
|
87
|
+
return spawn ('tar', ['c', '-C', dir, ...args], { env: { COPYFILE_DISABLE: 1 }})
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
|
|
91
|
+
// Extracted to allow os-specific implementations, e.g. for win32
|
|
92
|
+
exports._path = p => p
|
|
93
|
+
|
|
94
|
+
|
|
222
95
|
/**
|
|
223
96
|
* Extracts a tar archive, from an in-memory Buffer, or piped from a read stream or file.
|
|
224
97
|
* @example ```js
|
|
@@ -242,7 +115,7 @@ exports.extract = (archive, ...args) => ({
|
|
|
242
115
|
to (...dest) {
|
|
243
116
|
if (typeof dest === 'string') dest = _resolve(...dest)
|
|
244
117
|
const input = typeof archive !== 'string' || archive == '-' ? '-' : _resolve(archive)
|
|
245
|
-
const x = spawn('tar', ['xf',
|
|
118
|
+
const x = spawn('tar', ['xf', exports._path(input), '-C', exports._path(dest), ...args])
|
|
246
119
|
if (archive === '-') return x.stdin
|
|
247
120
|
if (Buffer.isBuffer(archive)) archive = require('stream').Readable.from (archive)
|
|
248
121
|
if (typeof archive !== 'string') (archive.stdout || archive) .pipe (x.stdin)
|
|
@@ -251,8 +124,8 @@ exports.extract = (archive, ...args) => ({
|
|
|
251
124
|
x.stderr.on ('data', d => stderr += d)
|
|
252
125
|
return {__proto__:x,
|
|
253
126
|
then (resolve, reject) {
|
|
254
|
-
x.on('close',
|
|
255
|
-
if (
|
|
127
|
+
x.on('close', err => {
|
|
128
|
+
if (err) return reject (new Error(stderr))
|
|
256
129
|
if (process.platform === 'linux') stdout = stderr
|
|
257
130
|
resolve (stdout ? stdout.split('\n').slice(0,-1).map(x => x.replace(/^x |\r/g,'')): undefined)
|
|
258
131
|
})
|
|
@@ -304,12 +177,4 @@ exports.t = tar.tf = tar.list
|
|
|
304
177
|
* @example fs.createReadStream('t.tar') .pipe (tar.x.to('dest/dir'))
|
|
305
178
|
* @returns `stdin` of the tar child process
|
|
306
179
|
*/
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
// ---------------------------------------------------------------------------------
|
|
312
|
-
// Compatibility...
|
|
313
|
-
|
|
314
|
-
exports.packTarArchive = (resources,d) => d ? tar.cz (d,resources) : tar.cz (resources)
|
|
315
|
-
exports.unpackTarArchive = (x,dir) => tar.xz(x).to(dir)
|
|
180
|
+
exports.extract.to = function (..._) { return this('-').to(..._) }
|
|
@@ -11,19 +11,19 @@ module.exports = cds.service.impl(function () {
|
|
|
11
11
|
this.on(['CREATE', 'READ', 'UPDATE', 'UPSERT', 'DELETE'], '*', async function handle_crud_requests(req) {
|
|
12
12
|
|
|
13
13
|
if (!cds.db)
|
|
14
|
-
return req.reject
|
|
14
|
+
return req.reject('NO_DATABASE_CONNECTION') // REVISIT: error message
|
|
15
15
|
|
|
16
16
|
if (!req.query)
|
|
17
|
-
return req.reject
|
|
17
|
+
return req.reject(501, 'The request has no query and cannot be served generically.')
|
|
18
18
|
|
|
19
19
|
if (typeof req.query !== 'string' && req.target?._hasPersistenceSkip)
|
|
20
|
-
return req.reject
|
|
20
|
+
return req.reject(501, `Entity "${req.target.name}" is annotated with "@cds.persistence.skip" and cannot be served generically.`)
|
|
21
21
|
|
|
22
22
|
// validate that all elements in path exist on db, if necessary
|
|
23
23
|
// - INSERT has no where clause to do this in one roundtrip
|
|
24
24
|
// - SELECT returns [] -> really empty collection or invalid path?
|
|
25
25
|
const subject = req.query.INSERT?.into || req.query.SELECT?.from
|
|
26
|
-
const pathExistsQuery = subject?.ref?.length > 1 && SELECT(1).from({ ref: subject.ref.slice(0
|
|
26
|
+
const pathExistsQuery = subject?.ref?.length > 1 && SELECT(1).from({ ref: subject.ref.slice(0, -1) })
|
|
27
27
|
|
|
28
28
|
if (req.event === 'CREATE' && pathExistsQuery) {
|
|
29
29
|
// REVISIT: Why dont't we just run the insert and check affected rows?
|
|
@@ -33,10 +33,10 @@ module.exports = cds.service.impl(function () {
|
|
|
33
33
|
|
|
34
34
|
if (req.event in { DELETE: 1, UPDATE: 1 } && req.target?._isSingleton) {
|
|
35
35
|
if (req.event === 'DELETE' && !req.target['@odata.singleton.nullable'])
|
|
36
|
-
return req.reject
|
|
36
|
+
return req.reject(400, 'SINGLETON_NOT_NULLABLE')
|
|
37
37
|
|
|
38
38
|
const selectSingleton = SELECT.one(req.target)
|
|
39
|
-
const keyColumns = [...(req.target.keys||[])].filter(e => !e.isAssociation).map(e => e.name)
|
|
39
|
+
const keyColumns = [...(req.target.keys || [])].filter(e => !e.isAssociation).map(e => e.name)
|
|
40
40
|
|
|
41
41
|
// if no keys available, select all columns so we can delete the singleton with same content
|
|
42
42
|
if (keyColumns.length) selectSingleton.columns(keyColumns)
|
|
@@ -54,7 +54,8 @@ module.exports = cds.service.impl(function () {
|
|
|
54
54
|
|
|
55
55
|
if (req.event === 'READ' && req.query?.SELECT && req.locale) req.query.SELECT.localized ??= true
|
|
56
56
|
|
|
57
|
-
|
|
57
|
+
// REVISIT for cds^10: can we always use cds.db.dispatch(req)?
|
|
58
|
+
const result = req.iterator && !req.objectMode ? await cds.db.dispatch(req) : await cds.run(req.query, req.data)
|
|
58
59
|
|
|
59
60
|
if (req.event === 'READ') {
|
|
60
61
|
// do not execute additional select to distinguish between 412 and 404
|
|
@@ -50,6 +50,12 @@ const _addDefaultSortOrder = (req, select) => {
|
|
|
50
50
|
)
|
|
51
51
|
}
|
|
52
52
|
|
|
53
|
+
const containsAggregation = column => {
|
|
54
|
+
if (column.func) return true
|
|
55
|
+
if (column.xpr) return column.xpr.some(xpr => containsAggregation(xpr))
|
|
56
|
+
return false
|
|
57
|
+
}
|
|
58
|
+
|
|
53
59
|
/**
|
|
54
60
|
* 1. query options --> already set in req.query
|
|
55
61
|
* 2. orders from view
|
|
@@ -63,9 +69,7 @@ const handle_sorting = function (req) {
|
|
|
63
69
|
let select = req.query.SELECT
|
|
64
70
|
|
|
65
71
|
// do not sort for /$count queries or queries only using aggregations
|
|
66
|
-
if (select.columns
|
|
67
|
-
return
|
|
68
|
-
}
|
|
72
|
+
if (select.columns?.length && select.columns.every(col => containsAggregation(col))) return
|
|
69
73
|
|
|
70
74
|
if (select.from && select.from.SELECT) {
|
|
71
75
|
// add default sort to root query
|