@sap/cds 7.7.3 → 7.8.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 +31 -1
- package/lib/auth/ias-auth.js +5 -3
- package/lib/auth/jwt-auth.js +4 -2
- package/lib/compile/cdsc.js +0 -10
- package/lib/compile/for/java.js +9 -5
- package/lib/compile/for/lean_drafts.js +1 -1
- package/lib/compile/to/edm.js +2 -1
- package/lib/compile/to/sql.js +0 -21
- package/lib/compile/to/srvinfo.js +13 -4
- package/lib/dbs/cds-deploy.js +7 -7
- package/lib/env/cds-requires.js +6 -0
- package/lib/index.js +4 -3
- package/lib/linked/classes.js +151 -88
- package/lib/linked/entities.js +28 -23
- package/lib/linked/models.js +57 -36
- package/lib/linked/types.js +42 -104
- package/lib/ql/Whereable.js +3 -3
- package/lib/req/context.js +9 -5
- package/lib/srv/protocols/hcql.js +2 -1
- package/lib/srv/protocols/http.js +7 -7
- package/lib/srv/protocols/index.js +31 -13
- package/lib/srv/protocols/odata-v4.js +79 -58
- package/lib/srv/srv-api.js +7 -6
- package/lib/srv/srv-dispatch.js +1 -12
- package/lib/srv/srv-tx.js +9 -13
- package/lib/utils/cds-utils.js +6 -5
- package/libx/_runtime/cds-services/adapter/odata-v4/ODataRequest.js +11 -8
- package/libx/_runtime/cds-services/adapter/odata-v4/handlers/read.js +21 -12
- package/libx/_runtime/cds-services/adapter/odata-v4/to.js +5 -3
- package/libx/_runtime/cds-services/adapter/odata-v4/utils/handlerUtils.js +3 -7
- package/libx/_runtime/cds-services/adapter/odata-v4/utils/metaInfo.js +5 -0
- package/libx/_runtime/cds-services/adapter/odata-v4/utils/readAfterWrite.js +2 -1
- package/libx/_runtime/cds-services/adapter/odata-v4/utils/request.js +3 -7
- package/libx/_runtime/cds-services/services/utils/columns.js +6 -3
- package/libx/_runtime/cds.js +0 -13
- package/libx/_runtime/common/generic/input.js +3 -0
- package/libx/_runtime/common/generic/sorting.js +8 -6
- package/libx/_runtime/common/i18n/messages.properties +1 -0
- package/libx/_runtime/common/utils/cqn.js +5 -0
- package/libx/_runtime/common/utils/foreignKeyPropagations.js +7 -1
- package/libx/_runtime/common/utils/keys.js +2 -2
- package/libx/_runtime/common/utils/resolveView.js +2 -1
- package/libx/_runtime/common/utils/rewriteAsterisks.js +1 -1
- package/libx/_runtime/common/utils/stream.js +0 -10
- package/libx/_runtime/common/utils/template.js +20 -35
- package/libx/_runtime/db/Service.js +5 -1
- package/libx/_runtime/db/utils/columns.js +1 -1
- package/libx/_runtime/fiori/lean-draft.js +14 -2
- package/libx/_runtime/messaging/Outbox.js +7 -5
- package/libx/_runtime/messaging/kafka.js +266 -0
- package/libx/_runtime/messaging/service.js +7 -5
- package/libx/_runtime/sqlite/convertDraftAdminPathExpression.js +1 -0
- package/libx/common/assert/validation.js +1 -1
- package/libx/odata/index.js +8 -2
- package/libx/odata/middleware/batch.js +340 -0
- package/libx/odata/middleware/create.js +43 -46
- package/libx/odata/middleware/delete.js +27 -15
- package/libx/odata/middleware/error.js +6 -5
- package/libx/odata/middleware/metadata.js +16 -15
- package/libx/odata/middleware/operation.js +107 -59
- package/libx/odata/middleware/parse.js +15 -7
- package/libx/odata/middleware/read.js +150 -24
- package/libx/odata/middleware/service-document.js +17 -6
- package/libx/odata/middleware/stream.js +34 -17
- package/libx/odata/middleware/update.js +123 -87
- package/libx/odata/parse/afterburner.js +131 -28
- package/libx/odata/parse/cqn2odata.js +1 -1
- package/libx/odata/parse/grammar.peggy +4 -5
- package/libx/odata/parse/multipartToJson.js +163 -0
- package/libx/odata/parse/parser.js +1 -1
- package/libx/odata/utils/index.js +29 -47
- package/libx/odata/utils/path.js +72 -0
- package/libx/odata/utils/result.js +123 -20
- package/package.json +1 -1
- package/server.js +4 -0
package/CHANGELOG.md
CHANGED
|
@@ -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
|
+
## Version 7.8.1 - 2024-04-11
|
|
8
|
+
|
|
9
|
+
### Fixed
|
|
10
|
+
|
|
11
|
+
- In some cases, `<entity>.drafts` erroneously pointed to a CSN entity stub.
|
|
12
|
+
- Feature vectors including falsy values like `{ ft1: true, ft2: true, ft3: false }`
|
|
13
|
+
|
|
14
|
+
## Version 7.8.0 - 2024-03-25
|
|
15
|
+
|
|
16
|
+
### Added
|
|
17
|
+
|
|
18
|
+
- Health check endpoint `/health` in default server
|
|
19
|
+
- Class `cds.service` now provides getters for `entities`, `types`, `events` and `operations`. These return iterable objects, which can be used in `for...of` loops.
|
|
20
|
+
- Class `cds.entity` getters for `keys`, `associations`, `operations` also return `Iterable` objects now
|
|
21
|
+
- Method `compile.to.serviceinfo()` now lists all Node.js service endpoints in cases where multiple protocols are configured. For Java, the list is still limited to the first endpoint. This will be fixed in a future release.
|
|
22
|
+
- More warnings for deprecated features, functions and annotations.
|
|
23
|
+
|
|
24
|
+
### Fixed
|
|
25
|
+
|
|
26
|
+
- Reverted `cds.Association` being derived from `cds.struct`; it's now derived from `cds.type` again.
|
|
27
|
+
- Entity definitions using joins were erroneously marked as `_unresolved`
|
|
28
|
+
- Consistent error messages for query options validation with new parser
|
|
29
|
+
- Validation for mandatory associations which target entities with defaulted keys
|
|
30
|
+
- Transaction handling for aborted streaming requests
|
|
31
|
+
- Create/Update over filtered managed compositions
|
|
32
|
+
- Templates are cached at the model (instead of the service)
|
|
33
|
+
- Deprecation warnings use `cds.log()` in production
|
|
34
|
+
- Single quote in a string in `.where` for remote service
|
|
35
|
+
- Escaped characters in double quoted search term when using `odata_new_parser`
|
|
36
|
+
|
|
7
37
|
## Version 7.7.3 - 2024-03-18
|
|
8
38
|
|
|
9
39
|
### Fixed
|
|
@@ -42,7 +72,7 @@
|
|
|
42
72
|
- `cds.fiori.draft_lock_timeout` as successor of `cds.drafts.cancellationTimeout`.
|
|
43
73
|
+ Possible values are /^([0-9]+)(h|hrs|min)$/ or a number in milliseconds.
|
|
44
74
|
- There is a new `sap.common.Timezones` entity with a basic time zone definition. There will be accompanying data in package `@sap/cds-common-content`.
|
|
45
|
-
- Deprecation warnings for configuration options `cds.drafts.cancellationTimeout`, `cds.features.serve_on_root`, `cds.features.stream_compat`, `cds.fiori.lean_draft` and `cds.requires.middlewares`, as well as for the properties `req.user.locale` and `req.user.tenant`. The deprecation warnings can be turned off by setting `cds.features.deprecated` to `off`.
|
|
75
|
+
- Deprecation warnings for configuration options `cds.drafts.cancellationTimeout`, `cds.features.serve_on_root`, `cds.features.stream_compat`, `cds.fiori.lean_draft` and `cds.requires.middlewares`, as well as for the properties `req.user.locale` and `req.user.tenant`. The deprecation warnings can be turned off by setting `cds.features.deprecated` to `off`.
|
|
46
76
|
|
|
47
77
|
### Changed
|
|
48
78
|
|
package/lib/auth/ias-auth.js
CHANGED
|
@@ -5,6 +5,9 @@ const LOG = cds.log('auth')
|
|
|
5
5
|
const _require = require('../../libx/_runtime/common/utils/require')
|
|
6
6
|
const xssec = _require('@sap/xssec')
|
|
7
7
|
|
|
8
|
+
// getter function extracted to show deprecation warning only once
|
|
9
|
+
const _getTokenInfo = tokenInfo => tokenInfo
|
|
10
|
+
|
|
8
11
|
module.exports = function ias_auth(config) {
|
|
9
12
|
const { kind, credentials, known_claims } = config
|
|
10
13
|
|
|
@@ -56,9 +59,8 @@ module.exports = function ias_auth(config) {
|
|
|
56
59
|
// -> the "always available" part is not true for ias (see REVISIT above)
|
|
57
60
|
tokenInfo && Object.defineProperty(req, 'tokenInfo', {
|
|
58
61
|
get() {
|
|
59
|
-
cds.
|
|
60
|
-
|
|
61
|
-
}
|
|
62
|
+
return cds.utils.deprecated(_getTokenInfo, {kind: 'Property', old: 'req.tokenInfo'})(tokenInfo)
|
|
63
|
+
}
|
|
62
64
|
})
|
|
63
65
|
|
|
64
66
|
if (!securityContext) {
|
package/lib/auth/jwt-auth.js
CHANGED
|
@@ -5,6 +5,9 @@ const LOG = cds.log('auth')
|
|
|
5
5
|
const _require = require('../../libx/_runtime/common/utils/require')
|
|
6
6
|
const xssec = _require('@sap/xssec')
|
|
7
7
|
|
|
8
|
+
// getter function extracted to show deprecation warning only once
|
|
9
|
+
const _getTokenInfo = tokenInfo => tokenInfo
|
|
10
|
+
|
|
8
11
|
module.exports = function jwt_auth(config) {
|
|
9
12
|
const { kind, credentials } = config
|
|
10
13
|
|
|
@@ -56,8 +59,7 @@ module.exports = function jwt_auth(config) {
|
|
|
56
59
|
// if no general problem, tokenInfo object is always available -> add to req via getter for compat reasons
|
|
57
60
|
Object.defineProperty(req, 'tokenInfo', {
|
|
58
61
|
get() {
|
|
59
|
-
cds.
|
|
60
|
-
return tokenInfo
|
|
62
|
+
return cds.utils.deprecated(_getTokenInfo, {kind: 'Property', old: 'req.tokenInfo'})(tokenInfo)
|
|
61
63
|
}
|
|
62
64
|
})
|
|
63
65
|
|
package/lib/compile/cdsc.js
CHANGED
|
@@ -100,16 +100,6 @@ const _options = {for: Object.assign (_options4, {
|
|
|
100
100
|
})}
|
|
101
101
|
|
|
102
102
|
|
|
103
|
-
const { inspect } = require('util')
|
|
104
|
-
compile.CompilationError.prototype [inspect.custom] = function() {
|
|
105
|
-
// return this.stack
|
|
106
|
-
return 'Errors by cds.compile ...'+ this.messages.map (e => {
|
|
107
|
-
let {file,line,col} = e.$location
|
|
108
|
-
return `\nin ${file}:${line}:${col} — ${e.severity}: ${e.message}`
|
|
109
|
-
}).join('') + this.stack.slice(this.message.length+7)
|
|
110
|
-
}
|
|
111
|
-
|
|
112
|
-
|
|
113
103
|
/**
|
|
114
104
|
* Return a derivate of cdsc, with the most prominent
|
|
115
105
|
* @type { import('@sap/cds-compiler') }
|
package/lib/compile/for/java.js
CHANGED
|
@@ -2,15 +2,19 @@
|
|
|
2
2
|
|
|
3
3
|
const cds = require ('../../index')
|
|
4
4
|
|
|
5
|
-
// TODO: compiler functions to clarify - publish as individual API functions or
|
|
6
|
-
// have a compiler API function for.java?
|
|
7
5
|
function _4java (csn,o) {
|
|
8
6
|
const compile = require ('../cdsc');
|
|
9
|
-
|
|
7
|
+
o = compile._options.for.odata(o); // get compiler options, see compile.for.odata
|
|
8
|
+
return (compile.for.java ?? _4java_tmp) (csn,o);
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
function _4java_tmp (csn,o) { // as long as compile.for.java is not definitely there
|
|
10
12
|
const _4draft = require ('@sap/cds-compiler/lib/transform/draft/odata');
|
|
11
13
|
const dsn = JSON.parse (JSON.stringify (csn)) // REVISIT: workaround for bad test setup
|
|
12
|
-
|
|
13
|
-
|
|
14
|
+
if (o.tenantDiscriminator) {
|
|
15
|
+
const { addTenantFields } = require ('@sap/cds-compiler/lib/transform/addTenantFields');
|
|
16
|
+
addTenantFields (dsn, o);
|
|
17
|
+
}
|
|
14
18
|
return _4draft (dsn, o);
|
|
15
19
|
}
|
|
16
20
|
|
|
@@ -118,7 +118,7 @@ module.exports = function cds_compile_for_lean_drafts(csn) {
|
|
|
118
118
|
if (e._target['@odata.draft.enabled'] === false) continue // happens for texts if @fiori.draft.enabled is not set
|
|
119
119
|
_redirect(newEl, addDraftEntity(e._target, model))
|
|
120
120
|
}
|
|
121
|
-
newEl
|
|
121
|
+
Object.defineProperty (newEl,'parent',{value:draft,enumerable:false, configurable: true, writable: true})
|
|
122
122
|
|
|
123
123
|
for (const key in newEl) {
|
|
124
124
|
if (
|
package/lib/compile/to/edm.js
CHANGED
|
@@ -7,13 +7,14 @@ if (cds.env.features.precompile_edms !== false) {
|
|
|
7
7
|
cdsc.to.edm = Object.assign((csn, o) => {
|
|
8
8
|
if (o.to === 'openapi') return to_edm(csn, o)
|
|
9
9
|
if (!_precompiled.has(csn)) {
|
|
10
|
-
if (!o.serviceNames) o = { ...o, serviceNames: cds.linked(csn).services.filter(
|
|
10
|
+
if (!o.serviceNames) o = { ...o, serviceNames: cds.linked(csn).services.filter(d => 'odata' in d.protocols).map(d => d.name) }
|
|
11
11
|
_precompiled.set(csn, cdsc.to.edm.all(csn, o))
|
|
12
12
|
}
|
|
13
13
|
return _precompiled.get(csn)[o.service]
|
|
14
14
|
}, { all: cdsc.to.edm.all })
|
|
15
15
|
}
|
|
16
16
|
|
|
17
|
+
|
|
17
18
|
function cds_compile_to_edm (csn,_o) {
|
|
18
19
|
const o = cdsc._options.for.edm(_o) //> used twice below...
|
|
19
20
|
csn = _4odata(csn,o)
|
package/lib/compile/to/sql.js
CHANGED
|
@@ -7,7 +7,6 @@ const TRACE = cds.debug('trace')
|
|
|
7
7
|
function cds_compile_to_sql (csn,_o) {
|
|
8
8
|
TRACE?.time('cdsc.compile 2sql'.padEnd(22))
|
|
9
9
|
csn = cds.minify(csn)
|
|
10
|
-
csn = _extended(csn)
|
|
11
10
|
const o = cdsc._options.for.sql(_o) //> used twice below...
|
|
12
11
|
const all = cdsc.to.sql(csn,o) .map (each => each.replace(/^-- .+\n/,'')) //> strip comments
|
|
13
12
|
const sql = unfold_ddl(all, csn, o)
|
|
@@ -56,23 +55,3 @@ module.exports = Object.assign (cds_compile_to_sql, {
|
|
|
56
55
|
delta: cds_compile_to_deltaSql,
|
|
57
56
|
sqlite: { keywords },
|
|
58
57
|
})
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
/////////////////////////////////////////////////////////////////////////////
|
|
64
|
-
// UI Flex - read extensions__ to views, when ext fields are read
|
|
65
|
-
// REVISIT: We planned to remove the uiflex feature -> should do so
|
|
66
|
-
const _extended = (csn) => {
|
|
67
|
-
const defs = cds.linked(csn).definitions
|
|
68
|
-
for (let each in defs) {
|
|
69
|
-
const d = defs[each], columns = d.query?.SELECT?.columns || d.projection?.columns
|
|
70
|
-
if (columns && _is_extensible(d)) {
|
|
71
|
-
if (!columns.some(({ref}) => ref?.[0] === _extensions)) columns.push({ref:[_extensions]})
|
|
72
|
-
}
|
|
73
|
-
}
|
|
74
|
-
return csn
|
|
75
|
-
}
|
|
76
|
-
const _is_extensible = d => _extensions in d.elements || d.__proto__.elements && _is_extensible (d.__proto__)
|
|
77
|
-
const _extensions = 'extensions__'
|
|
78
|
-
/////////////////////////////////////////////////////////////////////////////
|
|
@@ -29,21 +29,30 @@ module.exports = (model, options={}) => {
|
|
|
29
29
|
return result
|
|
30
30
|
|
|
31
31
|
function _makeJava(service) {
|
|
32
|
+
// use first endpoint, preferably odata
|
|
33
|
+
// TODO handle multiple protocols for java, see https://cap.cloud.sap/docs/java/application-services#serve-configuration
|
|
34
|
+
const kind = Object.keys(service.protocols).find(k => k.startsWith('odata')) || Object.keys(service.protocols)[0]
|
|
35
|
+
const endpoints = [{ kind, path: _url4(_javaPath(service)) }]
|
|
32
36
|
return {
|
|
33
37
|
name: service.name,
|
|
34
|
-
urlPath:
|
|
38
|
+
urlPath: endpoints[0].path, // legacy
|
|
35
39
|
destination: 'srv-api', // the name to register in xs-app.json
|
|
40
|
+
endpoints,
|
|
36
41
|
runtime: 'Java',
|
|
37
42
|
location: service.$location
|
|
38
43
|
}
|
|
39
44
|
}
|
|
40
45
|
function _makeNode(service) {
|
|
46
|
+
// make a fake runtime object for the service, adding a `definition` property
|
|
47
|
+
if (!service.definition) Object.defineProperty(service, 'definition', { value: service, enumerable: false })
|
|
48
|
+
const endpoints = cds.service.protocols.endpoints4(service).map(e => Object.assign({}, e, { path: _url4(e.path) }))
|
|
41
49
|
return {
|
|
42
50
|
name: service.name,
|
|
43
|
-
urlPath:
|
|
51
|
+
urlPath: endpoints[0].path, // legacy
|
|
44
52
|
destination: 'srv-api', // the name to register in xs-app.json
|
|
53
|
+
endpoints,
|
|
45
54
|
runtime: 'Node.js',
|
|
46
|
-
location: service.$location
|
|
55
|
+
location: service.$location,
|
|
47
56
|
}
|
|
48
57
|
}
|
|
49
58
|
|
|
@@ -53,7 +62,7 @@ module.exports = (model, options={}) => {
|
|
|
53
62
|
p = p.replace(/\\/g, '/') // handle Windows
|
|
54
63
|
.replace(/^\/+/, '') // strip leading
|
|
55
64
|
.replace(/\/+$/, '') // strip trailing
|
|
56
|
-
p += '/' // end with /
|
|
65
|
+
if (!p.endsWith('/')) p += '/' // end with /
|
|
57
66
|
return p
|
|
58
67
|
}
|
|
59
68
|
}
|
package/lib/dbs/cds-deploy.js
CHANGED
|
@@ -149,12 +149,11 @@ deploy.schema = async function (db, csn = db.model, o) {
|
|
|
149
149
|
deploy.data = async function (db, csn = db.model, o, srces, log=()=>{}) {
|
|
150
150
|
|
|
151
151
|
const t = cds.context?.tenant; if (t && t === cds.requires.multitenancy?.t0) return
|
|
152
|
-
const crypto = require('crypto')
|
|
153
152
|
|
|
154
153
|
return db.run (async tx => {
|
|
155
154
|
TRACE?.time('cds.deploy data'.padEnd(22))
|
|
156
155
|
|
|
157
|
-
const m = tx.model = cds.compile.for.nodejs(csn) // NOTE: this used to create a redundant 4nodejs model for
|
|
156
|
+
const m = tx.model = cds.compile.for.nodejs(csn) // NOTE: this used to create a redundant 4nodejs model for the same csn
|
|
158
157
|
const data = await deploy.prepare (m,srces)
|
|
159
158
|
const query = _queries4 (db,m)
|
|
160
159
|
const INSERT_from = INSERT_from4 (db,m,o)
|
|
@@ -207,12 +206,12 @@ deploy.data = async function (db, csn = db.model, o, srces, log=()=>{}) {
|
|
|
207
206
|
return (file) => ({
|
|
208
207
|
'.json': { into (entity, json) {
|
|
209
208
|
let records = JSON.parse(json); if (!records.length) return
|
|
210
|
-
_add_ID_texts4 (entity, records)
|
|
209
|
+
_add_ID_texts4 (entity, m, records)
|
|
211
210
|
return INSERT_into(entity).entries(records)
|
|
212
211
|
}},
|
|
213
212
|
'.csv': { into (entity, csv) {
|
|
214
213
|
let [cols, ...rows] = cds.parse.csv(csv); if (!rows.length) return
|
|
215
|
-
_add_ID_texts4 (entity, rows, cols)
|
|
214
|
+
_add_ID_texts4 (entity, m, rows, cols)
|
|
216
215
|
return INSERT_into(entity).columns(cols).rows(rows)
|
|
217
216
|
}},
|
|
218
217
|
}) [path.extname(file)]
|
|
@@ -223,12 +222,13 @@ deploy.data = async function (db, csn = db.model, o, srces, log=()=>{}) {
|
|
|
223
222
|
* IMPORTANT: we use UUIDs generated from hashes of all original key values (ID, locale, ...)
|
|
224
223
|
* to ensure same ID_texts values for same keys across different deployments.
|
|
225
224
|
*/
|
|
226
|
-
function _add_ID_texts4 (entity, records, cols) {
|
|
225
|
+
function _add_ID_texts4 (entity, m, records, cols) {
|
|
227
226
|
if (entity.name) entity = entity.name //> entity can be an entity name or a definition
|
|
228
|
-
if (!
|
|
227
|
+
if (!m.definitions[entity]?.keys?.ID_texts) return // it's not a .texts entity with ID_texts key
|
|
229
228
|
if ((cols || Object.keys(records[0])).includes('ID_texts')) return // already there
|
|
230
229
|
else DEBUG?.(`adding ID_texts for ${entity}`)
|
|
231
|
-
const keys = Object.keys (
|
|
230
|
+
const keys = Object.keys (m.definitions[entity.slice(0,-6)].keys) .concat ('locale')
|
|
231
|
+
const crypto = require('crypto')
|
|
232
232
|
if (cols) {
|
|
233
233
|
cols.push ('ID_texts')
|
|
234
234
|
const indexes = keys.map (k => cols.indexOf(k))
|
package/lib/env/cds-requires.js
CHANGED
|
@@ -217,6 +217,12 @@ const _messaging = {
|
|
|
217
217
|
impl: `${_runtime}/messaging/message-queuing.js`,
|
|
218
218
|
outbox: true
|
|
219
219
|
},
|
|
220
|
+
'kafka': {
|
|
221
|
+
impl: `${_runtime}/messaging/kafka.js`,
|
|
222
|
+
topic: 'cds.default',
|
|
223
|
+
outbox: true,
|
|
224
|
+
local: false
|
|
225
|
+
},
|
|
220
226
|
"composite-messaging": {
|
|
221
227
|
impl: `${_runtime}/messaging/composite.js`
|
|
222
228
|
},
|
package/lib/index.js
CHANGED
|
@@ -103,9 +103,10 @@ const cds = module.exports = global.cds = new class cds extends EventEmitter {
|
|
|
103
103
|
exit(code){ return cds.shutdown ? cds.shutdown() : process.exit(code) }
|
|
104
104
|
|
|
105
105
|
// Querying and Databases
|
|
106
|
+
get txs() { return super.txs = new this.Service('cds.tx') }
|
|
106
107
|
get ql() { return super.ql = require('./ql/cds-ql') }
|
|
107
|
-
tx (..._) { return (this.db || this.
|
|
108
|
-
run (..._) { return (this.db || this.error._no_primary_db).run(..._) }
|
|
108
|
+
tx (..._) { return (this.db || this.txs).tx(..._) }
|
|
109
|
+
run (..._) { return (this.db || typeof _[0] === 'function' && this.txs || this.error._no_primary_db).run(..._) }
|
|
109
110
|
foreach (..._) { return (this.db || this.error._no_primary_db).foreach(..._) }
|
|
110
111
|
stream (..._) { return (this.db || this.error._no_primary_db).stream(..._) }
|
|
111
112
|
read (..._) { return (this.db || this.error._no_primary_db).read(..._) }
|
|
@@ -144,6 +145,6 @@ if (process.env.CDS_JEST_MEM_FIX && typeof jest !== 'undefined') require('./util
|
|
|
144
145
|
// Setting it to module.exports lead to issues with vitest while setting it to cds apparently works fine.
|
|
145
146
|
if (process.env.CDS_ESM_INTEROP_DEFAULT) {
|
|
146
147
|
Object.defineProperties(module.exports, { default: {value:module.exports}, __esModule: {value:true} })
|
|
147
|
-
} else {
|
|
148
|
+
} else {
|
|
148
149
|
Object.defineProperties(module.exports, { default: {value:cds}, __esModule: {value:true} })
|
|
149
150
|
}
|
package/lib/linked/classes.js
CHANGED
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
const { extend } = require('../lazy')
|
|
2
|
+
const _proxy = Symbol('_proxy')
|
|
2
3
|
|
|
3
|
-
class any {
|
|
4
|
+
class any { is (kind) { return this.kind === kind || kind === 'any' }
|
|
4
5
|
|
|
5
6
|
constructor(...aspects) { Object.assign (this,...aspects) }
|
|
6
7
|
set name(n) { this.set('name', n, false) }
|
|
7
8
|
set kind(k) { this.set('kind', k, true) }
|
|
8
9
|
get kind() { return this.set('kind', this.parent ? 'element' : 'type') }
|
|
9
|
-
|
|
10
|
-
valueOf() { return this.name }
|
|
10
|
+
valueOf() { return this.name || this }
|
|
11
11
|
|
|
12
12
|
own (property, ifAbsent) {
|
|
13
13
|
const pd = Reflect.getOwnPropertyDescriptor (this, property)
|
|
@@ -20,111 +20,174 @@ class any {
|
|
|
20
20
|
return value
|
|
21
21
|
}
|
|
22
22
|
|
|
23
|
-
|
|
24
|
-
|
|
23
|
+
toJSON() {
|
|
24
|
+
const o={}; for (let p in this) o[p] = this[p]
|
|
25
|
+
return o
|
|
25
26
|
}
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
class type extends any {}
|
|
29
|
-
class action extends any {}
|
|
30
|
-
class context extends any {}
|
|
31
27
|
|
|
32
|
-
|
|
33
|
-
/** @type <T> (p,v:T) => T */ static _lazy (p,v) { Reflect.defineProperty (this,p,{value:v}); return v }
|
|
34
|
-
static get protocols() { return this._lazy ('protocols', require('../srv/protocols')) }
|
|
35
|
-
static get bindings() { return this._lazy ('bindings', require('../srv/bindings')) }
|
|
36
|
-
static get factory() { return this._lazy ('factory', require('../srv/factory')) }
|
|
37
|
-
static endpoints4(..._) { return this.protocols.endpoints4(..._) }
|
|
38
|
-
static path4(..._) { return this.protocols.path4(..._) }
|
|
39
|
-
get _serves_odata() { return super._serves_odata = service.protocols._serves_odata(this) }
|
|
28
|
+
dataIn (d, prefix='') { return d[prefix + this.name] }
|
|
40
29
|
}
|
|
41
30
|
|
|
42
|
-
class
|
|
43
|
-
class
|
|
44
|
-
|
|
31
|
+
class aspect extends any { is(kind) { return kind === 'aspect' || super.is(kind) }}
|
|
32
|
+
class type extends any { is(kind) { return kind === 'type' || super.is(kind) }
|
|
33
|
+
toJSON() {
|
|
34
|
+
return this.own('type') ? {...this} : super.toJSON()
|
|
35
|
+
}
|
|
36
|
+
}
|
|
45
37
|
|
|
46
|
-
class
|
|
47
|
-
|
|
38
|
+
class scalar extends type {}
|
|
39
|
+
|
|
40
|
+
class boolean extends scalar {}
|
|
41
|
+
class Boolean extends boolean {}
|
|
42
|
+
|
|
43
|
+
class string extends scalar {}
|
|
44
|
+
class UUID extends string {}
|
|
45
|
+
class String extends string {}
|
|
46
|
+
class LargeString extends String {}
|
|
47
|
+
class Binary extends string {}
|
|
48
|
+
class LargeBinary extends Binary {}
|
|
49
|
+
class Vector extends Binary {}
|
|
50
|
+
|
|
51
|
+
class number extends scalar {}
|
|
52
|
+
class Integer extends number {}
|
|
53
|
+
class UInt8 extends Integer {}
|
|
54
|
+
class Int16 extends Integer {}
|
|
55
|
+
class Int32 extends Integer {}
|
|
56
|
+
class Int64 extends Integer {}
|
|
57
|
+
class Float extends number {}
|
|
58
|
+
class Double extends Float {}
|
|
59
|
+
class Decimal extends Float {}
|
|
60
|
+
|
|
61
|
+
class date extends scalar {}
|
|
62
|
+
class Date extends date {}
|
|
63
|
+
class Time extends date {}
|
|
64
|
+
class DateTime extends date {}
|
|
65
|
+
class Timestamp extends DateTime {}
|
|
66
|
+
|
|
67
|
+
class array extends type { is(kind) { return kind === 'array' || super.is(kind) }}
|
|
68
|
+
class struct extends type { is(kind) { return kind === 'struct' || super.is(kind) }
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
* Gets the foreign key data for a given managed association from inbound data
|
|
72
|
+
* in structured form.
|
|
73
|
+
*
|
|
74
|
+
* @example
|
|
75
|
+
* let { Books } = srv.entities
|
|
76
|
+
* let { author } = Books.elements
|
|
77
|
+
* let book = { // inbound data, e.g. from req.data
|
|
78
|
+
* title: 'Foo',
|
|
79
|
+
* author_ID: 111
|
|
80
|
+
* }
|
|
81
|
+
* let value = author.dataIn(book)
|
|
82
|
+
* //> { ID: 111 }
|
|
83
|
+
*
|
|
84
|
+
* Actually this works for all struct-like elements, i.e., which's definitions
|
|
85
|
+
* have .elements or .foreignKeys. Could be added to cds.struct/cds.Association.
|
|
86
|
+
*/
|
|
87
|
+
dataIn (d, prefix='', _skip_root) {
|
|
88
|
+
const key = prefix + this.name; if (!_skip_root && key in d) return d[key]
|
|
89
|
+
const elements = this.elements || this.foreignKeys
|
|
90
|
+
const nested={}, key_ = _skip_root ? '' : key+'_'
|
|
91
|
+
let any; for (let e in elements) {
|
|
92
|
+
const v = elements[e] .dataIn (d,key_)
|
|
93
|
+
if (v !== undefined) nested[any=e] = v
|
|
94
|
+
}
|
|
95
|
+
if (any) return !prefix && d._hull ? d._hull[key] = nested : nested
|
|
96
|
+
}
|
|
48
97
|
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
98
|
+
/**
|
|
99
|
+
* Returns a Proxy for provided data which adds getters to return values
|
|
100
|
+
* for struct elements (including Associations) in structured form.
|
|
101
|
+
*
|
|
102
|
+
* @example
|
|
103
|
+
* let { Books } = m.entities
|
|
104
|
+
* let data = Books.data ({
|
|
105
|
+
* author: {ID:111},
|
|
106
|
+
* genre_ID: 22
|
|
107
|
+
* })
|
|
108
|
+
* console.log ('author:', data.author) //> { ID: 111 }
|
|
109
|
+
* console.log ('genre:', data.genre) //> { ID: 22 }
|
|
110
|
+
*/
|
|
111
|
+
data (d) {
|
|
112
|
+
if (_proxy in d) return d[_proxy] //> use cached proxy, if exists
|
|
113
|
+
|
|
114
|
+
// hull to cache calculated values without polluting original input
|
|
115
|
+
const _hull = {__proto__:d}
|
|
116
|
+
|
|
117
|
+
// allow external code to access _hull
|
|
118
|
+
Object.defineProperty (_hull, '_hull', {value:_hull})
|
|
119
|
+
|
|
120
|
+
// proxy calls def.dataIn() for defined elements, fallback hull[p]
|
|
121
|
+
const {elements} = this, proxy = new Proxy (d, {
|
|
122
|
+
get: (_,p) => elements[p]?.dataIn?.(_hull) || _hull[p],
|
|
123
|
+
})
|
|
124
|
+
|
|
125
|
+
// cache proxy with original data
|
|
126
|
+
Object.defineProperty (d, _proxy, {value:proxy})
|
|
127
|
+
|
|
128
|
+
return proxy
|
|
73
129
|
}
|
|
74
|
-
if (any) return !prefix && d._hull ? d._hull[key] = nested : nested
|
|
75
130
|
}
|
|
76
131
|
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
* for struct elements (including Associations) in structured form.
|
|
80
|
-
*
|
|
81
|
-
* @example
|
|
82
|
-
* let { Books } = m.entities
|
|
83
|
-
* let data = Books.data ({
|
|
84
|
-
* author: {ID:111},
|
|
85
|
-
* genre_ID: 22
|
|
86
|
-
* })
|
|
87
|
-
* console.log ('author:', data.author) //> { ID: 111 }
|
|
88
|
-
* console.log ('genre:', data.genre) //> { ID: 22 }
|
|
89
|
-
*/
|
|
90
|
-
data (d) {
|
|
91
|
-
if (_proxy in d) return d[_proxy] //> use cached proxy, if exists
|
|
92
|
-
|
|
93
|
-
// hull to cache calculated values without polluting original input
|
|
94
|
-
const _hull = {__proto__:d}
|
|
95
|
-
|
|
96
|
-
// allow external code to access _hull
|
|
97
|
-
Object.defineProperty (_hull, '_hull', {value:_hull})
|
|
132
|
+
class context extends any {}
|
|
133
|
+
class service extends context {
|
|
98
134
|
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
}
|
|
135
|
+
get entities() { return this.set('entities', this._collect (d => d.kind === 'entity')) }
|
|
136
|
+
get types() { return this.set('types', this._collect (d => d.kind === 'type' || !d.kind)) }
|
|
137
|
+
get events() { return this.set('events', this._collect (d => d.kind === 'event')) }
|
|
138
|
+
get actions() { return this.set('actions', this._collect (d => d.kind === 'action' || d.kind === 'function')) }
|
|
139
|
+
get operations() { return this.set('actions', this._collect (d => d.kind === 'action' || d.kind === 'function')) }
|
|
140
|
+
|
|
141
|
+
/** @private */ _collect (filter) {
|
|
142
|
+
const defs = this.model?.definitions, prefix = this.name+'.', dict = new LinkedDefinitions
|
|
143
|
+
for (let each in defs) {
|
|
144
|
+
let d = defs[each]
|
|
145
|
+
if (d._service === this && filter(d)) dict[each.slice(prefix.length)] = d
|
|
146
|
+
}
|
|
147
|
+
return dict
|
|
148
|
+
}
|
|
103
149
|
|
|
104
|
-
|
|
105
|
-
|
|
150
|
+
get protocols() { return this.set('protocols', service.protocols.for(this)) }
|
|
151
|
+
static get protocols() { return this._lazy ('protocols', require('../srv/protocols')) }
|
|
152
|
+
static get bindings() { return this._lazy ('bindings', require('../srv/bindings')) }
|
|
153
|
+
static get factory() { return this._lazy ('factory', require('../srv/factory')) }
|
|
154
|
+
static endpoints4(..._) { return this.protocols.endpoints4(..._) }
|
|
155
|
+
static path4(..._) { return this.protocols.path4(..._) }
|
|
106
156
|
|
|
107
|
-
|
|
157
|
+
/** @private @type <T> (p,v:T) => T */ static _lazy (p,v) {
|
|
158
|
+
Reflect.defineProperty (this,p,{value:v})
|
|
159
|
+
return v
|
|
108
160
|
}
|
|
161
|
+
}
|
|
162
|
+
class action extends any {}
|
|
163
|
+
class event extends aspect {}
|
|
164
|
+
|
|
109
165
|
|
|
166
|
+
class LinkedDefinitions {
|
|
167
|
+
*[Symbol.iterator](){ for (let e in this) yield this[e] }
|
|
168
|
+
forEach(f){ let i=0; for (let k in this) f(this[k],i++,this) }
|
|
169
|
+
filter(f){ let i=0, r=[]; for (let k in this) f(this[k],i++,this) && r.push(k); return r }
|
|
170
|
+
map(f){ let i=0, r=[]; for (let k in this) r.push(f(this[k],i++,this)); return r }
|
|
171
|
+
some(f){ for (let k in this) if (f(this[k])) return true }
|
|
172
|
+
find(f){ for (let k in this) if (f(this[k])) return k }
|
|
110
173
|
}
|
|
111
|
-
const _proxy = Symbol('_proxy')
|
|
112
174
|
|
|
113
175
|
|
|
114
|
-
/**
|
|
115
|
-
* Export is a dictionary of all builtin classes
|
|
116
|
-
*/
|
|
117
176
|
module.exports = {
|
|
118
177
|
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
array,
|
|
122
|
-
|
|
123
|
-
|
|
178
|
+
LinkedDefinitions,
|
|
179
|
+
|
|
180
|
+
any, type, aspect, struct, array,
|
|
181
|
+
scalar, boolean, string, number, date,
|
|
182
|
+
service, event, action,
|
|
124
183
|
context,
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
184
|
+
|
|
185
|
+
UUID, Boolean, String,
|
|
186
|
+
Integer, UInt8, Int16, Int32, Int64,
|
|
187
|
+
|
|
188
|
+
Float, Double, Decimal,
|
|
189
|
+
Date, Time, DateTime, Timestamp,
|
|
190
|
+
Binary, Vector, LargeBinary, LargeString,
|
|
128
191
|
|
|
129
192
|
/**
|
|
130
193
|
* Allows to mixin functions or properties to several equally named builtin classes
|