@sap/cds 6.1.2 → 6.2.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 (212) hide show
  1. package/CHANGELOG.md +92 -8
  2. package/apis/cds.d.ts +18 -6
  3. package/apis/connect.d.ts +1 -1
  4. package/apis/cqn.d.ts +1 -1
  5. package/apis/log.d.ts +23 -5
  6. package/apis/ql.d.ts +128 -61
  7. package/apis/services.d.ts +11 -0
  8. package/apis/test.d.ts +61 -0
  9. package/apis/utils.d.ts +15 -0
  10. package/app/fiori/preview.js +1 -0
  11. package/bin/build/buildTaskEngine.js +70 -22
  12. package/bin/build/buildTaskFactory.js +18 -11
  13. package/bin/build/buildTaskHandler.js +1 -1
  14. package/bin/build/buildTaskProviderFactory.js +3 -13
  15. package/bin/build/constants.js +0 -1
  16. package/bin/build/index.js +14 -6
  17. package/bin/build/provider/buildTaskHandlerEdmx.js +2 -3
  18. package/bin/build/provider/buildTaskHandlerFeatureToggles.js +2 -2
  19. package/bin/build/provider/buildTaskHandlerInternal.js +3 -6
  20. package/bin/build/provider/buildTaskProviderInternal.js +51 -39
  21. package/bin/build/provider/fiori/index.js +3 -3
  22. package/bin/build/provider/hana/2migration.js +1 -1
  23. package/bin/build/provider/hana/index.js +34 -27
  24. package/bin/build/provider/java/index.js +6 -7
  25. package/bin/build/provider/mtx/index.js +20 -18
  26. package/bin/build/provider/mtx/resourcesTarBuilder.js +8 -11
  27. package/bin/build/provider/mtx-sidecar/index.js +13 -17
  28. package/bin/build/provider/nodejs/index.js +8 -7
  29. package/bin/build/util.js +22 -4
  30. package/bin/cds.js +8 -4
  31. package/bin/deploy/to-hana/cfUtil.js +53 -18
  32. package/bin/mtx/in-cds.js +1 -0
  33. package/bin/serve.js +37 -30
  34. package/lib/auth/basic-auth.js +33 -0
  35. package/lib/auth/dummy-auth.js +7 -0
  36. package/lib/auth/ias-auth.js +2 -0
  37. package/lib/auth/index.js +31 -0
  38. package/lib/auth/jwt-auth.js +3 -0
  39. package/lib/auth/mocked-users.js +72 -0
  40. package/lib/auth/passport-basic.js +12 -0
  41. package/lib/auth/passport-digest.js +14 -0
  42. package/lib/auth/xsuaa-auth.js +3 -0
  43. package/lib/compile/cds-compile.js +3 -3
  44. package/lib/compile/to/cdl.js +5 -1
  45. package/lib/compile/to/edm.js +8 -0
  46. package/lib/compile/to/gql.js +1 -0
  47. package/lib/compile/to/json.js +30 -5
  48. package/lib/compile/to/sql.js +3 -1
  49. package/lib/core/index.js +5 -1
  50. package/lib/dbs/cds-deploy.js +36 -6
  51. package/lib/env/cds-env.js +15 -5
  52. package/lib/env/cds-requires.js +51 -58
  53. package/lib/env/defaults.js +1 -0
  54. package/lib/env/schemas/cds-package.json +4 -0
  55. package/lib/env/schemas/cds-rc.json +63 -77
  56. package/lib/i18n/localize.js +16 -5
  57. package/lib/index.js +9 -4
  58. package/lib/log/cds-error.js +4 -6
  59. package/lib/log/cds-log.js +89 -53
  60. package/lib/log/service/index.js +1 -0
  61. package/lib/ql/CREATE.js +2 -5
  62. package/lib/ql/DELETE.js +1 -1
  63. package/lib/ql/DROP.js +1 -3
  64. package/lib/ql/INSERT.js +3 -3
  65. package/lib/ql/Query.js +10 -23
  66. package/lib/ql/SELECT.js +1 -2
  67. package/lib/ql/UPDATE.js +2 -2
  68. package/lib/ql/Whereable.js +7 -15
  69. package/lib/ql/cds-ql.js +9 -3
  70. package/lib/req/cds-context.js +11 -3
  71. package/lib/req/context.js +29 -23
  72. package/lib/req/locale.js +9 -5
  73. package/lib/req/request.js +1 -0
  74. package/lib/req/user.js +2 -1
  75. package/lib/srv/cds-connect.js +1 -1
  76. package/lib/srv/cds-serve.js +21 -14
  77. package/lib/srv/middlewares/cds-context.js +29 -0
  78. package/lib/srv/middlewares/ctx-model.js +24 -0
  79. package/lib/srv/middlewares/errors.js +9 -0
  80. package/lib/srv/middlewares/index.js +22 -0
  81. package/lib/srv/middlewares/sap-statistics.js +13 -0
  82. package/lib/srv/middlewares/trace.js +102 -0
  83. package/lib/srv/protocols/_legacy.js +42 -0
  84. package/lib/srv/protocols/graphql.js +39 -0
  85. package/lib/srv/protocols/hcql.js +37 -0
  86. package/lib/srv/protocols/index.js +86 -0
  87. package/lib/srv/protocols/odata-v2-proxy.js +3767 -0
  88. package/lib/srv/protocols/odata-v2.js +26 -0
  89. package/lib/srv/protocols/odata-v4.js +16 -0
  90. package/lib/srv/protocols/rest.js +13 -0
  91. package/lib/srv/srv-api.js +5 -0
  92. package/lib/srv/srv-models.js +4 -6
  93. package/lib/utils/axios.js +3 -2
  94. package/lib/utils/cds-test.js +27 -21
  95. package/lib/utils/cds-utils.js +19 -20
  96. package/lib/utils/tar.js +175 -0
  97. package/libx/_runtime/audit/generic/personal/utils.js +18 -7
  98. package/libx/_runtime/audit/utils/v2.js +1 -0
  99. package/libx/_runtime/auth/index.js +4 -0
  100. package/libx/_runtime/auth/strategies/ias-auth.js +76 -0
  101. package/libx/_runtime/cds-services/adapter/odata-v4/handlers/error.js +8 -3
  102. package/libx/_runtime/cds-services/adapter/odata-v4/handlers/request.js +15 -4
  103. package/libx/_runtime/cds-services/adapter/odata-v4/odata-to-cqn/expandToCQN.js +1 -1
  104. package/libx/_runtime/cds-services/adapter/odata-v4/odata-to-cqn/orderByToCQN.js +1 -1
  105. package/libx/_runtime/cds-services/adapter/odata-v4/odata-to-cqn/utils.js +1 -1
  106. package/libx/_runtime/cds-services/adapter/odata-v4/okra/odata-commons/uri/ResourcePathParser.js +9 -0
  107. package/libx/_runtime/cds-services/adapter/odata-v4/okra/odata-commons/uri/UriInfo.js +5 -1
  108. package/libx/_runtime/cds-services/adapter/odata-v4/okra/odata-commons/uri/UriParser.js +12 -0
  109. package/libx/_runtime/cds-services/adapter/odata-v4/okra/odata-server/utils/BufferedWriter.js +6 -2
  110. package/libx/_runtime/cds-services/adapter/odata-v4/okra/odata-server/validator/RequestValidator.js +47 -7
  111. package/libx/_runtime/cds-services/adapter/odata-v4/to.js +1 -1
  112. package/libx/_runtime/cds-services/adapter/odata-v4/utils/readAfterWrite.js +0 -2
  113. package/libx/_runtime/cds-services/services/utils/compareJson.js +3 -1
  114. package/libx/_runtime/cds-services/util/assert.js +7 -0
  115. package/libx/_runtime/common/aspects/relation.js +1 -1
  116. package/libx/_runtime/common/composition/data.js +61 -15
  117. package/libx/_runtime/common/composition/delete.js +0 -1
  118. package/libx/_runtime/common/composition/insert.js +0 -1
  119. package/libx/_runtime/common/composition/tree.js +4 -10
  120. package/libx/_runtime/common/composition/update.js +44 -21
  121. package/libx/_runtime/common/generic/auth/capabilities.js +8 -10
  122. package/libx/_runtime/common/generic/crud.js +1 -2
  123. package/libx/_runtime/common/generic/etag.js +4 -4
  124. package/libx/_runtime/common/generic/input.js +21 -6
  125. package/libx/_runtime/common/generic/paging.js +3 -3
  126. package/libx/_runtime/common/generic/put.js +7 -4
  127. package/libx/_runtime/common/generic/sorting.js +4 -4
  128. package/libx/_runtime/common/generic/temporal.js +3 -6
  129. package/libx/_runtime/common/i18n/messages.properties +0 -7
  130. package/libx/_runtime/common/utils/cqn2cqn4sql.js +11 -6
  131. package/libx/_runtime/common/utils/csn.js +0 -28
  132. package/libx/_runtime/common/utils/draft.js +8 -1
  133. package/libx/_runtime/common/utils/path.js +7 -1
  134. package/libx/_runtime/common/utils/propagateForeignKeys.js +122 -0
  135. package/libx/_runtime/common/utils/resolveView.js +2 -3
  136. package/libx/_runtime/common/utils/template.js +2 -3
  137. package/libx/_runtime/db/data-conversion/post-processing.js +3 -44
  138. package/libx/_runtime/db/generic/input.js +6 -6
  139. package/libx/_runtime/db/sql-builder/dataTypes.js +4 -0
  140. package/libx/_runtime/fiori/generic/activate.js +2 -2
  141. package/libx/_runtime/fiori/generic/before.js +40 -72
  142. package/libx/_runtime/fiori/generic/cancel.js +2 -2
  143. package/libx/_runtime/fiori/generic/delete.js +2 -2
  144. package/libx/_runtime/fiori/generic/edit.js +2 -2
  145. package/libx/_runtime/fiori/generic/new.js +3 -5
  146. package/libx/_runtime/fiori/generic/patch.js +49 -43
  147. package/libx/_runtime/fiori/generic/prepare.js +2 -2
  148. package/libx/_runtime/fiori/generic/read.js +27 -37
  149. package/libx/_runtime/fiori/utils/where.js +4 -2
  150. package/libx/_runtime/hana/Service.js +1 -3
  151. package/libx/_runtime/hana/conversion.js +3 -0
  152. package/libx/_runtime/hana/driver.js +33 -3
  153. package/libx/_runtime/hana/dynatrace.js +1 -0
  154. package/libx/_runtime/hana/search2Contains.js +12 -1
  155. package/libx/_runtime/hana/search2cqn4sql.js +10 -27
  156. package/libx/_runtime/hana/streaming.js +1 -0
  157. package/libx/_runtime/messaging/AMQPWebhookMessaging.js +4 -2
  158. package/libx/_runtime/messaging/common-utils/AMQPClient.js +1 -0
  159. package/libx/_runtime/messaging/enterprise-messaging-utils/getTenantInfo.js +5 -2
  160. package/libx/_runtime/messaging/enterprise-messaging-utils/registerEndpoints.js +2 -0
  161. package/libx/_runtime/messaging/enterprise-messaging.js +62 -3
  162. package/libx/_runtime/messaging/outbox/utils.js +1 -1
  163. package/libx/_runtime/messaging/redis-messaging.js +1 -0
  164. package/libx/_runtime/remote/Service.js +2 -2
  165. package/libx/_runtime/remote/utils/client.js +35 -11
  166. package/libx/_runtime/remote/utils/data.js +7 -2
  167. package/libx/_runtime/sqlite/Service.js +18 -7
  168. package/libx/_runtime/sqlite/conversion.js +3 -0
  169. package/libx/_runtime/sqlite/convertAssocToOneManaged.js +3 -3
  170. package/libx/_runtime/sqlite/localized.js +8 -8
  171. package/libx/odata/afterburner.js +39 -7
  172. package/libx/odata/cqn2odata.js +6 -3
  173. package/libx/odata/grammar.pegjs +66 -18
  174. package/libx/odata/index.js +3 -2
  175. package/libx/odata/parser.js +1 -1
  176. package/libx/odata/utils.js +2 -0
  177. package/libx/rest/RestAdapter.js +62 -43
  178. package/libx/rest/middleware/input.js +2 -3
  179. package/libx/rest/middleware/parse.js +2 -1
  180. package/libx/rest/middleware/update.js +1 -1
  181. package/package.json +2 -2
  182. package/server.js +5 -4
  183. package/srv/mtx.cds +1 -1
  184. package/srv/mtx.js +4 -24
  185. package/lib/srv/adapters.js +0 -85
  186. package/lib/utils/resources/index.js +0 -48
  187. package/lib/utils/resources/tar.js +0 -49
  188. package/lib/utils/resources/utils.js +0 -11
  189. package/libx/_runtime/db/utils/propagateForeignKeys.js +0 -93
  190. package/libx/_runtime/extensibility/activate.js +0 -69
  191. package/libx/_runtime/extensibility/add.js +0 -50
  192. package/libx/_runtime/extensibility/addExtension.js +0 -72
  193. package/libx/_runtime/extensibility/defaults.js +0 -34
  194. package/libx/_runtime/extensibility/handler/transformREAD.js +0 -121
  195. package/libx/_runtime/extensibility/handler/transformRESULT.js +0 -51
  196. package/libx/_runtime/extensibility/handler/transformWRITE.js +0 -64
  197. package/libx/_runtime/extensibility/linter/allowlist_checker.js +0 -373
  198. package/libx/_runtime/extensibility/linter/annotations_checker.js +0 -113
  199. package/libx/_runtime/extensibility/linter/checker_base.js +0 -20
  200. package/libx/_runtime/extensibility/linter/namespace_checker.js +0 -180
  201. package/libx/_runtime/extensibility/linter.js +0 -32
  202. package/libx/_runtime/extensibility/push.js +0 -118
  203. package/libx/_runtime/extensibility/service.js +0 -38
  204. package/libx/_runtime/extensibility/token.js +0 -57
  205. package/libx/_runtime/extensibility/utils.js +0 -131
  206. package/libx/_runtime/extensibility/validation.js +0 -50
  207. package/libx/_runtime/extensibility/views.js +0 -12
  208. package/srv/extensibility-service.cds +0 -59
  209. package/srv/extensibility-service.js +0 -1
  210. package/srv/extensions.cds +0 -8
  211. package/srv/model-provider.cds +0 -61
  212. package/srv/model-provider.js +0 -143
