@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.
Files changed (75) hide show
  1. package/CHANGELOG.md +31 -1
  2. package/lib/auth/ias-auth.js +5 -3
  3. package/lib/auth/jwt-auth.js +4 -2
  4. package/lib/compile/cdsc.js +0 -10
  5. package/lib/compile/for/java.js +9 -5
  6. package/lib/compile/for/lean_drafts.js +1 -1
  7. package/lib/compile/to/edm.js +2 -1
  8. package/lib/compile/to/sql.js +0 -21
  9. package/lib/compile/to/srvinfo.js +13 -4
  10. package/lib/dbs/cds-deploy.js +7 -7
  11. package/lib/env/cds-requires.js +6 -0
  12. package/lib/index.js +4 -3
  13. package/lib/linked/classes.js +151 -88
  14. package/lib/linked/entities.js +28 -23
  15. package/lib/linked/models.js +57 -36
  16. package/lib/linked/types.js +42 -104
  17. package/lib/ql/Whereable.js +3 -3
  18. package/lib/req/context.js +9 -5
  19. package/lib/srv/protocols/hcql.js +2 -1
  20. package/lib/srv/protocols/http.js +7 -7
  21. package/lib/srv/protocols/index.js +31 -13
  22. package/lib/srv/protocols/odata-v4.js +79 -58
  23. package/lib/srv/srv-api.js +7 -6
  24. package/lib/srv/srv-dispatch.js +1 -12
  25. package/lib/srv/srv-tx.js +9 -13
  26. package/lib/utils/cds-utils.js +6 -5
  27. package/libx/_runtime/cds-services/adapter/odata-v4/ODataRequest.js +11 -8
  28. package/libx/_runtime/cds-services/adapter/odata-v4/handlers/read.js +21 -12
  29. package/libx/_runtime/cds-services/adapter/odata-v4/to.js +5 -3
  30. package/libx/_runtime/cds-services/adapter/odata-v4/utils/handlerUtils.js +3 -7
  31. package/libx/_runtime/cds-services/adapter/odata-v4/utils/metaInfo.js +5 -0
  32. package/libx/_runtime/cds-services/adapter/odata-v4/utils/readAfterWrite.js +2 -1
  33. package/libx/_runtime/cds-services/adapter/odata-v4/utils/request.js +3 -7
  34. package/libx/_runtime/cds-services/services/utils/columns.js +6 -3
  35. package/libx/_runtime/cds.js +0 -13
  36. package/libx/_runtime/common/generic/input.js +3 -0
  37. package/libx/_runtime/common/generic/sorting.js +8 -6
  38. package/libx/_runtime/common/i18n/messages.properties +1 -0
  39. package/libx/_runtime/common/utils/cqn.js +5 -0
  40. package/libx/_runtime/common/utils/foreignKeyPropagations.js +7 -1
  41. package/libx/_runtime/common/utils/keys.js +2 -2
  42. package/libx/_runtime/common/utils/resolveView.js +2 -1
  43. package/libx/_runtime/common/utils/rewriteAsterisks.js +1 -1
  44. package/libx/_runtime/common/utils/stream.js +0 -10
  45. package/libx/_runtime/common/utils/template.js +20 -35
  46. package/libx/_runtime/db/Service.js +5 -1
  47. package/libx/_runtime/db/utils/columns.js +1 -1
  48. package/libx/_runtime/fiori/lean-draft.js +14 -2
  49. package/libx/_runtime/messaging/Outbox.js +7 -5
  50. package/libx/_runtime/messaging/kafka.js +266 -0
  51. package/libx/_runtime/messaging/service.js +7 -5
  52. package/libx/_runtime/sqlite/convertDraftAdminPathExpression.js +1 -0
  53. package/libx/common/assert/validation.js +1 -1
  54. package/libx/odata/index.js +8 -2
  55. package/libx/odata/middleware/batch.js +340 -0
  56. package/libx/odata/middleware/create.js +43 -46
  57. package/libx/odata/middleware/delete.js +27 -15
  58. package/libx/odata/middleware/error.js +6 -5
  59. package/libx/odata/middleware/metadata.js +16 -15
  60. package/libx/odata/middleware/operation.js +107 -59
  61. package/libx/odata/middleware/parse.js +15 -7
  62. package/libx/odata/middleware/read.js +150 -24
  63. package/libx/odata/middleware/service-document.js +17 -6
  64. package/libx/odata/middleware/stream.js +34 -17
  65. package/libx/odata/middleware/update.js +123 -87
  66. package/libx/odata/parse/afterburner.js +131 -28
  67. package/libx/odata/parse/cqn2odata.js +1 -1
  68. package/libx/odata/parse/grammar.peggy +4 -5
  69. package/libx/odata/parse/multipartToJson.js +163 -0
  70. package/libx/odata/parse/parser.js +1 -1
  71. package/libx/odata/utils/index.js +29 -47
  72. package/libx/odata/utils/path.js +72 -0
  73. package/libx/odata/utils/result.js +123 -20
  74. package/package.json +1 -1
  75. 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
 
