@sap/cds 8.5.1 → 8.6.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 (81) hide show
  1. package/CHANGELOG.md +56 -3
  2. package/_i18n/i18n.properties +4 -7
  3. package/eslint.config.mjs +1 -1
  4. package/lib/compile/etc/properties.js +12 -9
  5. package/lib/compile/for/java.js +15 -3
  6. package/lib/compile/for/lean_drafts.js +44 -34
  7. package/lib/compile/for/nodejs.js +19 -10
  8. package/lib/compile/minify.js +2 -4
  9. package/lib/compile/parse.js +106 -72
  10. package/lib/compile/to/edm.js +19 -9
  11. package/lib/compile/to/hana.js +25 -21
  12. package/lib/compile/to/json.js +2 -2
  13. package/lib/compile/to/sql.js +15 -8
  14. package/lib/core/linked-csn.js +10 -4
  15. package/lib/dbs/cds-deploy.js +1 -1
  16. package/lib/env/cds-env.js +76 -66
  17. package/lib/env/defaults.js +1 -0
  18. package/lib/i18n/bundles.js +2 -1
  19. package/lib/i18n/files.js +3 -3
  20. package/lib/i18n/localize.js +2 -2
  21. package/lib/index.js +24 -18
  22. package/lib/ql/CREATE.js +11 -6
  23. package/lib/ql/DELETE.js +12 -9
  24. package/lib/ql/DROP.js +15 -8
  25. package/lib/ql/INSERT.js +19 -14
  26. package/lib/ql/SELECT.js +95 -168
  27. package/lib/ql/UPDATE.js +23 -14
  28. package/lib/ql/UPSERT.js +15 -2
  29. package/lib/ql/Whereable.js +44 -118
  30. package/lib/ql/cds-ql.js +222 -28
  31. package/lib/ql/{Query.js → cds.ql-Query.js} +52 -41
  32. package/lib/ql/cds.ql-predicates.js +133 -0
  33. package/lib/ql/cds.ql-projections.js +111 -0
  34. package/lib/ql/cqn.d.ts +146 -0
  35. package/lib/srv/cds-connect.js +3 -3
  36. package/lib/srv/cds-serve.js +2 -2
  37. package/lib/srv/cds.Service.js +132 -0
  38. package/lib/srv/{srv-api.js → cds.ServiceClient.js} +16 -71
  39. package/lib/srv/cds.ServiceProvider.js +20 -0
  40. package/lib/srv/factory.js +20 -8
  41. package/lib/srv/protocols/hcql.js +2 -3
  42. package/lib/srv/protocols/index.js +3 -3
  43. package/lib/srv/srv-dispatch.js +7 -6
  44. package/lib/srv/srv-handlers.js +103 -113
  45. package/lib/srv/srv-methods.js +14 -14
  46. package/lib/srv/srv-tx.js +5 -3
  47. package/lib/utils/cds-utils.js +2 -2
  48. package/libx/_runtime/cds-services/adapter/odata-v4/handlers/error.js +3 -3
  49. package/libx/_runtime/cds.js +2 -1
  50. package/libx/_runtime/common/aspects/service.js +25 -0
  51. package/libx/_runtime/common/generic/auth/index.js +5 -0
  52. package/libx/_runtime/common/generic/auth/restrict.js +36 -14
  53. package/libx/_runtime/common/generic/auth/service.js +24 -0
  54. package/libx/_runtime/common/generic/auth/utils.js +14 -6
  55. package/libx/_runtime/common/generic/etag.js +1 -1
  56. package/libx/_runtime/common/utils/cqn.js +1 -2
  57. package/libx/_runtime/common/utils/cqn2cqn4sql.js +1 -1
  58. package/libx/_runtime/common/utils/generateOnCond.js +7 -3
  59. package/libx/_runtime/common/utils/postProcess.js +4 -1
  60. package/libx/_runtime/common/utils/restrictions.js +1 -0
  61. package/libx/_runtime/fiori/lean-draft.js +54 -43
  62. package/libx/_runtime/messaging/enterprise-messaging-utils/registerEndpoints.js +1 -1
  63. package/libx/_runtime/remote/Service.js +2 -0
  64. package/libx/_runtime/remote/utils/client.js +12 -0
  65. package/libx/odata/index.js +5 -3
  66. package/libx/odata/middleware/create.js +2 -2
  67. package/libx/odata/middleware/delete.js +2 -2
  68. package/libx/odata/middleware/operation.js +2 -2
  69. package/libx/odata/middleware/read.js +14 -12
  70. package/libx/odata/middleware/service-document.js +16 -8
  71. package/libx/odata/middleware/update.js +2 -2
  72. package/libx/odata/parse/afterburner.js +63 -29
  73. package/libx/odata/parse/grammar.peggy +95 -0
  74. package/libx/odata/parse/parser.js +1 -1
  75. package/libx/odata/utils/index.js +5 -1
  76. package/libx/odata/utils/metadata.js +69 -75
  77. package/libx/odata/utils/postProcess.js +24 -3
  78. package/package.json +1 -1
  79. package/server.js +1 -1
  80. package/lib/ql/parse.js +0 -36
  81. /package/lib/ql/{infer.js → cds.ql-infer.js} +0 -0