package/lib/index.js CHANGED
@@ -23,10 +23,9 @@ const _class = lazy => cds.builtin.classes [lazy]
23
23
  const _require = require; require = lazified (module) // eslint-disable-line
24
24
  const _service = lazified ({ // nested facade for service-related modules
25
25
  /** @param x {(this:Service, srv:Service, ...etc) => any} */ impl: x=>x,
26
- /** @type {{ [path:string] : Service }} */ paths: {},
27
26
  /** @type Service[] */ providers: [],
28
27
  factory: require ('./srv/factory'),
29
- adapters: require ('./srv/adapters'),
28
+ protocols: require ('./srv/protocols'),
30
29
  bindings: require ('./srv/bindings'),
31
30
  })
32
31
 
@@ -66,6 +65,8 @@ const cds = module.exports = extend (new facade) .with ({
66
65
  *[Symbol.iterator]() {for (let e in this) yield this[e]}
67
66
  get _pending(){ let p={}; Object.defineProperty(this,'_pending',{value:p}); return p }
68
67
  },
68
+ middlewares: require('./srv/middlewares'),
69
+ auth: require ('./auth'),
69
70
 
70
71
  // Core Services API
71
72
  Service: require ('./srv/srv-api'),
@@ -73,7 +74,7 @@ const cds = module.exports = extend (new facade) .with ({
73
74
  Request: require ('./req/request'),
74
75
  Event: require ('./req/event'),
75
76
  User: require ('./req/user'),
76
- ql: require ('./ql/cds-ql'),
77
+ ql: lazy => require ('./ql/cds-ql'),
77
78
  tx: (..._) => (cds.db || cds.Service.prototype) .tx (..._),
78
79
  /** @type Service */ db: undefined,
79
80
 
@@ -124,7 +125,7 @@ extend (cds.__proto__) .with ({
124
125
  extend (cds.__proto__) .with (lazified ({
125
126
  /** @deprecated */ in: (cwd) => !cwd ? cds : {__proto__:cds, cwd, env: cds.env.for('cds',cwd) },
126
127
  mtx: lazy => require('../bin/mtx/in-cds'),
127
- build: lazy => require('../bin/build'),
128
+ build: lazy => require('../bin/build')
128
129
  }))
129
130
 
130
131
  // Add global forwards to cds.ql and cds.parse
@@ -147,6 +148,10 @@ if (process.env.CDS_STRICT_NODE_VERSION !== 'false') {
147
148
  // restore require for subsequent uses in lazy getters
148
149
  require = _require // eslint-disable-line
149
150
 
151
+ // can be used to later check that one has only one cds object
152
+ if (!global.__cds_loaded_from) global.__cds_loaded_from = new Set
153
+ global.__cds_loaded_from.add(__filename)
154
+
150
155
  if (process.env.CDS_GLOBAL) // TODO for stakeholder-tests only. Remove after cds 6.
151
156
  Object.assign(module,{ exports: global.cds || (global.cds = cds)})
152
157
  else
@@ -17,12 +17,10 @@ const { format } = require('util'), _formatted = v => format(v)
17
17
  * let x = y || cds.error `Argument 'y' must not be null`
18
18
  */
19
19
  const error = exports = module.exports = function cds_error (msg, _details, _base) {
20
- let e; if (msg.raw) e = new error (error.message(...arguments))
21
- else {
22
- e = msg.stack ? msg : typeof msg === 'string' ? new Error(msg) : Object.assign(new Error,msg)
23
- Error.captureStackTrace (e,_base||error)
24
- if (_details) Object.assign (e,_details)
25
- }
20
+ if (msg.raw) [ msg, _details, _base ] = [ error.message(...arguments) ]
21
+ const e = msg.stack ? msg : typeof msg === 'string' ? new Error(msg) : Object.assign(new Error,msg)
22
+ Error.captureStackTrace (e,_base||error)
23
+ if (_details) Object.assign (e,_details)
26
24
  if (new.target) return e; else throw e
27
25
  }
28
26
 
@@ -1,14 +1,10 @@
1
- const cds = require ('../index'), { log } = cds.env
2
-
3
- // Use configured logger in case of cds serve
4
- if (log.Logger || log.service) {
5
- if (log.Logger) exports.Logger = require (log.Logger)
6
- if (log.service) {
7
- const {app} = cds, serveIn = app => require('./service').serveIn(app)
8
- app ? setImmediate(() => serveIn(app)) : cds.on('bootstrap', app => serveIn(app))
9
- }
10
- }
1
+ const cds = require ('../index'), conf = cds.env.log
2
+ const log = module.exports = exports = cds_log
11
3
 
4
+ /**
5
+ * Cache used for all constructed loggers.
6
+ */
7
+ exports.loggers = {}
12
8
 
13
9
  /**
14
10
  * Returns a trace logger for the given module if trace is switched on for it,
@@ -42,27 +38,26 @@ if (log.Logger || log.service) {
42
38
  * @param {string} [module] the module for which a logger is requested
43
39
  * @param {string|number|{ level, prefix }} [options] the log level to enable -> 0=off, 1=error, 2=warn, 3=info, 4=debug, 5=trace
44
40
  */
45
- module.exports = exports = function cds_log (module, options) { // NOSONAR
46
- let id = module ? module.match(/^[^|]+/)[0] : 'cds'
47
- let logger = cached[id]; if (logger && !options) return logger
41
+ function cds_log (module, options) { // NOSONAR
42
+ const id = module?.match(/^[^|]+/)[0] || 'cds', cache = log.loggers
43
+ const cached = cache[id]
44
+ if (cached && !options) return cached
48
45
 
49
- let { level, prefix } = typeof options === 'object' ? options : {level:options}
50
- if (!prefix) prefix = logger && logger.prefix || id
51
- if (!level) level = (
52
- process.env.DEBUG && process.env.DEBUG.match(RegExp(`\\b(y|all|${module||'any'})\\b`)) ? DEBUG :
53
- log.levels[id] || INFO
54
- )
55
- if (typeof level === 'string') {
56
- level = exports.levels [level.toUpperCase()]
57
- }
46
+ let label = options?.label || options?.prefix || cached?.label || id
47
+ let level = typeof options === 'object' ? options.level : options
48
+ if (!level) level = DEBUG_matches(module) ? DEBUG : conf.levels[id] || INFO
49
+ if (typeof level === 'string') level = log.levels [level.toUpperCase()]
50
+ if (cached && cached.level === level) return cached
58
51
 
59
- // IMPORTANT: cds.log() can be called again to change the log level
60
- // of formerly constructed loggers!!
61
- if (logger && logger.level === level) return logger
62
- else logger = exports.Logger (prefix, level)
63
- return cached[id] = Object.assign (cached[id] || logger.log, logger, {
64
- id, level, prefix, setFormat(fn){ logger.format = fn }
65
- })
52
+ const logger = new Logger (label, level)
53
+ return cache[id] = Object.assign (cached || logger.log, {
54
+ id, label, level, setFormat(fn){ logger.format = fn; return this },
55
+ _trace: level >= TRACE,
56
+ _debug: level >= DEBUG,
57
+ _info: level >= INFO,
58
+ _warn: level >= WARN,
59
+ _error: level >= ERROR,
60
+ }, logger)
66
61
  }
67
62
 
68
63
 
@@ -72,7 +67,7 @@ module.exports = exports = function cds_log (module, options) { // NOSONAR
72
67
  */
73
68
  exports.debug = function cds_debug (module) {
74
69
  const L = this.log (module)
75
- return L._debug && L.debug
70
+ if (L._debug) return L.debug
76
71
  }
77
72
 
78
73
 
@@ -84,53 +79,94 @@ exports.debug = function cds_debug (module) {
84
79
  *
85
80
  * cds.log.Logger = () => winston.createLogger (...)
86
81
  *
87
- * @param {string} [module] the module for which a logger is requested
82
+ * @param {string} [label] the module for which a logger is requested
88
83
  * @param {number} [level] the log level to enable -> 0=off, 1=error, 2=warn, 3=info, 4=debug, 5=trace
89
84
  */
90
85
  /* eslint-disable no-console */
91
- exports.Logger = (module, level) => {
92
- const fmt = (level,args) => logger.format (module,level,...args)
93
- const logger = Object.assign ({
94
- format: exports.format,
86
+ exports.Logger = (label, level) => {
87
+ const fmt = (level,args) => logger.format (label,level,...args)
88
+ const logger = {
89
+ format: exports.format, // use logger.format as this could be changed dynamically
95
90
  trace: level < TRACE ? ()=>{} : (...args) => console.trace (...fmt(TRACE,args)),
96
91
  debug: level < DEBUG ? ()=>{} : (...args) => console.debug (...fmt(DEBUG,args)),
97
92
  log: level < INFO ? ()=>{} : (...args) => console.log (...fmt(INFO,args)),
98
93
  info: level < INFO ? ()=>{} : (...args) => console.info (...fmt(INFO,args)),
99
94
  warn: level < WARN ? ()=>{} : (...args) => console.warn (...fmt(WARN,args)),
100
95
  error: level < ERROR ? ()=>{} : (...args) => console.error (...fmt(ERROR,args)),
101
- _trace: level >= TRACE,
102
- _debug: level >= DEBUG,
103
- _info: level >= INFO,
104
- _warn: level >= WARN,
105
- _error: level >= ERROR,
106
- })
107
- // deleted stdout -> stderr redirection for cds compile as bin/utils/log.js is used
96
+ }
108
97
  return logger
109
98
  }
99
+ function Logger (label, level) { return exports.Logger (label, level) }
100
+
101
+
102
+ /**
103
+ * Convenience method to construct winston loggers, very similar to `winston.createLogger()`.
104
+ * @param {object} options - as in `winston.createLogger()`
105
+ * @returns The winston logger, decorated with the standard cds.log methods
106
+ * .debug(), .info(), .warn(), .error(), etc.
107
+ */
108
+ exports.winstonLogger = (options) => (label, level) => {
109
+ const winston = require("winston") // eslint-disable-line cds/no-missing-dependencies
110
+ const logger = winston.createLogger({
111
+ levels: log.levels, level: Object.keys(log.levels)[level],
112
+ transports: [new winston.transports.Console()],
113
+ ...options
114
+ })
115
+ const { formatWithOptions } = require('util')
116
+ const _fmt = ([...args]) => formatWithOptions(
117
+ { colors: false }, `[${label}] -`, ...args
118
+ )
119
+ return Object.assign (logger, {
120
+ trace: (...args) => logger.TRACE (_fmt(args)),
121
+ debug: (...args) => logger.DEBUG (_fmt(args)),
122
+ log: (...args) => logger.INFO (_fmt(args)),
123
+ info: (...args) => logger.INFO (_fmt(args)),
124
+ warn: (...args) => logger.WARN (_fmt(args)),
125
+ error: (...args) => logger.ERROR (_fmt(args)),
126
+ })
127
+ }
110
128
 
129
+ /**
130
+ * Built-in formatters
131
+ */
132
+ const { simple } = exports.formatters = {
133
+ simple: (label, level, ...args) => [ `[${label}] -`, ...args ],
134
+ mt: (label, level, ...args) => {
135
+ const t = cds.context?.tenant; if (t) label += '|'+t
136
+ return simple (label, level, ...args)
137
+ },
138
+ get json() {
139
+ return this._json || (this._json = require('./format/kibana'))
140
+ }
141
+ }
111
142
 
112
143
  /**
113
- * Formats a log outputs by returning an array of arguments which are passed to
144
+ * Formats log outputs by returning an array of arguments which are passed to
114
145
  * console.log() et al.
115
146
  * You can assign custom formatters like that:
116
147
  *
117
- * cds.log.format = (module, level, ...args) => [ '[', module, ']', ...args ]
148
+ * cds.log.format = (label, level, ...args) => [ '[', label, ']', ...args ]
118
149
  *
119
- * @param {string} module the module for which a logger is requested
120
- * @param {number} level the log level to enable -> 0=off, 1=error, 2=warn, 3=info, 4=debug, 5=trace
150
+ * @param {string} label the label to prefix to log output
151
+ * @param {number} level the log level to enable -> 0=off, 1=error, 2=warn, 3=info, 4=debug, 5=trace
121
152
  * @param {any[]} args the arguments passed to Logger.debug|log|info|wanr|error()
122
153
  */
123
154
  exports.format = (
124
- process.env.NODE_ENV === 'production' && cds.env.features.kibana_formatter ? require('./format/kibana')
125
- : cds.requires.multitenancy ? (module, level, ...args) => {
126
- const t = cds.context?.tenant
127
- return [ t ? `[${module}|${t}]` : `[${module}]`, '-', ...args ]
128
- }
129
- : (module, level, ...args) => [ `[${module}] -`, ...args ]
155
+ process.env.NODE_ENV === 'production' && cds.env.features.kibana_formatter ? log.formatters.json :
156
+ cds.requires.multitenancy ? log.formatters.mt : simple
130
157
  )
131
158
 
132
159
 
160
+ const DEBUG_matches = (m) => process.env.DEBUG?.match(RegExp(`\\b(y|all|${m||'any'})\\b`))
133
161
  const { ERROR, WARN, INFO, DEBUG, TRACE } = exports.levels = {
134
162
  SILENT:0, ERROR:1, WARN:2, INFO:3, DEBUG:4, TRACE:5, SILLY:5, VERBOSE:5
135
163
  }
136
- const cached = exports.loggers = {}
164
+
165
+ ;(function _init() {
166
+ const conf = cds.env.log
167
+ if (conf.Logger) exports.Logger = require (conf.Logger) // Use configured logger in case of cds serve
168
+ if (conf.service) {
169
+ const {app} = cds, serveIn = app => require('./service').serveIn(app)
170
+ app ? setImmediate(() => serveIn(app)) : cds.on('bootstrap', app => serveIn(app))
171
+ }
172
+ })()
@@ -23,6 +23,7 @@ module.exports = class LogService extends cds.Service {
23
23
  }
24
24
 
25
25
  // serve embedded UI
26
+ // eslint-disable-next-line cds/no-missing-dependencies
26
27
  const express = require('express')
27
28
  app.use (path+'/ui', express.static(__dirname+'/vue.html'))
28
29
 
package/lib/ql/CREATE.js CHANGED
@@ -1,10 +1,7 @@
1
- const Query = require('./Query')
2
- const $ = Object.assign
3
-
4
- module.exports = class CREATE extends Query {
1
+ module.exports = class Query extends require('./Query') {
5
2
 
6
3
  static _api() {
7
- return $((..._) => (new this).entity(..._), {
4
+ return Object.assign((..._) => (new this).entity(..._), {
8
5
  entity: (..._) => (new this).entity(..._),
9
6
  })
10
7
  }
package/lib/ql/DELETE.js CHANGED
@@ -1,6 +1,6 @@
1
1
  const Whereable = require('./Whereable')
2
2
 
3
- module.exports = class DELETE extends Whereable {
3
+ module.exports = class Query extends Whereable {
4
4
 
5
5
  static _api() {
6
6
  return Object.assign ((..._) => (new this).from(..._), {
package/lib/ql/DROP.js CHANGED
@@ -1,6 +1,4 @@
1
- const Query = require('./Query')
2
-
3
- module.exports = class DROP extends Query {
1
+ module.exports = class Query extends require('./Query') {
4
2
  static _api() {
5
3
  return Object.assign ((e) => (new this).entity(e), {
6
4
  entity: (e) => (new this).entity(e),
package/lib/ql/INSERT.js CHANGED
@@ -1,7 +1,5 @@
1
- const Query = require('./Query')
2
- const is_array = Array.isArray
1
+ module.exports = class Query extends require('./Query') {
3
2
 
4
- module.exports = class INSERT extends Query {
5
3
  static _api() {
6
4
  return Object.assign ((..._) => (new this).entries(..._), {
7
5
  into: (..._) => (new this).into(..._),
@@ -63,3 +61,5 @@ module.exports = class INSERT extends Query {
63
61
  return super.valueOf('INSERT INTO')
64
62
  }
65
63
  }
64
+
65
+ const is_array = Array.isArray
package/lib/ql/Query.js CHANGED
@@ -1,5 +1,4 @@
1
1
  const { AsyncResource } = require('async_hooks')
2
- const { inspect } = require('util')
3
2
  const cds = require('../index')
4
3
 
5
4
  class Query {
@@ -8,7 +7,14 @@ class Query {
8
7
 
9
8
  /** Creates a derived instance that initially inherits all properties. */
10
9
  clone(){
11
- return { __proto__:this, [this.cmd]: {__proto__: this[this.cmd]} }
10
+ const inherited = Object.create (this[this.cmd])
11
+ return {__proto__:this, [this.cmd]: inherited }
12
+ }
13
+
14
+ flat(){
15
+ const flat = this[this.cmd]
16
+ for (let x=flat; x.__proto__;) Object.assign(flat, x = x.__proto__)
17
+ return new this.constructor (flat)
12
18
  }
13
19
 
14
20
  /** Binds this query to be executed with the given service */
@@ -23,20 +29,6 @@ class Query {
23
29
  return (r,e) => q.runInAsyncScope (srv.run, srv, this) .then (r,e)
24
30
  }
25
31
 
26
- /** Beautifies output in REPL */
27
- [inspect.custom]() {
28
- const {cmd} = this, colors = process.env.CDS_TERM_COLORS !== false
29
- return `{ ${cmd}: `+ inspect(this[cmd], { colors, depth: 22 })
30
- .replace(/^\w*\s/, '')
31
- .replace(
32
- /{ ref: \[([^\]]*)\] }/g,
33
- (_,ref) => '{ref:[' + ref.slice(1, -1) + ']}'
34
- )
35
- .replace(/{ val: ([^ ]*) }/g, '{val:$1}')
36
- .replace(/{ (xpr|ref|val): /g, '{$1:') +
37
- '}'
38
- }
39
-
40
32
  _target_ref4 (target, arg2) {
41
33
 
42
34
  // Resolving this._target --> REVISIT: this is not reliable !!!
@@ -55,19 +47,14 @@ class Query {
55
47
 
56
48
  //> REVISIT: should we rather have consistent .from/.entity/.into in CQN?
57
49
  _target_name4 (...args) {
58
- const {ref} = this._target_ref4 (...args)
59
- return ref.length === 1 && typeof ref[0] === 'string' ? ref[0] : {ref}
50
+ const {ref, as} = this._target_ref4 (...args)
51
+ return ref.length === 1 && typeof ref[0] === 'string' && !as ? ref[0] : as ? {ref, as} : {ref}
60
52
  }
61
53
 
62
54
  _expected (...args) {
63
55
  return cds.error.expected (...args)
64
56
  }
65
57
 
66
- _own (property, _ = this[this.cmd]) {
67
- const pd = Reflect.getOwnPropertyDescriptor (_, property)
68
- return pd && pd.value
69
- }
70
-
71
58
  _add (property, values) {
72
59
  const _ = this[this.cmd], pd = Reflect.getOwnPropertyDescriptor (_,property)
73
60
  _[property] = !pd || !pd.value ? values : [ ...pd.value, ...values ]
package/lib/ql/SELECT.js CHANGED
@@ -1,8 +1,7 @@
1
1
  const Whereable = require('./Whereable'), { parse, predicate4 } = Whereable
2
2
  const cds = require('../index')
3
3
 
4
-
5
- module.exports = class SELECT extends Whereable {
4
+ module.exports = class Query extends Whereable {
6
5
 
7
6
  static _api() {
8
7
  const $ = Object.assign
package/lib/ql/UPDATE.js CHANGED
@@ -1,7 +1,7 @@
1
1
  const Whereable = require('./Whereable')
2
2
  const { parse } = require('../index')
3
3
 
4
- module.exports = class UPDATE extends Whereable {
4
+ module.exports = class Query extends Whereable {
5
5
 
6
6
  static _api() {
7
7
  return Object.assign ((..._) => (new this).entity(..._), {
@@ -94,4 +94,4 @@ const _comma_separated_exprs = (s) => {
94
94
  return all
95
95
  }
96
96
 
97
- const operators = { '=':1, '-=':2, '+=':2, '*=':2, '/=':2, '%=':2 }
97
+ const operators = { '=':1, '-=':2, '+=':2, '*=':2, '/=':2, '%=':2 }
@@ -2,12 +2,7 @@ const { error } = require ('../index')
2
2
  const cds = require('../index')
3
3
  const parse = require('./parse')
4
4
 
5
- const _setIncludesOr = where => {
6
- if (where.includes('or')) Object.defineProperty(where,'_includes_or__',{value:1})
7
- else Object.defineProperty(where,'_includes_or__',{value:0})
8
- }
9
-
10
- class Whereable extends require('./Query') {
5
+ class Query extends require('./Query') {
11
6
 
12
7
  where(...x) { return this._where (x,'and','where') }
13
8
  and(...x) { return this._where (x,'and') }
@@ -19,20 +14,17 @@ class Whereable extends require('./Query') {
19
14
  if (!_clause) _clause = (
20
15
  _.having ? 'having' :
21
16
  _.where ? 'where' :
22
- _.from && _.from.on ? 'on' :
17
+ _.from?.on ? 'on' :
23
18
  error (`Invalid attempt to call '${this.cmd}.${and_or}()' before a prior call to '${this.cmd}.where()'`)
24
19
  )
25
20
  if (_clause === 'on') _ = _.from
26
- let left = this._own(_clause,_)
21
+ let left = Reflect.getOwnPropertyDescriptor(_,_clause)?.value
27
22
  if (!left) {
28
- _setIncludesOr(pred)
23
+ if (pred.includes('or')) this._left_has_or = true
29
24
  _[_clause] = pred
30
25
  } else {
31
- if (and_or === 'and') {
32
- if (left._includes_or__ === undefined) _setIncludesOr(left) // information might be lost while copying
33
- if (left._includes_or__ === 1) left = [{xpr:left}]
34
- if (pred.includes('or')) pred = [{xpr:pred}]
35
- }
26
+ if (this._left_has_or && and_or === 'and') { left = [{xpr:left}]; delete this._left_has_or }
27
+ if (pred.includes('or')) pred = [{xpr:pred}]
36
28
  _[_clause] = [ ...left, and_or, ...pred ]
37
29
  }
38
30
  }
@@ -111,4 +103,4 @@ const is_cqn = x => x.val !== undefined || x.xpr || x.ref || x.list || x.func ||
111
103
  const is_array = Array.isArray
112
104
  const operators = { '=':1, '<':2, '<=':2, '>':2, '>=':2, '!=':3, '<>':3, in:4, like:4, IN:4, LIKE:4 }
113
105
 
114
- module.exports = Object.assign (Whereable, { predicate4, parse })
106
+ module.exports = Object.assign (Query, { predicate4, parse })
package/lib/ql/cds-ql.js CHANGED
@@ -2,19 +2,25 @@ const cds = require('../index')
2
2
  const Query = require('./Query')
3
3
  require = path => { // eslint-disable-line no-global-assign
4
4
  const clazz = module.require (path); if (!clazz._api) return clazz
5
+ const api = clazz._api()
5
6
  Object.defineProperty (clazz.prototype, 'cmd', { value: path.match(/\w+$/)[0] })
6
- return clazz._api()
7
+ return Object.assign (function (...args) {
8
+ if (new.target) return new clazz (...args) // allows: new SELECT
9
+ return api (...args) // allows: SELECT(...).from()
10
+ }, api)
7
11
  }
8
12
 
9
13
  module.exports = Object.assign (_deprecated_srv_ql, { cdr: true,
10
- Query,
14
+ Query, clone(q) {
15
+ const cmd = q.cmd || Object.keys(q)[0]
16
+ return {__proto__:q, [cmd]: {__proto__:q[cmd] }}
17
+ },
11
18
  SELECT: require('./SELECT'),
12
19
  INSERT: require('./INSERT'),
13
20
  UPDATE: require('./UPDATE'),
14
21
  DELETE: require('./DELETE'),
15
22
  CREATE: require('./CREATE'),
16
23
  DROP: require('./DROP'),
17
- clone(q) { return Query.prototype.clone.call(q) }
18
24
  })
19
25
 
20
26
  function _deprecated_srv_ql() { // eslint-disable-next-line no-console
@@ -10,7 +10,6 @@ module.exports = new class extends AsyncLocalStorage {
10
10
  _context4(v) {
11
11
  if (v instanceof EventContext || typeof v !== 'object') return v
12
12
  if (v.context) return v.context
13
- if (v.req) v = v.res ? {http:v} : {http:{ ...v, res: v.req.res }}
14
13
  return EventContext.for(v)
15
14
  }
16
15
 
@@ -31,12 +30,21 @@ module.exports = new class extends AsyncLocalStorage {
31
30
  if (o instanceof EventContext) throw cds.error `The passed options must not be an instance of cds.EventContext.`
32
31
  const fx = ()=>{
33
32
  const tx = cds.tx({...o}) // create a new detached transaction for each run of the background job
34
- return cds._context.run (tx, ()=> Promise.resolve(fn(tx))
33
+ return cds._context.run (tx, async ()=> {
34
+ // REVISIT: The model must be set _after_ run to make sure that cds.context.tenant is correctly set.
35
+ // Otherwise, `model4` could query the wrong database to check for extensions.
36
+ if (cds.model && (cds.env.requires.extensibility || cds.env.requires.toggles)) {
37
+ const ctx = cds.context
38
+ const ExtendedModels = require('../srv/srv-models') // the sentinel is automatically started when required
39
+ cds.context.model = await ExtendedModels.model4(ctx.tenant, ctx.features)
40
+ tx.model = cds.context.model
41
+ }
42
+ return Promise.resolve(fn(tx))
35
43
  .then (tx.commit, e => tx.rollback(_error(e, cds)))
36
44
  .then (res => Promise.all(em.listeners('succeeded').map(each => each(res))))
37
45
  .catch (err => Promise.all(em.listeners('failed').map(each => each(err))))
38
46
  .finally (() => Promise.all(em.listeners('done').map(each => each())))
39
- )
47
+ })
40
48
  }
41
49
  const em = new EventEmitter; em.timer = (
42
50
  (o && o.after) ? setTimeout(fx, o.after) :
@@ -88,9 +88,18 @@ class EventContext {
88
88
  }
89
89
 
90
90
  set user(u) {
91
- const user = u instanceof cds.User ? u : new cds.User(u)
92
- if (u && typeof u === 'object') for (let p in _TENANT_LOCALE) if (p in u && u[p]) this[p] = u[p]
93
- super.user = new Proxy (user,{ get:(t,p) => p in _TENANT_LOCALE ? this[p] : t[p] })
91
+ if (u && typeof u === 'object') for (let p of ['tenant','locale']) {
92
+ let pd = Reflect.getOwnPropertyDescriptor(u,p)
93
+ if (pd?.value) this[p] = pd.value
94
+ }
95
+ let user = u instanceof cds.User ? Object.create(u,{
96
+ tenant: {get:()=> this.tenant},
97
+ locale: {get:()=> this.locale},
98
+ }) : Object.defineProperties (new cds.User(u), {
99
+ tenant: {get:()=> this.tenant},
100
+ locale: {get:()=> this.locale},
101
+ })
102
+ super.user = user
94
103
  }
95
104
  get user() {
96
105
  this.user = this._propagated.user || _anonymous
@@ -109,13 +118,13 @@ class EventContext {
109
118
  }
110
119
 
111
120
  get _features() {
112
- return super._features = this._propagated._features || _features4 (this.http?.req?.features || this.user?.features || this.http?.req?.user?.features)
121
+ return super._features = this._propagated._features || Features.for (this.http?.req?.features || this.user?.features || this.http?.req?.user?.features)
113
122
  }
114
123
  get features() {
115
- return super.features = this._features || noFeatures
124
+ return super.features = this._features || Features.none
116
125
  }
117
126
  set features(v) {
118
- super.features = _features4(v)
127
+ super.features = Features.for(v)
119
128
  }
120
129
 
121
130
  get model() {
@@ -174,26 +183,23 @@ class EventContext {
174
183
  get _tx() { return this.tx } // REVISIT: for compatibility to bade usages of req._tx
175
184
  }
176
185
 
177
- const _TENANT_LOCALE = { tenant:1, locale:2 }
178
186
  const _anonymous = new cds.User.default
179
187
 
180
- const _features4 = features => { // normalizes features to an object
181
- if (!features) return
182
- if (features === '*') return allFeatures
183
- const o = (
184
- Array.isArray(features) ? features.reduce((fts,f)=>{ fts[f] = true; return fts },{}) :
185
- typeof features === 'object' ? Object.fromEntries (Object.entries(features).filter(([,v])=>v)) :
186
- (''+features).split(',').reduce((fts,f)=>{ fts[f] = true; return fts },{})
187
- )
188
- return Object.defineProperty (o,'$hash',$hash)
189
- }
190
- const $hash = {
191
- get() { return this.$hash = Object.keys(this).join(',') },
192
- set(v){ Object.defineProperty(this,'$hash',{value:v}) },
193
- configurable:true
188
+
189
+ class Features {
190
+ static for (x) { // normalizes features to an object
191
+ if (!x) return
192
+ if (x === '*') return this.all
193
+ const fts = new this
194
+ if (Array.isArray(x)) { for (let f of x) fts[f] = true }
195
+ else if (typeof x === 'object') { for (let f in x) if (x[f]) fts[f] = true }
196
+ else if (typeof x === 'string') { for (let f of x.split(',')) fts[f] = true }
197
+ return fts
198
+ }
199
+ get $hash() { return super.$hash = Object.keys(this).join(',') }
200
+ static all = new Proxy ({'*':true},{ has:() => true, get:(_,p) => p === '$hash' ? '*' : true })
201
+ static none = new class none extends Features {}
194
202
  }
195
- const allFeatures = new Proxy ({'*':true},{ has:() => true, get:(_,p) => p === '$hash' ? '*' : true })
196
- const noFeatures = {__proto__:{ $hash:'' }}
197
203
 
198
204
  EventContext.prototype._set('_propagated', Object.seal({}))
199
205
  EventContext.propagateHeaders = [ 'x-correlation-id' ]
package/lib/req/locale.js CHANGED
@@ -5,14 +5,18 @@ const INCLUDE_LIST = i18n.preserved_locales.reduce((p,n)=>{
5
5
  },{
6
6
  en_US_x_saptrc: 'en_US_saptrc',
7
7
  en_US_x_sappsd: 'en_US_sappsd',
8
- en_US_x_saprigi: 'en_US_saprigi',
9
- '1Q': 'en_US_saptrc',
10
- '2Q': 'en_US_sappsd',
11
- '3Q': 'en_US_saprigi'
8
+ en_US_x_saprigi: 'en_US_saprigi'
12
9
  })
13
10
 
11
+ const SAP_LANGUAGES = {
12
+ '1Q': 'en_US_x_saptrc',
13
+ '2Q': 'en_US_x_sappsd',
14
+ '3Q': 'en_US_x_saprigi'
15
+ }
16
+
17
+ // normalizes to BCP47
14
18
  const from_req = req => req && (
15
- req.query && req.query['sap-language'] ||
19
+ req.query && (req.query['sap-locale'] || SAP_LANGUAGES[req.query['sap-language']]) ||
16
20
  req.headers && (req.headers['x-sap-request-language'] || req.headers['accept-language'])
17
21
  )
18
22
 
@@ -61,6 +61,7 @@ class Request extends require('./event') {
61
61
  warn (...args) { return this._messages.add (3, ...args) }
62
62
  error (...args) { return this._errors.add (4, ...args) }
63
63
  reject (...args) {
64
+ if (args[0] === 401 && this._.req?.login) return this._.req.login()
64
65
  let e = this.error(...args)
65
66
  if (!e.stack) Error.captureStackTrace (e = Object.assign(new Error,e), this.reject)
66
67
  throw e
package/lib/req/user.js CHANGED
@@ -8,7 +8,8 @@ class User {
8
8
  }
9
9
  if (typeof _ === 'string') { this.id = _; return }
10
10
  for (let each in _) super[each === '_roles' ? 'roles' : each] = _[each] // overrides getters
11
- if (Array.isArray(this.roles)) this.roles = this.roles.reduce ((p,n)=>{p[n]=1; return p},{})
11
+ const roles = this.hasOwnProperty('roles') && this.roles // eslint-disable-line no-prototype-builtins
12
+ if (Array.isArray(roles)) this.roles = roles.reduce ((p,n)=>{p[n]=1; return p},{})
12
13
  }
13
14
 
14
15
  get attr() { return super.attr = {} }
@@ -6,7 +6,7 @@ const _pending = cds.services._pending || {} // used below to chain parallel con
6
6
  */
7
7
  const connect = module.exports = async function cds_connect (options) {
8
8
  if (typeof options === 'object' && cds.db) throw cds.error (
9
- `You need to disconnect before creating a new primary connection with different options!`
9
+ `Re-connect to primary db with potentially different options is not allowed!`
10
10
  )
11
11
  if (typeof options === 'string') cds.db = await connect.to (options)
12
12
  else await connect.to ('db',options)