@cap-js/audit-logging 0.8.2 → 0.8.3

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 CHANGED
@@ -4,6 +4,10 @@ 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.8.3 - 2025-04-09
8
+
9
+ - Preperation for `@sap/cds^9`
10
+
7
11
  ## Version 0.8.2 - 2024-11-27
8
12
 
9
13
  ### Fixed
@@ -16,7 +20,7 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/).
16
20
 
17
21
  ### Fixed
18
22
 
19
- - Support for @sap/cds^8.2
23
+ - Support for `@sap/cds^8.2`
20
24
  - Reduce clutter in error raised for outbound requests
21
25
 
22
26
  ## Version 0.8.0 - 2024-05-24
@@ -39,7 +43,7 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/).
39
43
 
40
44
  ### Added
41
45
 
42
- - Support for @sap/cds^7.5
46
+ - Support for `@sap/cds^7.5`
43
47
 
44
48
  ### Fixed
45
49
 
@@ -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,11 @@ 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
+ if (!('_relations' in relative)) {
127
+ const newRelation = Relation.to(relative)
128
+ relative._relations = new Proxy(exposeRelation(newRelation), relationHandler(newRelation))
129
+ }
130
+
124
131
  let w = relative._relations[element.name].join(targetAlias, relativeAlias)
125
132
 
126
133
  // REVISIT: rewrite to path expression, if alias for relative is already used in subselect to avoid sql error
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@cap-js/audit-logging",
3
- "version": "0.8.2",
3
+ "version": "0.8.3",
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)",