@@ -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._logDeprecation('req.tokenInfo was added for compatibility purposes but will be removed in the next major version.')
60
- return tokenInfo
61
- }
62
+ return cds.utils.deprecated(_getTokenInfo, {kind: 'Property', old: 'req.tokenInfo'})(tokenInfo)
63
+ }
62
64
  })
63
65
 
64
66
  if (!securityContext) {
@@ -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._logDeprecation('req.tokenInfo was added for compatibility purposes but will be removed in the next major version.')
60
- return tokenInfo
62
+ return cds.utils.deprecated(_getTokenInfo, {kind: 'Property', old: 'req.tokenInfo'})(tokenInfo)
61
63
  }
62
64
  })
63
65
 
@@ -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') }
@@ -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
- const { addTenantFields } = require ('@sap/cds-compiler/lib/transform/addTenantFields');
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
- o = compile._options.for.odata(o); // get compiler options, see compile.for.odata
13
- if (o.tenantDiscriminator) addTenantFields (dsn, o);
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.parent = draft
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 (
@@ -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(srv => srv._serves_odata).map(d => d.name) }
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)
@@ -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: _url4 (_javaPath(service)),
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: _url4 (cds.service.protocols.path4(service)),
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
  }
@@ -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 tha same csn
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 (!csn.definitions[entity]?.keys?.ID_texts) return // it's not a .texts entity with ID_texts key
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 (csn.definitions[entity.slice(0,-6)].keys) .concat ('locale')
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))
@@ -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.Service.prototype) .tx (..._) }
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
  }
@@ -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
- is (kind) { return this.kind === kind || kind === 'any' }
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
- dataIn (d, prefix='') {
24
- return d[prefix + this.name]
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
- class service extends context {
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 array extends type { is(kind) { return kind === 'array' || super.is(kind) }}
43
- class aspect extends type { is(kind) { return kind === 'aspect' || super.is(kind) }}
44
- class event extends aspect{}
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 struct extends aspect {
47
- is(kind) { return kind === 'struct' || super.is(kind) }
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
- * Gets the foreign key data for a given managed association from inbound data
51
- * in structured form.
52
- *
53
- * @example
54
- * let { Books } = srv.entities
55
- * let { author } = Books.elements
56
- * let book = { // inbound data, e.g. from req.data
57
- * title: 'Foo',
58
- * author_ID: 111
59
- * }
60
- * let value = author.dataIn(book)
61
- * //> { ID: 111 }
62
- *
63
- * Actually this works for all struct-like elements, i.e., which's definitions
64
- * have .elements or .foreignKeys. Could be added to cds.struct/cds.Association.
65
- */
66
- dataIn (d, prefix='', _skip_root) {
67
- const key = prefix + this.name; if (!_skip_root && key in d) return d[key]
68
- const elements = this.elements || this.foreignKeys // REVISIT: .foreignKeys should be .elements
69
- const nested={}, key_ = _skip_root ? '' : key+'_'
70
- let any; for (let e in elements) {
71
- const v = elements[e] .dataIn (d,key_)
72
- if (v !== undefined) nested[any=e] = v
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
- * Returns a Proxy for provided data which adds getters to return values
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
- // proxy calls def.dataIn() for defined elements, fallback hull[p]
100
- const {elements} = this, proxy = new Proxy (d, {
101
- get: (_,p) => elements[p]?.dataIn(_hull) || _hull[p],
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
- // cache proxy with original data
105
- Object.defineProperty (d, _proxy, {value:proxy})
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
- return proxy
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
- any,
120
- type,
121
- array,
122
- aspect,
123
- struct,
178
+ LinkedDefinitions,
179
+
180
+ any, type, aspect, struct, array,
181
+ scalar, boolean, string, number, date,
182
+ service, event, action,
124
183
  context,
125
- service,
126
- action,
127
- event,
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