@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.
- package/CHANGELOG.md +56 -3
- package/_i18n/i18n.properties +4 -7
- package/eslint.config.mjs +1 -1
- package/lib/compile/etc/properties.js +12 -9
- package/lib/compile/for/java.js +15 -3
- package/lib/compile/for/lean_drafts.js +44 -34
- package/lib/compile/for/nodejs.js +19 -10
- package/lib/compile/minify.js +2 -4
- package/lib/compile/parse.js +106 -72
- package/lib/compile/to/edm.js +19 -9
- package/lib/compile/to/hana.js +25 -21
- package/lib/compile/to/json.js +2 -2
- package/lib/compile/to/sql.js +15 -8
- package/lib/core/linked-csn.js +10 -4
- package/lib/dbs/cds-deploy.js +1 -1
- package/lib/env/cds-env.js +76 -66
- package/lib/env/defaults.js +1 -0
- package/lib/i18n/bundles.js +2 -1
- package/lib/i18n/files.js +3 -3
- package/lib/i18n/localize.js +2 -2
- package/lib/index.js +24 -18
- package/lib/ql/CREATE.js +11 -6
- package/lib/ql/DELETE.js +12 -9
- package/lib/ql/DROP.js +15 -8
- package/lib/ql/INSERT.js +19 -14
- package/lib/ql/SELECT.js +95 -168
- package/lib/ql/UPDATE.js +23 -14
- package/lib/ql/UPSERT.js +15 -2
- package/lib/ql/Whereable.js +44 -118
- package/lib/ql/cds-ql.js +222 -28
- package/lib/ql/{Query.js → cds.ql-Query.js} +52 -41
- package/lib/ql/cds.ql-predicates.js +133 -0
- package/lib/ql/cds.ql-projections.js +111 -0
- package/lib/ql/cqn.d.ts +146 -0
- package/lib/srv/cds-connect.js +3 -3
- package/lib/srv/cds-serve.js +2 -2
- package/lib/srv/cds.Service.js +132 -0
- package/lib/srv/{srv-api.js → cds.ServiceClient.js} +16 -71
- package/lib/srv/cds.ServiceProvider.js +20 -0
- package/lib/srv/factory.js +20 -8
- package/lib/srv/protocols/hcql.js +2 -3
- package/lib/srv/protocols/index.js +3 -3
- package/lib/srv/srv-dispatch.js +7 -6
- package/lib/srv/srv-handlers.js +103 -113
- package/lib/srv/srv-methods.js +14 -14
- package/lib/srv/srv-tx.js +5 -3
- package/lib/utils/cds-utils.js +2 -2
- package/libx/_runtime/cds-services/adapter/odata-v4/handlers/error.js +3 -3
- package/libx/_runtime/cds.js +2 -1
- package/libx/_runtime/common/aspects/service.js +25 -0
- package/libx/_runtime/common/generic/auth/index.js +5 -0
- package/libx/_runtime/common/generic/auth/restrict.js +36 -14
- package/libx/_runtime/common/generic/auth/service.js +24 -0
- package/libx/_runtime/common/generic/auth/utils.js +14 -6
- package/libx/_runtime/common/generic/etag.js +1 -1
- package/libx/_runtime/common/utils/cqn.js +1 -2
- package/libx/_runtime/common/utils/cqn2cqn4sql.js +1 -1
- package/libx/_runtime/common/utils/generateOnCond.js +7 -3
- package/libx/_runtime/common/utils/postProcess.js +4 -1
- package/libx/_runtime/common/utils/restrictions.js +1 -0
- package/libx/_runtime/fiori/lean-draft.js +54 -43
- package/libx/_runtime/messaging/enterprise-messaging-utils/registerEndpoints.js +1 -1
- package/libx/_runtime/remote/Service.js +2 -0
- package/libx/_runtime/remote/utils/client.js +12 -0
- package/libx/odata/index.js +5 -3
- package/libx/odata/middleware/create.js +2 -2
- package/libx/odata/middleware/delete.js +2 -2
- package/libx/odata/middleware/operation.js +2 -2
- package/libx/odata/middleware/read.js +14 -12
- package/libx/odata/middleware/service-document.js +16 -8
- package/libx/odata/middleware/update.js +2 -2
- package/libx/odata/parse/afterburner.js +63 -29
- package/libx/odata/parse/grammar.peggy +95 -0
- package/libx/odata/parse/parser.js +1 -1
- package/libx/odata/utils/index.js +5 -1
- package/libx/odata/utils/metadata.js +69 -75
- package/libx/odata/utils/postProcess.js +24 -3
- package/package.json +1 -1
- package/server.js +1 -1
- package/lib/ql/parse.js +0 -36
- /package/lib/ql/{infer.js → cds.ql-infer.js} +0 -0
package/lib/ql/cqn.d.ts
ADDED
|
@@ -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>
|
package/lib/srv/cds-connect.js
CHANGED
|
@@ -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('./
|
|
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
|
|
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
|
|
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
|
package/lib/srv/cds-serve.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
const cds = require ('..')
|
|
2
|
-
const
|
|
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
|
|
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('
|
|
8
|
-
const
|
|
7
|
+
const cds = require('../index'), { Event, Request } = cds
|
|
8
|
+
const Service = require('./cds.Service')
|
|
9
9
|
|
|
10
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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 (
|
|
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
|
-
|
|
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
|
package/lib/srv/factory.js
CHANGED
|
@@ -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'
|
|
3
|
+
const DEBUG = cds.debug('cds.service.factory'); DEBUG?.({ 'cds.root':cds.root, paths })
|
|
4
4
|
|
|
5
|
-
/** @typedef {import('./
|
|
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 = !
|
|
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?.
|
|
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
|
|
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
|
-
|
|
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
|
|
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, {
|
|
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
|
-
...
|
|
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
|
|
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
|
-
|
|
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
|
}
|
package/lib/srv/srv-dispatch.js
CHANGED
|
@@ -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('./
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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
|
|
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
|