@cap-js/audit-logging 0.8.2 → 0.9.0
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 +14 -2
- package/lib/_relation.js +208 -0
- package/lib/utils.js +8 -0
- package/package.json +7 -9
- package/srv/service.js +1 -3
package/CHANGELOG.md
CHANGED
|
@@ -4,6 +4,18 @@ All notable changes to this project will be documented in this file.
|
|
|
4
4
|
This project adheres to [Semantic Versioning](http://semver.org/).
|
|
5
5
|
The format is based on [Keep a Changelog](http://keepachangelog.com/).
|
|
6
6
|
|
|
7
|
+
## Version 0.9.0 - 2025-06-05
|
|
8
|
+
|
|
9
|
+
### Added
|
|
10
|
+
|
|
11
|
+
- Support for `@sap/cds^9`
|
|
12
|
+
|
|
13
|
+
## Version 0.8.3 - 2025-04-09
|
|
14
|
+
|
|
15
|
+
### Fixed
|
|
16
|
+
|
|
17
|
+
- Preperation for `@sap/cds^9`
|
|
18
|
+
|
|
7
19
|
## Version 0.8.2 - 2024-11-27
|
|
8
20
|
|
|
9
21
|
### Fixed
|
|
@@ -16,7 +28,7 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/).
|
|
|
16
28
|
|
|
17
29
|
### Fixed
|
|
18
30
|
|
|
19
|
-
- Support for
|
|
31
|
+
- Support for `@sap/cds^8.2`
|
|
20
32
|
- Reduce clutter in error raised for outbound requests
|
|
21
33
|
|
|
22
34
|
## Version 0.8.0 - 2024-05-24
|
|
@@ -39,7 +51,7 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/).
|
|
|
39
51
|
|
|
40
52
|
### Added
|
|
41
53
|
|
|
42
|
-
- Support for
|
|
54
|
+
- Support for `@sap/cds^7.5`
|
|
43
55
|
|
|
44
56
|
### Fixed
|
|
45
57
|
|
package/lib/_relation.js
ADDED
|
@@ -0,0 +1,208 @@
|
|
|
1
|
+
let initializing = false
|
|
2
|
+
|
|
3
|
+
class Relation {
|
|
4
|
+
constructor(csn, path = []) {
|
|
5
|
+
if (!initializing) throw new Error(`Do not new a relation, use 'Relation.to()' instead`)
|
|
6
|
+
Object.defineProperty(this, 'csn', { get: () => csn })
|
|
7
|
+
Object.defineProperty(this, 'path', {
|
|
8
|
+
get: () => path,
|
|
9
|
+
set: _ => {
|
|
10
|
+
path = _
|
|
11
|
+
}
|
|
12
|
+
})
|
|
13
|
+
if (csn.target) Object.defineProperty(this, 'target', { get: () => csn.target })
|
|
14
|
+
initializing = false
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
static to(from, name) {
|
|
18
|
+
initializing = true
|
|
19
|
+
if (!name) return new Relation(from)
|
|
20
|
+
return from._elements[name] && new Relation(from._elements[name], [...from.path, name])
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
_has(prop) {
|
|
24
|
+
return Reflect.has(this, prop)
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
get _elements() {
|
|
28
|
+
if (this.csn.elements) return this.csn.elements
|
|
29
|
+
if (this.csn._target && this.csn._target.elements) return this.csn._target.elements
|
|
30
|
+
// if (csn.targetAspect) relation.elements = model.definitions[csn.targetAspect].elements
|
|
31
|
+
// if (csn.kind = 'type') relation.elements = model.definitions[csn.type].element
|
|
32
|
+
return {}
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
join(fromAlias = '', toAlias = '') {
|
|
36
|
+
return _getOnCond(this.csn, this.path, { select: fromAlias, join: toAlias })
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
const exposeRelation = relation => Object.defineProperty({}, '_', { get: () => relation })
|
|
41
|
+
|
|
42
|
+
const relationHandler = relation => ({
|
|
43
|
+
get: (target, name) => {
|
|
44
|
+
const path = name.split(',')
|
|
45
|
+
const prop = path.join('_')
|
|
46
|
+
if (!target[prop]) {
|
|
47
|
+
if (path.length === 1) {
|
|
48
|
+
// REVISIT: property 'join' must not be used in CSN to make this working
|
|
49
|
+
if (relation._has(prop)) return relation[prop]
|
|
50
|
+
const newRelation = Relation.to(relation, prop)
|
|
51
|
+
if (newRelation) {
|
|
52
|
+
target[prop] = new Proxy(exposeRelation(newRelation), relationHandler(newRelation))
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
return target[prop]
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
target[prop] = path.reduce((relation, value) => relation[value] || relation.csn._relations[value], relation)
|
|
59
|
+
target[prop].path = path
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
return target[prop]
|
|
63
|
+
}
|
|
64
|
+
})
|
|
65
|
+
|
|
66
|
+
module.exports = {
|
|
67
|
+
Relation,
|
|
68
|
+
exposeRelation,
|
|
69
|
+
relationHandler
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
//
|
|
73
|
+
// ----- utils
|
|
74
|
+
//
|
|
75
|
+
|
|
76
|
+
const _prefixForStruct = element => {
|
|
77
|
+
const prefixes = []
|
|
78
|
+
let parent = element.parent
|
|
79
|
+
while (parent && parent.kind !== 'entity') {
|
|
80
|
+
prefixes.push(parent.name)
|
|
81
|
+
parent = parent.parent
|
|
82
|
+
}
|
|
83
|
+
return prefixes.length ? prefixes.reverse().join('_') + '_' : ''
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
const _toRef = (alias, column) => {
|
|
87
|
+
if (Array.isArray(column)) column = column.join('_')
|
|
88
|
+
return { ref: alias ? [alias, column] : [column] }
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
const _adaptRefs = (onCond, path, { select, join }) => {
|
|
92
|
+
const _adaptEl = el => {
|
|
93
|
+
const ref = el.ref
|
|
94
|
+
|
|
95
|
+
if (ref) {
|
|
96
|
+
if (ref[0] === path.join('_') && ref[1]) {
|
|
97
|
+
return _toRef(select, ref.slice(1))
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
// no alias for special $user of canonical localized association
|
|
101
|
+
if (ref[0] === '$user' && path[0] === 'localized') {
|
|
102
|
+
return _toRef(undefined, ref.slice(0))
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
return _toRef(join, ref.slice(0))
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
if (el.xpr) return { xpr: el.xpr.map(_adaptEl) }
|
|
109
|
+
return el
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
return onCond.map(_adaptEl)
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
const _replace$selfAndAliasOnCond = (xpr, csnElement, aliases, path) => {
|
|
116
|
+
const selfIndex = xpr.findIndex(({ ref }) => ref?.[0] === '$self')
|
|
117
|
+
if (selfIndex != -1) {
|
|
118
|
+
let backLinkIndex
|
|
119
|
+
if (xpr[selfIndex + 1] && xpr[selfIndex + 1] === '=') backLinkIndex = selfIndex + 2
|
|
120
|
+
if (xpr[selfIndex - 1] && xpr[selfIndex - 1] === '=') backLinkIndex = selfIndex - 2
|
|
121
|
+
if (backLinkIndex != null) {
|
|
122
|
+
const ref = xpr[backLinkIndex].ref
|
|
123
|
+
const backlinkName = ref[ref.length - 1]
|
|
124
|
+
const mutOnCond = _newOnConditions(csnElement._backlink, [backlinkName], {
|
|
125
|
+
select: aliases.join,
|
|
126
|
+
join: aliases.select
|
|
127
|
+
})
|
|
128
|
+
|
|
129
|
+
xpr.splice(Math.min(backLinkIndex, selfIndex), 3, ...mutOnCond)
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
for (let i = 0; i < xpr.length; i++) {
|
|
134
|
+
const element = xpr[i]
|
|
135
|
+
if (element.xpr) {
|
|
136
|
+
_replace$selfAndAliasOnCond(element.xpr, csnElement, aliases, path)
|
|
137
|
+
continue
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
if (element.ref) {
|
|
141
|
+
if (element.ref[0] === path.join('_') && element.ref[1]) {
|
|
142
|
+
element.ref = _toRef(aliases.select, element.ref.slice(1)).ref
|
|
143
|
+
continue
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
// no alias for special $user of canonical localized association
|
|
147
|
+
if (element.ref[0] === '$user' && path[0] === 'localized') {
|
|
148
|
+
element.ref = _toRef(undefined, element.ref.slice(0)).ref
|
|
149
|
+
continue
|
|
150
|
+
}
|
|
151
|
+
//no alias for special $now variable
|
|
152
|
+
if (element.ref[0] === '$now') {
|
|
153
|
+
continue
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
if (element.ref[0] === aliases.join || element.ref[0] === aliases.select) {
|
|
157
|
+
// nothing todo here, as already right alias
|
|
158
|
+
continue
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
element.ref = _toRef(aliases.join, element.ref.slice(0)).ref
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
const _args = (csnElement, path, aliases) => {
|
|
167
|
+
const onCond = csnElement.on
|
|
168
|
+
if (!onCond || onCond.length === 0) return []
|
|
169
|
+
if (onCond.length < 3 && !onCond[0]?.xpr) return onCond
|
|
170
|
+
if (!csnElement._isSelfManaged) return _adaptRefs(onCond, path, aliases)
|
|
171
|
+
|
|
172
|
+
const onCondCopy = JSON.parse(JSON.stringify(onCond))
|
|
173
|
+
_replace$selfAndAliasOnCond(onCondCopy, csnElement, aliases, path)
|
|
174
|
+
|
|
175
|
+
return onCondCopy
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
// this is only for 2one managed w/o on-conditions, i.e. no static values are possible
|
|
179
|
+
const _foreignToOn = (csnElement, path, { select, join }) => {
|
|
180
|
+
const on = []
|
|
181
|
+
|
|
182
|
+
for (const key of csnElement._foreignKeys) {
|
|
183
|
+
if (on.length !== 0) {
|
|
184
|
+
on.push('and')
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
const prefixChild = _prefixForStruct(key.childElement)
|
|
188
|
+
const ref1 = _toRef(select, prefixChild + key.childElement.name)
|
|
189
|
+
const structPrefix = path.length > 1 ? path.slice(0, -1) : []
|
|
190
|
+
const ref2 = _toRef(join, [...structPrefix, key.parentElement.name])
|
|
191
|
+
on.push(ref1, '=', ref2)
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
return on
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
const _newOnConditions = (csnElement, path, aliases) => {
|
|
198
|
+
if (csnElement.keys) {
|
|
199
|
+
return _foreignToOn(csnElement, path, aliases)
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
return _args(csnElement, path, aliases)
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
const _getOnCond = (csnElement, path = [], aliases = { select: '', join: '' }) => {
|
|
206
|
+
const onCond = _newOnConditions(csnElement, path, aliases)
|
|
207
|
+
return [{ xpr: onCond }]
|
|
208
|
+
}
|
package/lib/utils.js
CHANGED
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
const cds = require('@sap/cds')
|
|
2
2
|
|
|
3
|
+
const { Relation, exposeRelation, relationHandler } = require('./_relation')
|
|
4
|
+
|
|
3
5
|
const WRITE = { CREATE: 1, UPDATE: 1, DELETE: 1 }
|
|
4
6
|
|
|
5
7
|
const $hasPersonalData = Symbol('@cap-js/audit-logging:hasPersonalData')
|
|
@@ -121,6 +123,12 @@ const _buildSubSelect = (model, { entity, relative, element, next }, row, previo
|
|
|
121
123
|
const targetAlias = _alias(element._target)
|
|
122
124
|
const relativeAlias = _alias(relative)
|
|
123
125
|
|
|
126
|
+
// REVISIT: there seems to be a caching issue in cds^9 when elemets are renamed
|
|
127
|
+
if (!('_relations' in relative) || !relative._relations[element.name]) {
|
|
128
|
+
const newRelation = Relation.to(relative)
|
|
129
|
+
relative._relations = new Proxy(exposeRelation(newRelation), relationHandler(newRelation))
|
|
130
|
+
}
|
|
131
|
+
|
|
124
132
|
let w = relative._relations[element.name].join(targetAlias, relativeAlias)
|
|
125
133
|
|
|
126
134
|
// REVISIT: rewrite to path expression, if alias for relative is already used in subselect to avoid sql error
|
package/package.json
CHANGED
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@cap-js/audit-logging",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.9.0",
|
|
4
4
|
"description": "CDS plugin providing integration to the SAP Audit Log service as well as out-of-the-box personal data-related audit logging based on annotations.",
|
|
5
5
|
"repository": "cap-js/audit-logging",
|
|
6
6
|
"author": "SAP SE (https://www.sap.com)",
|
|
7
7
|
"homepage": "https://cap.cloud.sap/",
|
|
8
|
-
"license": "
|
|
8
|
+
"license": "Apache-2.0",
|
|
9
9
|
"main": "cds-plugin.js",
|
|
10
10
|
"files": [
|
|
11
11
|
"lib",
|
|
@@ -14,21 +14,19 @@
|
|
|
14
14
|
],
|
|
15
15
|
"scripts": {
|
|
16
16
|
"lint": "npx eslint .",
|
|
17
|
-
"test": "
|
|
18
|
-
"test-new-db": "CDS_ENV='better-sqlite' npx jest --silent",
|
|
19
|
-
"test-old-db": "CDS_ENV='legacy-sqlite' npx jest --silent"
|
|
17
|
+
"test": "npx jest --silent"
|
|
20
18
|
},
|
|
21
19
|
"peerDependencies": {
|
|
22
|
-
"@sap/cds": ">=
|
|
20
|
+
"@sap/cds": ">=8"
|
|
23
21
|
},
|
|
24
22
|
"devDependencies": {
|
|
25
23
|
"@cap-js/audit-logging": "file:.",
|
|
26
|
-
"@cap-js/
|
|
24
|
+
"@cap-js/cds-test": ">=0",
|
|
25
|
+
"@cap-js/sqlite": ">=1",
|
|
27
26
|
"axios": "^1",
|
|
28
27
|
"eslint": "^9",
|
|
29
28
|
"express": "^4",
|
|
30
|
-
"jest": "^29"
|
|
31
|
-
"sqlite3": "^5.1.6"
|
|
29
|
+
"jest": "^29"
|
|
32
30
|
},
|
|
33
31
|
"cds": {
|
|
34
32
|
"requires": {
|
package/srv/service.js
CHANGED
|
@@ -1,8 +1,6 @@
|
|
|
1
1
|
const cds = require('@sap/cds')
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
module.exports = class AuditLogService extends Base {
|
|
3
|
+
module.exports = class AuditLogService extends cds.Service {
|
|
6
4
|
async init() {
|
|
7
5
|
// add common audit log entry fields
|
|
8
6
|
this.before('*', req => {
|