@@ -0,0 +1,146 @@
1
+ /**
2
+ * `INSERT` and `UPSERT` queries are represented by the same internal
3
+ * structures. The `UPSERT` keyword is used to indicate that the
4
+ * statement should be updated if the targeted data exists.
5
+ * The `into` property specifies the target entity.
6
+ *
7
+ * The data to be inserted or updated can be specified in different ways:
8
+ *
9
+ * - in the `entries` property as deeply nested records.
10
+ * - in the `columns` and `values` properties as in SQL.
11
+ * - in the `columns` and `rows` properties, with `rows` being array of `values`.
12
+ * - in the `from` property with a `SELECT` query to provide the data to be inserted.
13
+ *
14
+ * The latter is the equivalent of SQL's `INSERT INTO ... SELECT ...` statements.
15
+ */
16
+ export class INSERT { INSERT: UPSERT['UPSERT'] }
17
+ export class UPSERT { UPSERT: {
18
+ into : ref
19
+ entries? : data[]
20
+ columns? : string[]
21
+ values? : scalar[]
22
+ rows? : scalar[][]
23
+ from? : SELECT
24
+ }}
25
+
26
+
27
+ /**
28
+ * `UPDATE` queries are used to capture modifications to existing data.
29
+ * They support a `where` clause to specify the rows to be updated,
30
+ * and a `with` clause to specify the new values. Alternatively, the
31
+ * `data` property can be used to specify updates with plain data only.
32
+ */
33
+ export class UPDATE { UPDATE: {
34
+ entity : ref
35
+ where? : expr
36
+ data : data
37
+ with : changes
38
+ }}
39
+
40
+
41
+ /**
42
+ * `DELETE` queries are used to remove data from a target datasource.
43
+ * They support a `where` clause to specify the rows to be deleted.
44
+ */
45
+ export class DELETE { DELETE: {
46
+ from : ref
47
+ where? : expr
48
+ }}
49
+
50
+
51
+ /**
52
+ * `SELECT` queries are used to retrieve data from a target datasource,
53
+ * and very much resemble SQL's `SELECT` statements, with these noteworthy
54
+ * additions:
55
+ *
56
+ * - The `from` clause supports `{ref}` paths with infix filters.
57
+ * - The `columns` clause supports deeply nested projections.
58
+ * - The `count` property requests the total count, similar to OData's `$count`.
59
+ * - The `one` property indicates that only a single record object shall be
60
+ * returned instead of an array.
61
+ *
62
+ * Also, CDS, and hence CQN, supports minimalistic `SELECT` statements with a `from`
63
+ * as the only mandatory property, which is equivalent to SQL's `SELECT * from ...`.
64
+ */
65
+ export class SELECT { SELECT: {
66
+ distinct? : true
67
+ count? : true
68
+ one? : true
69
+ from : source
70
+ columns? : column[]
71
+ where? : xo[]
72
+ having? : xo[]
73
+ groupBy? : expr[]
74
+ orderBy? : order[]
75
+ limit? : { rows: val, offset: val }
76
+ }}
77
+
78
+ type source = OneOf< ref &as | SELECT | {
79
+ join : 'inner' | 'left' | 'right'
80
+ args : [ source, source ]
81
+ on? : expr
82
+ }>
83
+
84
+ type column = OneOf< '*' | expr &as &cast | ref &as & OneOf<(
85
+ { expand?: column[] } |
86
+ { inline?: column[] }
87
+ )> &infix >
88
+
89
+ type order = expr & {
90
+ sort : 'asc' | 'desc'
91
+ nulls : 'first' | 'last'
92
+ }
93
+
94
+
95
+ interface changes { [elm:string]: OneOf< scalar | expr | changes | changes[] >}
96
+ interface data { [elm:string]: OneOf< scalar | data | data[] >}
97
+ interface as { as?: name }
98
+ interface cast { cast?: {type:name} }
99
+
100
+ interface infix {
101
+ orderBy? : order[]
102
+ where? : expr
103
+ limit? : { rows: val, offset: val }
104
+ }
105
+
106
+
107
+ /**
108
+ * Expressions can be entity or element references, query parameters,
109
+ * literal values, lists of all the former, function calls, sub selects,
110
+ * or compound expressions.
111
+ */
112
+ export type expr = OneOf< ref | val | xpr | list | func | param | SELECT >
113
+ export type ref = { ref: OneOf< name | { id:name &infix } >[] }
114
+ export type val = { val: scalar }
115
+ export type xpr = { xpr: xo[] }
116
+ export type list = { list: expr[] }
117
+ export type func = { func: string, args: expr[] }
118
+ export type param = { ref: [ '?' | number | string ], param: true }
119
+
120
+ /**
121
+ * This is used in `{xpr}` objects as well as in `SELECT.where` clauses to
122
+ * represent compound expressions as flat `xo` sequences.
123
+ * Note that CQN by intent does not _understand_ expressions and therefore
124
+ * keywords and operators are just represented as plain strings.
125
+ * This allows us to translate to and from any other query languages,
126
+ * including support for native SQL features.
127
+ */
128
+ type xo = OneOf< expr | keyword | operator >
129
+ type operator = '=' | '==' | '!=' | '<' | '<=' | '>' | '>='
130
+ type keyword = 'in' | 'like' | 'and' | 'or' | 'not'
131
+ type scalar = number | string | boolean | null
132
+ type name = string
133
+
134
+
135
+
136
+ // ---------------------------------------------------------------------------
137
+ // maybe coming later...
138
+
139
+ declare class CREATE { CREATE: {} }
140
+ declare class DROP { DROP: {} }
141
+
142
+
143
+ // ---------------------------------------------------------------------------
144
+ // internal helpers...
145
+
146
+ type OneOf<U> = Partial<(U extends any ? (k:U) => void : never) extends (k: infer I) => void ? I : never>
@@ -19,7 +19,7 @@ const connect = module.exports = async function cds_connect (options) {
19
19
  * or with options configured in cds.env.requires.<datasource>.
20
20
  * @param { string|Function|object } [datasource]
21
21
  * @param {{ kind?:String, impl?:String }} [options]
22
- * @returns { Promise<import('./srv-api')> }
22
+ * @returns { Promise<import('./cds.ServiceClient')> }
23
23
  */
24
24
  connect.to = (datasource, options) => {
25
25
  let Service = cds.service.factory
@@ -34,7 +34,7 @@ connect.to = (datasource, options) => {
34
34
  }
35
35
  const promise = (async()=>{
36
36
  TRACE?.time(`cds.connect.to ${datasource}`.padEnd(22).slice(0,22))
37
- const o = Service === cds.service.factory ? options4 (datasource, options) : {}
37
+ const o = Service.is_factory ? options4 (datasource, options) : {}
38
38
  const m = await model4 (o)
39
39
  // check if required service definition exists
40
40
  const required = cds.requires[datasource]
@@ -43,7 +43,7 @@ connect.to = (datasource, options) => {
43
43
  throw new Error (`No service definition found for '${required.service || datasource}'`)
44
44
  }
45
45
  // construct new service instance
46
- let srv = await new Service (datasource,m,o); await srv._init()
46
+ let srv = await new Service (datasource,m,o); await Service.init?.(srv)
47
47
  if (o.outbox) srv = cds.outboxed(srv)
48
48
  if (datasource) {
49
49
  if (datasource === 'db') cds.db = srv
@@ -1,5 +1,5 @@
1
1
  const cds = require ('..')
2
- const { Service } = cds.service.factory
2
+ const Service = cds.service.factory
3
3
  const _pending = cds.services._pending ??= {}
4
4
  const _ready = Symbol()
5
5
  const TRACE = cds.debug('trace')
@@ -84,7 +84,7 @@ module.exports = function cds_serve (som, _options) { // NOSONAR
84
84
  // cds.services, so they'll be found when they cds.connect to each others.
85
85
  let ready = provided.then (()=> Promise.all (all.map (async srv => {
86
86
  if (o.service && o.service._is_service_instance) return srv
87
- cds.services[srv.name] = await srv._init()
87
+ cds.services[srv.name] = await Service.init (srv)
88
88
  cds.service.providers.push (srv)
89
89
  if (srv[_ready]) srv[_ready](srv)
90
90
  return srv
@@ -0,0 +1,132 @@
1
+ //////////////////////////////////////////////////////////////
2
+ //
3
+ // PLEASE DO NOT RUN prettier ON THIS FILE
4
+ //
5
+ //////////////////////////////////////////////////////////////
6
+
7
+ const cds = require('../index')
8
+ const EventHandlers = require('./srv-handlers')
9
+
10
+
11
+ /**
12
+ * Private base class which contains all model & reflection-related APIs.
13
+ */
14
+ class ModeledService {
15
+
16
+ /**
17
+ * Constructs a new instance. Argument name can be omitted if the model contains only one service.
18
+ * @param {string} [name] - the name of the service
19
+ * @param {object} [model] - the CSN model
20
+ * @param {object} [options] - additional options
21
+ * @param {string} [options.kind] - the kind of the service
22
+ * @param {function} [options.impl] - the implementation function
23
+ */
24
+ constructor (model, name, options={}) {
25
+ this.name = name || new.target.name
26
+ this.options = options
27
+ if (options.kind) this.kind = options.kind // shortcut
28
+ if (model) this.model = model
29
+ }
30
+
31
+ /** @param {import('../core/linked-csn').LinkedCSN} csn */
32
+ set model (csn) {
33
+ super.model = csn ? cds.compile.for.nodejs(csn) : undefined
34
+ }
35
+
36
+ /**
37
+ * Lazy get the service definition from this.model
38
+ * @type import('../core/classes').service
39
+ */
40
+ get definition() {
41
+ const defs = this.model?.definitions; if (!defs) return super.definition = undefined
42
+ return super.definition = defs[this.options.service] || defs[this.name]
43
+ }
44
+
45
+ /** The namespace always is the service definition name */
46
+ get namespace() {
47
+ return super.namespace = this.definition?.name
48
+ || this.model?.namespace
49
+ || !this.isDatabaseService && !/\W/.test(this.name) && this.name
50
+ || undefined
51
+ }
52
+
53
+ // Reflection methods
54
+ get entities() { return super.entities = this.reflect (d => d.kind === 'entity') }
55
+ get events() { return super.events = this.reflect (d => d.kind === 'event') }
56
+ get types() { return super.types = this.reflect (d => !d.kind || d.kind === 'type') }
57
+ get actions() { return super.actions = this.reflect (d => d.kind === 'action' || d.kind === 'function') }
58
+ /** @deprecated */ get operations() { return this.actions } // compatibility
59
+ reflect(filter) { return this.model?.childrenOf (this.namespace, filter) || [] }
60
+ }
61
+
62
+
63
+ class Service extends ModeledService {
64
+
65
+ constructor (name, model, options) {
66
+ if (typeof name === 'object') {
67
+ [ model, options ] = [ name, model ]
68
+ let srv = cds.linked(model).services[0] || cds.error.expected `${{model}} passed as first argument to be a CSN with a single service definition`
69
+ name = srv.name
70
+ }
71
+ super (model, name, options)
72
+ }
73
+
74
+ /**
75
+ * Empty init() function to allow custom code to always call super.init()
76
+ * without having to care for if .init() exists.
77
+ */
78
+ init(){ return this }
79
+
80
+ // Event Handlers Registration API
81
+ handlers = new EventHandlers
82
+ prepend (fn) { return this.handlers.prepend.call (this,fn) }
83
+ before (...args) { return this.handlers.register (this, 'before', ...args) }
84
+ on (...args) { return this.handlers.register (this, 'on', ...args) }
85
+ after (...args) { return this.handlers.register (this, 'after', ...args) }
86
+ reject (e, path) { return this.handlers.register (this, '_initial', e, path,
87
+ r => r.reject (405, `Event "${r.event}" not allowed for entity "${r.path}".`)
88
+ )}
89
+
90
+ // Event Dispatching API
91
+ dispatch (req) {
92
+ const { dispatch } = require('./srv-dispatch')
93
+ return (Service.prototype.dispatch = dispatch) .call (this,req)
94
+ }
95
+
96
+ handle (req) {
97
+ const { handle } = require('./srv-dispatch')
98
+ return (Service.prototype.handle = handle) .call (this,req)
99
+ }
100
+
101
+ tx (...args) {
102
+ return (Service.prototype.tx = require('./srv-tx')) .call (this,...args)
103
+ }
104
+
105
+ /**
106
+ * @deprecated Flag to control whether this service is extensible.
107
+ * Can be overridden by subclasses.
108
+ */
109
+ get isExtensible() { // REVISIT: can we eliminate this?
110
+ return this.model === cds.model && !this.name?.startsWith('cds.xt.')
111
+ }
112
+
113
+ /** @deprecated */ get _handlers() { return this.handlers }
114
+ }
115
+
116
+ /** @deprecated */ Service.prototype.transaction = function(...args) { return this.tx(...args) }
117
+ /** @deprecated */ Service.prototype._implicit_next = cds.env.features.implicit_next
118
+
119
+ Service.prototype._is_service_instance = true //> for factory
120
+ Service._is_service_class = true //> for factory
121
+
122
+ //--------------------------------------------------------------------------
123
+ // EXPERIMENTAL: It is not decided yet, whether we should keep the stuff below
124
+ // => Please do not use anywhere!
125
+ Service.prototype.onSucceeded = function (...args) { return _req_on (this, 'succeeded', ...args) }
126
+ Service.prototype.onFailed = function (...args) { return _req_on (this, 'failed', ...args) }
127
+ const _req_on = (srv, succeeded_or_failed, event, path, handler) => {
128
+ if (!handler) [path,handler] = [undefined,path]
129
+ return srv.before (event,path, req => req.on(succeeded_or_failed,handler))
130
+ }
131
+
132
+ module.exports = Service
@@ -4,33 +4,19 @@
4
4
  //
5
5
  //////////////////////////////////////////////////////////////
6
6
 
7
- const cds = require('..'), { Event, Request } = cds
8
- const add_methods_to = require ('./srv-methods')
7
+ const cds = require('../index'), { Event, Request } = cds
8
+ const Service = require('./cds.Service')
9
9
 
10
- class Service extends require('./srv-handlers') {
10
+ const is_rest = x => x && typeof x === 'string' && x[0] === '/'
11
+ const is_query = x => x && x.bind || is_array(x) && !x.raw
12
+ const is_array = (x) => Array.isArray(x) && !x.raw
13
+ const is_object = (x) => typeof x === 'object'
11
14
 
12
- constructor (name, model, o) {
13
- if (is_object(name)) {
14
- [ model, o ] = [ name, model ]
15
- let srv = cds.linked(model).services[0] || cds.error.expected `${{model}} passed as first argument to be a CSN with a single service definition`
16
- name = srv.name
17
- }
18
- super (name || new.target.name) .options = o || (o={})
19
- if (o.kind) this.kind = o.kind // shortcut
20
- if (model) this.model = model
21
- }
15
+ class ServiceClient extends Service {
22
16
 
23
- /**
24
- * Subclasses may override this to prepare the given model appropriately
25
- */
26
- set model (csn) {
27
- if (csn) {
28
- let {definitions:defs={}} = super.model = cds.compile.for.nodejs(csn)
29
- super.definition = defs[this.options?.service] || defs[this.name]
30
- add_methods_to (this)
31
- } else {
32
- super.model = undefined
33
- }
17
+ constructor (...args) {
18
+ super(...args)
19
+ this.decorate()
34
20
  }
35
21
 
36
22
  /**
@@ -50,7 +36,7 @@ class Service extends require('./srv-handlers') {
50
36
  send (method, path, data, headers) {
51
37
  const req = method instanceof Request ? method : new Request (
52
38
  is_object(method) ? method :
53
- is_object(path) ? { method, data:path, headers:data }
39
+ is_object(path) ? path.is_linked ? { method, entity:path, data, headers } : { method, data:path, headers:data }
54
40
  : { method, path, data, headers }
55
41
  )
56
42
  return this.dispatch (req)
@@ -65,7 +51,8 @@ class Service extends require('./srv-handlers') {
65
51
  * Querying API to send synchronous requests...
66
52
  */
67
53
  run (query, data) {
68
- if (typeof query === 'function') {
54
+ if (query.raw) [ query, data ] = [ cds.ql (...arguments) ]
55
+ else if (typeof query === 'function') {
69
56
  const fn = query; if (this.context) return fn(this) // if this is a tx -> run fn with this
70
57
  const ctx = cds.context, tx = ctx?.tx // is there an (open) outer tx? ...
71
58
  if (!tx || tx._done === 'committed') return this.tx(fn) // no -> run fn with root tx
@@ -75,7 +62,7 @@ class Service extends require('./srv-handlers') {
75
62
  const req = new Request ({ query, data })
76
63
  return this.dispatch (req)
77
64
  }
78
- read (...args) { return is_query(args[0]) ? this.run(...args) : SELECT(...args).bind(this) }
65
+ read (...args) { return is_query(args[0]) ? this.run(...args) : SELECT.read(...args).bind(this) }
79
66
  insert (...args) { return INSERT(...args).bind(this) }
80
67
  create (...args) { return INSERT.into(...args).bind(this) }
81
68
  update (...args) { return UPDATE.entity(...args).bind(this) }
@@ -92,29 +79,6 @@ class Service extends require('./srv-handlers') {
92
79
  return this.run (query, data) .then (rows => rows.forEach(callback) || rows)
93
80
  }
94
81
 
95
- /**
96
- * Model Reflection API...
97
- */
98
- get namespace() {
99
- return super.namespace = this.definition?.name
100
- || this.model?.namespace
101
- || !this.isDatabaseService && !/\W/.test(this.name) && this.name
102
- || undefined
103
- }
104
- get entities() { return super.entities = _reflect (this, d => d.kind === 'entity') }
105
- get events() { return super.events = _reflect (this, d => d.kind === 'event') }
106
- get types() { return super.types = _reflect (this, d => !d.kind || d.kind === 'type') }
107
- get actions() { return super.actions = _reflect (this, d => d.kind === 'action' || d.kind === 'function') }
108
- get operations() { return super.operations = _reflect (this, d => d.kind === 'action' || d.kind === 'function') }
109
-
110
- /**
111
- * Flag to control whether this service is extensible.
112
- * Can be overridden by subclasses.
113
- */
114
- get isExtensible() {
115
- return this.model === cds.model && !this.name?.startsWith('cds.xt.') // REVISIT cds.xt name check should move to respective services
116
- }
117
-
118
82
  /**
119
83
  * Subclasses may override this to free resources when
120
84
  * tenants offboard or the service is disposed.
@@ -126,26 +90,7 @@ class Service extends require('./srv-handlers') {
126
90
  // }
127
91
  // delete cds.services[this.name] // REVISIT: this is in contrast to some tests
128
92
  }
129
-
130
- get path() { return super.path = cds.service.protocols.path4(this) }
131
- set path(p) { super.path = p }
132
-
133
- get endpoints() { return super.endpoints = cds.service.protocols.endpoints4(this) }
134
- set endpoints(p) { super.endpoints = p }
135
93
  }
94
+ ServiceClient.prototype.decorate = require('./srv-methods')
136
95
 
137
- const { dispatch, handle } = require('./srv-dispatch')
138
- Service.prototype.tx = require('./srv-tx')
139
- Service.prototype.handle = handle
140
- Service.prototype.dispatch = dispatch
141
- Service.prototype.transaction = Service.prototype.tx
142
- Service.prototype._implicit_next = cds.env.features.implicit_next
143
- Service.prototype._is_service_instance = Service._is_service_class = true //> for factory
144
- module.exports = Service
145
-
146
- // Helpers...
147
- const _reflect = (srv,filter) => !srv.model ? [] : srv.model.childrenOf (srv.namespace,filter)
148
- const is_rest = x => x && typeof x === 'string' && x[0] === '/'
149
- const is_query = x => x && x.bind || is_array(x) && !x.raw
150
- const is_array = (x) => Array.isArray(x) && !x.raw
151
- const is_object = (x) => typeof x === 'object'
96
+ module.exports = ServiceClient
@@ -0,0 +1,20 @@
1
+ //////////////////////////////////////////////////////////////
2
+ //
3
+ // PLEASE DO NOT RUN prettier ON THIS FILE
4
+ //
5
+ //////////////////////////////////////////////////////////////
6
+
7
+ const ServiceClient = require('./cds.ServiceClient')
8
+ const cds = require('../index')
9
+
10
+
11
+ class ServiceProvider extends ServiceClient {
12
+
13
+ get endpoints() { return super.endpoints = cds.service.protocols.endpoints4(this) }
14
+ set endpoints(p) { super.endpoints = p }
15
+ get path() { return super.path = cds.service.protocols.path4(this) }
16
+ set path(p) { super.path = p }
17
+
18
+ }
19
+
20
+ module.exports = ServiceProvider
@@ -1,16 +1,16 @@
1
1
  const cds = require('..'), { path, isfile, redacted } = cds.utils
2
2
  const paths = Array.from (new Set ([ cds.root, ...require.resolve.paths('x') ]))
3
- const DEBUG = cds.debug('cds.service.factory',); DEBUG && DEBUG ({ 'cds.root':cds.root, paths })
3
+ const DEBUG = cds.debug('cds.service.factory'); DEBUG?.({ 'cds.root':cds.root, paths })
4
4
 
5
- /** @typedef {import('./srv-api')} Service @type { (()=>Service) & (new()=>Service) } */
5
+ /** @typedef {import('./cds.Service')} Service @type { (()=>Service) & (new()=>Service) } */
6
6
  const ServiceFactory = function (name, model, options) { //NOSONAR
7
7
 
8
8
  const o = { ...options } // avoid changing shared options
9
9
  const conf = cds.requires[name]
10
- const serve = !(conf && conf.external && (!o.mocked || conf.credentials))
10
+ const serve = !conf?.external || o.mocked && !conf.credentials
11
11
  const defs = !model ? {[name]:{}} : model.definitions || cds.error `Invalid argument for 'model': ${model}`
12
12
  const def = !name || name === 'db' ? {} : defs[name] || {}
13
- DEBUG?. ({ name, definition:def, options:redacted(o) })
13
+ DEBUG?.({ name, definition:def, options:redacted(o) })
14
14
 
15
15
  let it /* eslint-disable no-cond-assign */
16
16
  if (it = o.with) return _use (it) // from cds.serve (<options>)
@@ -32,13 +32,13 @@ const ServiceFactory = function (name, model, options) { //NOSONAR
32
32
  const kind = o.kind = serve && def['@kind'] || o.kind || 'app-service'
33
33
  if (_require[kind]) return _require[kind]
34
34
  const {impl} = cds.requires[kind] || cds.requires.kinds[kind] || cds.error `No configuration found for 'cds.requires.${kind}'`
35
- DEBUG && DEBUG ('requires',{kind,impl})
35
+ DEBUG?.('requires',{kind,impl})
36
36
  return _require[kind] = _require (impl || cds.error `No 'impl' configured for 'cds.requires.${kind}'`)
37
37
  }
38
38
  }
39
39
 
40
40
  const _require = (it,d) => {
41
- DEBUG && d && DEBUG ('requires',{ service: d.name, source:_source(d), impl:it })
41
+ d && DEBUG?.('requires',{ service: d.name, source:_source(d), impl:it })
42
42
  if (it.startsWith('@sap/cds/')) it = cds.home + it.slice(8) //> for local tests in @sap/cds dev
43
43
  if (it.startsWith('./')) it = _relative (d,it.slice(2)) //> relative to <service>.cds
44
44
  try { var resolved = require.resolve(it,{paths}) } catch {
@@ -46,7 +46,7 @@ const _require = (it,d) => {
46
46
  throw cds.error `Failed loading service implementation from '${it}' ${{ Reason:e, paths, 'cds.root':cds.root }}`
47
47
  }
48
48
  }
49
- DEBUG && DEBUG({resolved})
49
+ DEBUG?.({resolved})
50
50
  return cds.utils._import(resolved)
51
51
  }
52
52
 
@@ -75,4 +75,16 @@ const _resolve = (...args) => {
75
75
  }
76
76
  const _is_class = (impl) => typeof impl === 'function' && impl.prototype && /^class\b/.test(impl)
77
77
 
78
- module.exports = Object.assign (ServiceFactory, { Service: ServiceFactory, resolve:_relative })
78
+ module.exports = Object.assign (ServiceFactory, { init, is_factory:true })
79
+
80
+ /**
81
+ * Used internally by cds.connect() and cds.serve() to add handlers
82
+ * from cds.service.impl-style implementations.
83
+ * @protected
84
+ */
85
+ async function init (srv) {
86
+ const { impl } = srv.options
87
+ if (typeof impl === 'function' && !/^class\b/.test(impl))
88
+ await impl.call (srv,srv)
89
+ return await srv.init?.() ?? srv
90
+ }
@@ -28,7 +28,7 @@ class HCQLAdapter extends require('./http') {
28
28
  .get ($(({entity,id,tail}, req) => {
29
29
  const body = typeof req.body === 'string' ? req.body : ''
30
30
  return tail || body ? {SELECT:{
31
- ...CQL(`SELECT from _ ${body} ${tail||''}`).SELECT,
31
+ ...SELECT (`from _ ${body} ${tail||''}`).SELECT,
32
32
  ...SELECT.from (entity,id).SELECT
33
33
  }} : SELECT.from (entity,id)
34
34
  }))
@@ -70,8 +70,7 @@ class HCQLAdapter extends require('./http') {
70
70
  * which is expected to be a plain CQN object or a CQL string.
71
71
  */
72
72
  query4 (/** @type express.Request */ req) {
73
- let b = req.body; if (typeof b === 'string') b = cds.parse.cql(b)
74
- let q = req.body = cds.ql.query(b); if (!q) return this.error (400, 'Invalid query', { query: req.body })
73
+ let q = req.body = cds.ql(req.body) || this.error (400, 'Invalid query', { query: req.body })
75
74
  // assert valid target entity
76
75
  if (q.target?._unresolved && this.service.definition) {
77
76
  q.target = q._target = this.service.entities [q.target.name]
@@ -61,11 +61,11 @@ class Protocols {
61
61
  // construct adapter instance from resolved implementation
62
62
  let adapter = cached[kind]; if (!adapter) {
63
63
  const conf = this[kind] ??= {}
64
- let { impl } = conf; if (typeof impl !== 'function') {
64
+ let { impl } = conf; if (typeof impl !== 'function') try {
65
65
  if (impl[0] === '.') impl = __dirname+impl.slice(1)
66
- try { require.resolve(impl) } catch { cds.error `Cannot find impl for protocol adapter: ${impl}` }
66
+ else impl = require.resolve(impl,{paths:[cds.root]})
67
67
  impl = conf.impl = require(impl)
68
- }
68
+ } catch { cds.error `Cannot find impl for protocol adapter: ${impl}` }
69
69
  adapter = cached[kind] = impl.prototype ? new impl(srv, conf) : impl(srv, conf)
70
70
  if (!adapter) continue
71
71
  }
@@ -3,7 +3,7 @@ const cds = require ('../index')
3
3
  /**
4
4
  * The default implementation of the `srv.dispatch(req)` ensures everything
5
5
  * is prepared before calling `srv.handle(req)`
6
- * @typedef {import('./srv-api')} Service
6
+ * @typedef {import('./cds.Service')} Service
7
7
  * @typedef {import('../req/request')} Request
8
8
  * @this {Service}
9
9
  * @param {Request} req
@@ -47,21 +47,21 @@ exports.handle = async function handle (req) {
47
47
  const srv=this; let handlers //...
48
48
 
49
49
  // ._initial handlers run in sequence
50
- handlers = this._handlers._initial.filter (h => h.for(req))
50
+ handlers = this.handlers._initial.filter (h => h.for(req))
51
51
  if (handlers.length) {
52
52
  for (const each of handlers) await each.handler.call (this,req)
53
53
  if (req.errors) throw req.reject()
54
54
  }
55
55
 
56
56
  // .before handlers run in parallel
57
- handlers = this._handlers.before.filter (h => h.for(req))
57
+ handlers = this.handlers.before.filter (h => h.for(req))
58
58
  if (handlers.length) {
59
59
  await Promise.all (handlers.map (each => each.handler.call (this,req)))
60
60
  if (req.errors) throw req.reject()
61
61
  }
62
62
 
63
63
  // .on handlers run in parallel for async events, and as interceptors stack for sync requests
64
- handlers = this._handlers.on.filter (h => h.for(req))
64
+ handlers = this.handlers.on.filter (h => h.for(req))
65
65
  if (handlers.length) {
66
66
  if (!req.reply) await Promise.all (handlers.map (each => each.handler.call (this,req,_dummy)))
67
67
  else await async function next (r=req) { //> handlers may pass a new req object into next()
@@ -76,7 +76,7 @@ exports.handle = async function handle (req) {
76
76
  else if (req.query) throw _unhandled (this,req)
77
77
 
78
78
  // .after handlers run in parallel
79
- handlers = this._handlers.after.filter (h => h.for(req))
79
+ handlers = this.handlers.after.filter (h => h.for(req))
80
80
  if (handlers.length) {
81
81
  const results = req.event === 'READ' && !_is_array(req.results) ? (req.results == null ? [] : [req.results]) : req.results
82
82
  await Promise.all (handlers.map (each => each.handler.call (this, results, req)))
@@ -100,13 +100,14 @@ const _ensure_target = (srv,req) => {
100
100
  else if (q.UPDATE) _ensure_fqn (q.UPDATE,'entity',srv)
101
101
  else if (q.DELETE) _ensure_fqn (q.DELETE,'from',srv)
102
102
  }
103
- const m = srv.model, defs = m && m.definitions || {}
103
+ const defs = srv.model?.definitions || {}
104
104
  req.target = typeof q === 'object' ? cds.infer(q,defs) : defs[req.path]
105
105
  }
106
106
 
107
107
  const _ensure_fqn = (x,p,srv, from = x[p]) => {
108
108
  if (!from) return
109
109
  if (typeof from === 'string') {
110
+ if (from in srv.entities) return x[p] = srv.entities[from].name
110
111
  if (srv.isDatabaseService) return
111
112
  if (srv.model && from in srv.model.definitions) return
112
113
  if (from.startsWith(srv.namespace)) return