@sap/cds-dk 10.1.0 → 10.1.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +12 -0
- package/bin/bind.js +4 -0
- package/bin/build.js +1 -0
- package/bin/compile/index.js +12 -11
- package/bin/completion/index.js +21 -10
- package/bin/completion/scripts/cds.fish +179 -127
- package/bin/completion/scripts/cds.ps1 +88 -14
- package/bin/completion/scripts/cds.sh +71 -5
- package/bin/completion/scripts/cds.zsh +81 -15
- package/bin/completion/templates/cds-tmpl.ps1 +15 -6
- package/bin/deploy/index.js +7 -3
- package/bin/env.js +4 -1
- package/bin/export.js +9 -1
- package/bin/import.js +4 -1
- package/bin/init.js +1 -0
- package/bin/inspect.js +3 -1
- package/bin/lint.js +5 -3
- package/bin/minify.js +6 -1
- package/bin/repl.js +11 -2
- package/bin/run.js +12 -1
- package/bin/serve.js +13 -2
- package/bin/up.js +4 -1
- package/bin/util/containerize.js +2 -0
- package/bin/watch/index.js +8 -1
- package/lib/build/util.js +2 -4
- package/lib/init/constants.js +1 -1
- package/lib/init/template/data/as-json.js +3 -0
- package/node_modules/@cap-js/db-service/CHANGELOG.md +17 -0
- package/node_modules/@cap-js/db-service/lib/common/generic-pool.js +4 -7
- package/node_modules/@cap-js/db-service/lib/cqn2pql.js +1 -1
- package/node_modules/@cap-js/db-service/lib/cqn2sql.js +15 -11
- package/node_modules/@cap-js/db-service/lib/cqn4sql.js +119 -19
- package/node_modules/@cap-js/db-service/lib/infer/index.js +27 -16
- package/node_modules/@cap-js/db-service/lib/recursion-guard.js +40 -0
- package/node_modules/@cap-js/db-service/lib/search.js +1 -1
- package/node_modules/@cap-js/db-service/package.json +1 -1
- package/node_modules/@cap-js/sqlite/CHANGELOG.md +19 -0
- package/node_modules/@cap-js/sqlite/lib/SQLiteService.js +110 -14
- package/node_modules/@cap-js/sqlite/package.json +3 -3
- package/node_modules/@sap/cds-compiler/CHANGELOG.md +15 -0
- package/node_modules/@sap/cds-compiler/lib/compiler/resolve.js +7 -3
- package/node_modules/@sap/cds-compiler/lib/model/csnUtils.js +2 -0
- package/node_modules/@sap/cds-compiler/lib/transform/db/applyTransformations.js +12 -0
- package/node_modules/@sap/cds-compiler/lib/transform/db/associations.js +5 -3
- package/node_modules/@sap/cds-compiler/lib/transform/db/flattening.js +4 -3
- package/node_modules/@sap/cds-compiler/lib/transform/effective/flattening.js +17 -1
- package/node_modules/@sap/cds-compiler/lib/transform/effective/main.js +19 -0
- package/node_modules/@sap/cds-compiler/lib/transform/fioriTreeViews.js +12 -1
- package/node_modules/@sap/cds-compiler/lib/transform/forRelationalDB.js +1 -1
- package/node_modules/@sap/cds-compiler/lib/transform/odata/adaptAnnotationRefs.js +11 -5
- package/node_modules/@sap/cds-compiler/lib/transform/translateAssocsToJoins.js +29 -2
- package/node_modules/@sap/cds-compiler/package.json +1 -1
- package/node_modules/@sap/cds-mtxs/CHANGELOG.md +34 -0
- package/node_modules/@sap/cds-mtxs/bin/cds-mtx-migrate.js +26 -5
- package/node_modules/@sap/cds-mtxs/bin/cds-mtx.js +3 -0
- package/node_modules/@sap/cds-mtxs/lib/utils.js +5 -4
- package/node_modules/@sap/cds-mtxs/package.json +1 -1
- package/node_modules/@sap/cds-mtxs/srv/cf/abstract-provisioning-service.js +62 -7
- package/node_modules/@sap/cds-mtxs/srv/cf/saas-provisioning-service.js +1 -1
- package/node_modules/@sap/cds-mtxs/srv/cf/sms-provisioning-service.js +23 -3
- package/node_modules/@sap/cds-mtxs/srv/extensibility/compat/update.js +15 -0
- package/node_modules/@sap/cds-mtxs/srv/extensibility/linter/annotations.js +2 -2
- package/node_modules/@sap/cds-mtxs/srv/extensibility/linter/config.js +2 -0
- package/node_modules/@sap/cds-mtxs/srv/extensibility/linter/index.js +2 -0
- package/node_modules/@sap/cds-mtxs/srv/extensibility/linter/protected-namespace.js +45 -0
- package/node_modules/@sap/cds-mtxs/srv/extensibility-service.js +8 -3
- package/node_modules/@sap/cds-mtxs/srv/jobs-service.js +49 -10
- package/node_modules/@sap/cds-mtxs/srv/model-provider.js +11 -2
- package/node_modules/@sap/cds-mtxs/srv/plugins/common.js +19 -12
- package/node_modules/@sap/cds-mtxs/srv/plugins/hana/hdi-ctnr-mgr.js +6 -3
- package/node_modules/@sap/cds-mtxs/srv/plugins/hana/serverless.js +271 -0
- package/node_modules/@sap/cds-mtxs/srv/plugins/hana.js +3 -2
- package/node_modules/@sap/cds-mtxs/srv/worker/job-worker-pool.js +212 -0
- package/node_modules/@sap/cds-mtxs/srv/worker/job-worker.js +61 -0
- package/package.json +2 -2
- /package/node_modules/@sap/cds-mtxs/srv/worker/{pool.js → model-worker-pool.js} +0 -0
- /package/node_modules/@sap/cds-mtxs/srv/worker/{load.js → model-worker.js} +0 -0
|
@@ -0,0 +1,212 @@
|
|
|
1
|
+
'use strict'
|
|
2
|
+
|
|
3
|
+
const { Worker } = require('worker_threads')
|
|
4
|
+
const path = require('path')
|
|
5
|
+
const cds = require('@sap/cds')
|
|
6
|
+
|
|
7
|
+
const LOG = cds.log('mtx')
|
|
8
|
+
const WORKER_PATH = path.join(__dirname, 'job-worker.js')
|
|
9
|
+
|
|
10
|
+
const IDLE = 'idle'
|
|
11
|
+
const BUSY = 'busy'
|
|
12
|
+
const BOOTING = 'booting'
|
|
13
|
+
|
|
14
|
+
class DeploymentPool {
|
|
15
|
+
|
|
16
|
+
constructor(size) {
|
|
17
|
+
this.size = size
|
|
18
|
+
this.workers = [] // { worker, state, pending: Map<id, {resolve,reject}> }
|
|
19
|
+
this.waitQueue = [] // queued acquire requests: { resolve, reject }
|
|
20
|
+
this._idCounter = 0
|
|
21
|
+
this._shuttingDown = false
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* Spawn a new worker, bootstrap it, and add it to the pool.
|
|
26
|
+
* Returns a promise that resolves when the worker is ready.
|
|
27
|
+
*/
|
|
28
|
+
_spawn() {
|
|
29
|
+
return new Promise((resolve, reject) => {
|
|
30
|
+
const worker = new Worker(WORKER_PATH, { env: process.env, workerData: { cdsEnv: cds.env, cdsRoot: cds.root } })
|
|
31
|
+
const entry = { worker, state: BOOTING, pending: new Map() }
|
|
32
|
+
this.workers.push(entry)
|
|
33
|
+
|
|
34
|
+
const onReady = (msg) => {
|
|
35
|
+
if (msg.ready) {
|
|
36
|
+
worker.off('message', onReady)
|
|
37
|
+
worker.off('error', onError)
|
|
38
|
+
entry.state = IDLE
|
|
39
|
+
worker.on('message', (msg) => this._onMessage(entry, msg))
|
|
40
|
+
worker.on('error', (err) => this._onError(entry, err))
|
|
41
|
+
worker.on('exit', (code) => this._onExit(entry, code))
|
|
42
|
+
resolve(entry)
|
|
43
|
+
} else if (msg.error) {
|
|
44
|
+
worker.off('message', onReady)
|
|
45
|
+
worker.off('error', onError)
|
|
46
|
+
this.workers = this.workers.filter(e => e !== entry)
|
|
47
|
+
reject(new Error(msg.error))
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
const onError = (err) => {
|
|
52
|
+
worker.off('message', onReady)
|
|
53
|
+
this.workers = this.workers.filter(e => e !== entry)
|
|
54
|
+
reject(err)
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
worker.on('message', onReady)
|
|
58
|
+
worker.on('error', onError)
|
|
59
|
+
})
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* Handle messages from a worker (command responses).
|
|
64
|
+
*/
|
|
65
|
+
_onMessage(entry, { id, result, error }) {
|
|
66
|
+
const pending = entry.pending.get(id)
|
|
67
|
+
if (!pending) return
|
|
68
|
+
entry.pending.delete(id)
|
|
69
|
+
if (error) pending.reject(new Error(error))
|
|
70
|
+
else pending.resolve(result)
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
/**
|
|
74
|
+
* Handle worker errors.
|
|
75
|
+
*/
|
|
76
|
+
_onError(entry, err) {
|
|
77
|
+
LOG.error('Deployment worker error:', err.message)
|
|
78
|
+
// Reject all pending tasks on this worker
|
|
79
|
+
for (const [, { reject }] of entry.pending) {
|
|
80
|
+
reject(new Error(`Worker error: ${err.message}`))
|
|
81
|
+
}
|
|
82
|
+
entry.pending.clear()
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
/**
|
|
86
|
+
* Handle worker exit — reject in-flight tasks and replace the worker.
|
|
87
|
+
*/
|
|
88
|
+
_onExit(entry, code) {
|
|
89
|
+
this.workers = this.workers.filter(e => e !== entry)
|
|
90
|
+
|
|
91
|
+
// Reject any in-flight tasks
|
|
92
|
+
for (const [, { reject }] of entry.pending) {
|
|
93
|
+
reject(new Error(`Worker exited unexpectedly with code ${code}`))
|
|
94
|
+
}
|
|
95
|
+
entry.pending.clear()
|
|
96
|
+
|
|
97
|
+
// Do not replace workers during shutdown
|
|
98
|
+
if (this._shuttingDown) return
|
|
99
|
+
|
|
100
|
+
// Immediately replace the crashed worker
|
|
101
|
+
LOG.warn('Deployment worker exited with code', code, '- spawning replacement')
|
|
102
|
+
this._spawn().then(newEntry => {
|
|
103
|
+
// If there are queued requests waiting, serve them
|
|
104
|
+
if (this.waitQueue.length > 0 && newEntry.state === IDLE) {
|
|
105
|
+
newEntry.state = BUSY
|
|
106
|
+
const { resolve } = this.waitQueue.shift()
|
|
107
|
+
resolve(newEntry)
|
|
108
|
+
}
|
|
109
|
+
}).catch(err => {
|
|
110
|
+
LOG.error('Failed to spawn replacement worker:', err.message)
|
|
111
|
+
// If there are queued requests waiting, reject the first one
|
|
112
|
+
if (this.waitQueue.length > 0) {
|
|
113
|
+
const { reject } = this.waitQueue.shift()
|
|
114
|
+
reject(err)
|
|
115
|
+
}
|
|
116
|
+
})
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
/**
|
|
120
|
+
* Acquire an idle worker. If none available and pool not full, spawns a new one.
|
|
121
|
+
* If all workers are busy and pool is full, queues the request.
|
|
122
|
+
*/
|
|
123
|
+
async acquire() {
|
|
124
|
+
// Try to find an idle worker
|
|
125
|
+
const idle = this.workers.find(e => e.state === IDLE)
|
|
126
|
+
if (idle) {
|
|
127
|
+
idle.state = BUSY
|
|
128
|
+
return idle
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
// Pool not full — spawn a new worker
|
|
132
|
+
if (this.workers.length < this.size) {
|
|
133
|
+
const entry = await this._spawn()
|
|
134
|
+
entry.state = BUSY
|
|
135
|
+
return entry
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
// All busy — wait in queue
|
|
139
|
+
return new Promise((resolve, reject) => {
|
|
140
|
+
this.waitQueue.push({ resolve, reject })
|
|
141
|
+
})
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
/**
|
|
145
|
+
* Release a worker back to idle state. Serves the next queued request if any.
|
|
146
|
+
*/
|
|
147
|
+
release(entry) {
|
|
148
|
+
if (this.waitQueue.length > 0) {
|
|
149
|
+
entry.state = BUSY
|
|
150
|
+
const { resolve } = this.waitQueue.shift()
|
|
151
|
+
resolve(entry)
|
|
152
|
+
} else {
|
|
153
|
+
entry.state = IDLE
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
/**
|
|
158
|
+
* Execute a service action on a worker.
|
|
159
|
+
* Acquires a worker, sends the command, waits for the result, releases the worker.
|
|
160
|
+
*/
|
|
161
|
+
async exec(service, op, tenant, args) {
|
|
162
|
+
const entry = await this.acquire()
|
|
163
|
+
const id = ++this._idCounter
|
|
164
|
+
try {
|
|
165
|
+
const result = await new Promise((resolve, reject) => {
|
|
166
|
+
entry.pending.set(id, { resolve, reject })
|
|
167
|
+
entry.worker.postMessage({ id, service, op, tenant, args, cdsEnv: cds.env })
|
|
168
|
+
})
|
|
169
|
+
return result
|
|
170
|
+
} finally {
|
|
171
|
+
this.release(entry)
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
/**
|
|
176
|
+
* Shut down all workers. Sends a shutdown message to each worker and waits for them to exit.
|
|
177
|
+
*/
|
|
178
|
+
async shutdown() {
|
|
179
|
+
this._shuttingDown = true
|
|
180
|
+
|
|
181
|
+
// Reject all queued acquire requests
|
|
182
|
+
for (const { reject } of this.waitQueue) {
|
|
183
|
+
reject(new Error('Worker pool is shutting down'))
|
|
184
|
+
}
|
|
185
|
+
this.waitQueue = []
|
|
186
|
+
|
|
187
|
+
// Send shutdown message to all workers and wait for them to exit
|
|
188
|
+
await Promise.all(this.workers.map(entry => {
|
|
189
|
+
for (const [, { reject }] of entry.pending) {
|
|
190
|
+
reject(new Error('Worker pool is shutting down'))
|
|
191
|
+
}
|
|
192
|
+
entry.pending.clear()
|
|
193
|
+
entry.worker.postMessage({ shutdown: true })
|
|
194
|
+
return new Promise(resolve => entry.worker.once('exit', resolve))
|
|
195
|
+
}))
|
|
196
|
+
this.workers = []
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
let _pool
|
|
201
|
+
|
|
202
|
+
/**
|
|
203
|
+
* Returns the singleton DeploymentPool instance.
|
|
204
|
+
* @param {number} size - Pool size (number of workers)
|
|
205
|
+
*/
|
|
206
|
+
module.exports = function getDeploymentPool(size) {
|
|
207
|
+
if (!_pool) {
|
|
208
|
+
_pool = new DeploymentPool(size)
|
|
209
|
+
cds.on('shutdown', async () => _pool.shutdown())
|
|
210
|
+
}
|
|
211
|
+
return _pool
|
|
212
|
+
}
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
'use strict'
|
|
2
|
+
|
|
3
|
+
const { parentPort, workerData } = require('worker_threads')
|
|
4
|
+
const cds = require('@sap/cds')
|
|
5
|
+
const { _import, isfile, path } = cds.utils
|
|
6
|
+
|
|
7
|
+
if (workerData?.cdsRoot) cds.root = workerData.cdsRoot
|
|
8
|
+
if (workerData?.cdsEnv) cds.env.add(workerData.cdsEnv)
|
|
9
|
+
|
|
10
|
+
async function bootstrap() {
|
|
11
|
+
const services = [
|
|
12
|
+
'@sap/cds-mtxs/srv/deployment-service',
|
|
13
|
+
'@sap/cds-mtxs/srv/model-provider',
|
|
14
|
+
'@sap/cds-mtxs/srv/cf/saas-provisioning-service',
|
|
15
|
+
'@sap/cds-mtxs/srv/extensibility-service',
|
|
16
|
+
]
|
|
17
|
+
await _local_cli_js()
|
|
18
|
+
cds.emit('bootstrap')
|
|
19
|
+
const model = await cds.load([...services, '@sap/cds-mtxs/db/extensions'])
|
|
20
|
+
cds.model = cds.compile.for.nodejs(model)
|
|
21
|
+
const unserved = !cds.services['cds.xt.DeploymentService']
|
|
22
|
+
if (unserved) await cds.serve(services)
|
|
23
|
+
await cds.plugins
|
|
24
|
+
await cds.emit('served', cds.services)
|
|
25
|
+
parentPort.postMessage({ ready: true })
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
async function _local_cli_js() {
|
|
29
|
+
const _local = file => isfile(file) || isfile(path.join(cds.env.folders.srv, file))
|
|
30
|
+
const cli_js = process.env.CDS_TYPESCRIPT && (_local('cli.ts') || _local('server.ts')) || _local('cli.js') || _local('server.js')
|
|
31
|
+
if (cli_js) await _import(cli_js)
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
parentPort.on('message', async (msg) => {
|
|
35
|
+
if (msg.shutdown) {
|
|
36
|
+
await cds.shutdown()
|
|
37
|
+
process.exit(0)
|
|
38
|
+
}
|
|
39
|
+
const { id, service, op, tenant, args, cdsEnv } = msg
|
|
40
|
+
if (cdsEnv) cds.env.add(cdsEnv)
|
|
41
|
+
const srv = cds.services[service]
|
|
42
|
+
if (!srv) {
|
|
43
|
+
parentPort.postMessage({ id, error: `Service ${service} not available` })
|
|
44
|
+
return
|
|
45
|
+
}
|
|
46
|
+
try {
|
|
47
|
+
const result = await srv[op](tenant, ...Object.values(args))
|
|
48
|
+
parentPort.postMessage({ id, result })
|
|
49
|
+
} catch (e) {
|
|
50
|
+
const error = e.message ?? 'Unknown error'
|
|
51
|
+
parentPort.postMessage({ id, error })
|
|
52
|
+
} finally {
|
|
53
|
+
// cds.db.disconnect(cds.env.requires.multitenancy.t0 ?? 't0')
|
|
54
|
+
// cds.db.disconnect(tenant)
|
|
55
|
+
}
|
|
56
|
+
})
|
|
57
|
+
|
|
58
|
+
bootstrap().catch(e => {
|
|
59
|
+
parentPort.postMessage({ error: `Bootstrap failed: ${e.message}` })
|
|
60
|
+
process.exit(1)
|
|
61
|
+
})
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@sap/cds-dk",
|
|
3
|
-
"version": "10.1.
|
|
3
|
+
"version": "10.1.1",
|
|
4
4
|
"description": "Command line client and development toolkit for the SAP Cloud Application Programming Model",
|
|
5
5
|
"homepage": "https://cap.cloud.sap/",
|
|
6
6
|
"keywords": [
|
|
@@ -21,7 +21,7 @@
|
|
|
21
21
|
"@cap-js/openapi": "^1.0.0",
|
|
22
22
|
"@cap-js/sqlite": "^3",
|
|
23
23
|
"@sap/cds": "^10",
|
|
24
|
-
"@sap/cds-mtxs": "4
|
|
24
|
+
"@sap/cds-mtxs": "^4",
|
|
25
25
|
"@sap/hdi-deploy": "^5",
|
|
26
26
|
"express": "^5",
|
|
27
27
|
"hdb": "^2.0.0",
|
|
File without changes
|
|
File without changes
|