@cap-js/cds-typer 0.24.0 → 0.26.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 +28 -2
- package/README.md +19 -0
- package/lib/cli.js +30 -14
- package/lib/compile.js +3 -3
- package/lib/components/enum.js +19 -10
- package/lib/components/identifier.js +1 -1
- package/lib/components/inline.js +81 -26
- package/lib/components/javascript.js +28 -0
- package/lib/components/property.js +12 -0
- package/lib/components/wrappers.js +27 -4
- package/lib/csn.js +90 -26
- package/lib/file.js +166 -44
- package/lib/logging.js +5 -1
- package/lib/resolution/builtin.js +3 -2
- package/lib/resolution/entity.js +46 -7
- package/lib/resolution/resolver.js +60 -20
- package/lib/typedefs.d.ts +83 -13
- package/lib/util.js +4 -3
- package/lib/visitor.js +199 -79
- package/package.json +10 -6
package/lib/csn.js
CHANGED
|
@@ -1,31 +1,65 @@
|
|
|
1
1
|
const annotation = '@odata.draft.enabled'
|
|
2
2
|
|
|
3
|
+
/** @typedef {import('./typedefs').resolver.CSN} CSN */
|
|
4
|
+
/** @typedef {import('./typedefs').resolver.EntityCSN} EntityCSN */
|
|
5
|
+
/** @typedef {import('./typedefs').resolver.ProjectionCSN} ProjectionCSN */
|
|
6
|
+
/** @typedef {import('./typedefs').resolver.ViewCSN} ViewCSN */
|
|
7
|
+
|
|
3
8
|
/**
|
|
4
9
|
* FIXME: this is pretty handwavey: we are looking for view-entities,
|
|
5
10
|
* i.e. ones that have a query, but are not a cds level projection.
|
|
6
11
|
* Those are still not expanded and we have to retrieve their definition
|
|
7
12
|
* with all properties from the inferred model.
|
|
8
13
|
* @param {any} entity - the entity
|
|
14
|
+
* @returns {entity is ViewCSN}
|
|
9
15
|
*/
|
|
10
16
|
const isView = entity => entity.query && !entity.projection
|
|
11
17
|
|
|
18
|
+
/**
|
|
19
|
+
* @param {EntityCSN} entity - the entity
|
|
20
|
+
* @returns {entity is ProjectionCSN | ViewCSN}
|
|
21
|
+
*/
|
|
22
|
+
const isViewOrProjection = entity => Object.hasOwn(entity, 'query') || Object.hasOwn(entity, 'projection')
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* @param {EntityCSN | ProjectionCSN} entity - the entity
|
|
26
|
+
* @returns {entity is ProjectionCSN}
|
|
27
|
+
*/
|
|
12
28
|
const isProjection = entity => entity.projection
|
|
13
29
|
|
|
14
30
|
/**
|
|
15
|
-
* @param {
|
|
31
|
+
* @param {EntityCSN} entity - the entity
|
|
16
32
|
* @see isView
|
|
17
33
|
* Unresolved entities have to be looked up from inferred csn.
|
|
18
34
|
*/
|
|
19
35
|
const isUnresolved = entity => entity._unresolved === true
|
|
20
36
|
|
|
37
|
+
/**
|
|
38
|
+
* @param {EntityCSN} entity - the entity
|
|
39
|
+
*/
|
|
21
40
|
const isCsnAny = entity => entity?.constructor?.name === 'any'
|
|
22
41
|
|
|
42
|
+
/**
|
|
43
|
+
* @param {EntityCSN} entity - the entity
|
|
44
|
+
*/
|
|
23
45
|
const isDraftEnabled = entity => entity['@odata.draft.enabled'] === true
|
|
24
46
|
|
|
47
|
+
/**
|
|
48
|
+
* @param {EntityCSN} entity - the entity
|
|
49
|
+
*/
|
|
25
50
|
const isType = entity => entity?.kind === 'type'
|
|
26
51
|
|
|
52
|
+
/**
|
|
53
|
+
* @param {EntityCSN} entity - the entity
|
|
54
|
+
*/
|
|
27
55
|
const isEntity = entity => entity?.kind === 'entity'
|
|
28
56
|
|
|
57
|
+
/**
|
|
58
|
+
* @param {EntityCSN | undefined} entity - the entity
|
|
59
|
+
* @returns {entity is import("./typedefs").resolver.EnumCSN}
|
|
60
|
+
*/
|
|
61
|
+
const isEnum = entity => Boolean(entity && Object.hasOwn(entity, 'enum'))
|
|
62
|
+
|
|
29
63
|
/**
|
|
30
64
|
* Attempts to retrieve the max cardinality of a CSN for an entity.
|
|
31
65
|
* @param {EntityCSN} element - csn of entity to retrieve cardinality for
|
|
@@ -34,13 +68,24 @@ const isEntity = entity => entity?.kind === 'entity'
|
|
|
34
68
|
* If it is set to '*', result is Infinity.
|
|
35
69
|
*/
|
|
36
70
|
const getMaxCardinality = element => {
|
|
37
|
-
const cardinality = element?.cardinality?.max ?? 1
|
|
71
|
+
const cardinality = element?.cardinality?.max ?? '1'
|
|
38
72
|
return cardinality === '*' ? Infinity : parseInt(cardinality)
|
|
39
73
|
}
|
|
40
74
|
|
|
41
|
-
|
|
75
|
+
/**
|
|
76
|
+
* @param {EntityCSN} entity - the entity
|
|
77
|
+
*/
|
|
78
|
+
const getViewTarget = entity => isView(entity)
|
|
79
|
+
? entity.query?.SELECT?.from?.ref?.[0]
|
|
80
|
+
: undefined
|
|
42
81
|
|
|
43
|
-
|
|
82
|
+
/**
|
|
83
|
+
* @param {EntityCSN} entity - the entity
|
|
84
|
+
* @returns {string | undefined}
|
|
85
|
+
*/
|
|
86
|
+
const getProjectionTarget = entity => isProjection(entity)
|
|
87
|
+
? entity.projection?.from?.ref?.[0]
|
|
88
|
+
: undefined
|
|
44
89
|
|
|
45
90
|
class DraftUnroller {
|
|
46
91
|
/** @type {Set<string>} */
|
|
@@ -48,15 +93,18 @@ class DraftUnroller {
|
|
|
48
93
|
/** @type {{[key: string]: boolean}} */
|
|
49
94
|
#draftable = {}
|
|
50
95
|
/** @type {{[key: string]: string}} */
|
|
51
|
-
#projections
|
|
52
|
-
/** @type {
|
|
53
|
-
#entities
|
|
96
|
+
#projections = {}
|
|
97
|
+
/** @type {EntityCSN[]} */
|
|
98
|
+
#entities = []
|
|
99
|
+
/** @type {CSN | undefined} */
|
|
54
100
|
#csn
|
|
55
101
|
set csn(c) {
|
|
56
102
|
this.#csn = c
|
|
103
|
+
if (c === undefined) return
|
|
57
104
|
this.#entities = Object.values(c.definitions)
|
|
58
105
|
this.#projections = this.#entities.reduce((pjs, entity) => {
|
|
59
106
|
if (isProjection(entity)) {
|
|
107
|
+
// @ts-ignore - we know that entity is a projection here
|
|
60
108
|
pjs[entity.name] = getProjectionTarget(entity)
|
|
61
109
|
}
|
|
62
110
|
return pjs
|
|
@@ -65,11 +113,13 @@ class DraftUnroller {
|
|
|
65
113
|
get csn() { return this.#csn }
|
|
66
114
|
|
|
67
115
|
/**
|
|
68
|
-
* @param {
|
|
116
|
+
* @param {EntityCSN | string} entityOrFq - entity to set draftable annotation for.
|
|
69
117
|
* @param {boolean} value - whether the entity is draftable.
|
|
70
118
|
*/
|
|
71
|
-
#setDraftable(
|
|
72
|
-
|
|
119
|
+
#setDraftable(entityOrFq, value) {
|
|
120
|
+
const entity = typeof entityOrFq === 'string'
|
|
121
|
+
? this.#getDefinition(entityOrFq)
|
|
122
|
+
: entityOrFq
|
|
73
123
|
if (!entity) return // inline definition -- not found in definitions
|
|
74
124
|
entity[annotation] = value
|
|
75
125
|
this.#draftable[entity.name] = value
|
|
@@ -81,32 +131,38 @@ class DraftUnroller {
|
|
|
81
131
|
}
|
|
82
132
|
|
|
83
133
|
/**
|
|
84
|
-
* @param {
|
|
134
|
+
* @param {EntityCSN | string} entityOrFq - entity to look draftability up for.
|
|
85
135
|
* @returns {boolean}
|
|
86
136
|
*/
|
|
87
|
-
#getDraftable(
|
|
88
|
-
const entity =
|
|
89
|
-
? this.#getDefinition(
|
|
90
|
-
:
|
|
137
|
+
#getDraftable(entityOrFq) {
|
|
138
|
+
const entity = typeof entityOrFq === 'string'
|
|
139
|
+
? this.#getDefinition(entityOrFq)
|
|
140
|
+
: entityOrFq
|
|
91
141
|
// assert(typeof entity !== 'string')
|
|
92
|
-
const name = entity?.name ??
|
|
142
|
+
const name = entity?.name ?? entityOrFq
|
|
143
|
+
// @ts-expect-error - .name not being present means entityOrFq is a string, so name is always a string and therefore a valid index
|
|
93
144
|
return this.#draftable[name] ??= this.#propagateInheritance(entity)
|
|
94
145
|
}
|
|
95
146
|
|
|
96
147
|
/**
|
|
148
|
+
* FIXME: could use EntityRepository here
|
|
97
149
|
* @param {string} name - name of the entity.
|
|
150
|
+
* @returns {EntityCSN}
|
|
98
151
|
*/
|
|
99
|
-
#
|
|
152
|
+
// @ts-expect-error - poor man's #getDefinitionOrThrow. We are always sure name is a valid key
|
|
153
|
+
#getDefinition(name) { return this.csn?.definitions[name] }
|
|
100
154
|
|
|
101
155
|
/**
|
|
102
156
|
* Propagate draft annotations through inheritance (includes).
|
|
103
157
|
* The latest annotation through the inheritance chain "wins".
|
|
104
158
|
* Annotations on the entity itself are always queued last, so they will always be decisive over ancestors.
|
|
105
|
-
* @param {
|
|
159
|
+
* @param {EntityCSN | undefined} entity - entity to pull draftability from its parents.
|
|
106
160
|
*/
|
|
107
161
|
#propagateInheritance(entity) {
|
|
108
|
-
|
|
109
|
-
|
|
162
|
+
if (!entity) return
|
|
163
|
+
/** @type {(boolean | undefined)[]} */
|
|
164
|
+
const annotations = (entity.includes ?? []).map(parent => this.#getDraftable(parent))
|
|
165
|
+
annotations.push(entity[annotation])
|
|
110
166
|
this.#setDraftable(entity, annotations.filter(a => a !== undefined).at(-1) ?? false)
|
|
111
167
|
}
|
|
112
168
|
|
|
@@ -114,6 +170,10 @@ class DraftUnroller {
|
|
|
114
170
|
* Propagate draft-enablement through projections.
|
|
115
171
|
*/
|
|
116
172
|
#propagateProjections() {
|
|
173
|
+
/**
|
|
174
|
+
* @param {string} from - entity to propagate draftability from.
|
|
175
|
+
* @param {string} to - entity to propagate draftability to.
|
|
176
|
+
*/
|
|
117
177
|
const propagate = (from, to) => {
|
|
118
178
|
do {
|
|
119
179
|
this.#setDraftable(to, this.#getDraftable(to) || this.#getDraftable(from))
|
|
@@ -131,7 +191,7 @@ class DraftUnroller {
|
|
|
131
191
|
/**
|
|
132
192
|
* If an entity E is draftable and contains any composition of entities,
|
|
133
193
|
* then those entities also become draftable. Recursively.
|
|
134
|
-
* @param {
|
|
194
|
+
* @param {EntityCSN} entity - entity to propagate all compositions from.
|
|
135
195
|
*/
|
|
136
196
|
#propagateCompositions(entity) {
|
|
137
197
|
if (!this.#getDraftable(entity)) return
|
|
@@ -146,6 +206,7 @@ class DraftUnroller {
|
|
|
146
206
|
}
|
|
147
207
|
}
|
|
148
208
|
|
|
209
|
+
/** @param {CSN} csn - the full csn */
|
|
149
210
|
unroll(csn) {
|
|
150
211
|
this.csn = csn
|
|
151
212
|
|
|
@@ -241,7 +302,7 @@ function propagateForeignKeys(csn) {
|
|
|
241
302
|
// foreign key fields from Associations/ Compositions.
|
|
242
303
|
if (!Object.hasOwn(this, '__keys')) {
|
|
243
304
|
const ownKeys = Object.entries(this.elements ?? {}).filter(([,el]) => el.key === true)
|
|
244
|
-
const inheritedKeys = this.includes?.flatMap(parent => Object.entries(csn.definitions[parent].keys)) ?? []
|
|
305
|
+
const inheritedKeys = this.includes?.flatMap((/** @type {string} */ parent) => Object.entries(csn.definitions[parent].keys)) ?? []
|
|
245
306
|
// not sure why, but .associations contains both Associations, as well as Compositions in CSN.
|
|
246
307
|
// (.compositions contains only Compositions, if any)
|
|
247
308
|
const remoteKeys = Object.entries(this.associations ?? {})
|
|
@@ -249,9 +310,7 @@ function propagateForeignKeys(csn) {
|
|
|
249
310
|
.flatMap(([kname, key]) => Object.entries(csn.definitions[key.target].keys)
|
|
250
311
|
.map(([ckname, ckey]) => [`${kname}_${ckname}`, ckey]))
|
|
251
312
|
|
|
252
|
-
this.__keys = Object.fromEntries(ownKeys
|
|
253
|
-
.concat(inheritedKeys)
|
|
254
|
-
.concat(remoteKeys)
|
|
313
|
+
this.__keys = Object.fromEntries([...ownKeys, ...inheritedKeys, ...remoteKeys]
|
|
255
314
|
.filter(([,ckey]) => !ckey.target) // discard keys that are Associations. Those are already part of .elements
|
|
256
315
|
)
|
|
257
316
|
}
|
|
@@ -270,8 +329,11 @@ function amendCSN(csn) {
|
|
|
270
329
|
propagateForeignKeys(csn)
|
|
271
330
|
}
|
|
272
331
|
|
|
273
|
-
|
|
332
|
+
/**
|
|
333
|
+
* @param {EntityCSN} entity - the entity
|
|
334
|
+
*/
|
|
274
335
|
const getProjectionAliases = entity => {
|
|
336
|
+
/** @type {Record<string, string[]>} */
|
|
275
337
|
const aliases = {}
|
|
276
338
|
let all = false
|
|
277
339
|
for (const col of entity?.projection?.columns ?? []) {
|
|
@@ -290,8 +352,10 @@ module.exports = {
|
|
|
290
352
|
amendCSN,
|
|
291
353
|
isView,
|
|
292
354
|
isProjection,
|
|
355
|
+
isViewOrProjection,
|
|
293
356
|
isDraftEnabled,
|
|
294
357
|
isEntity,
|
|
358
|
+
isEnum,
|
|
295
359
|
isUnresolved,
|
|
296
360
|
isType,
|
|
297
361
|
getMaxCardinality,
|