@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/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 {any} entity - the entity
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
- const getViewTarget = entity => entity.query?.SELECT?.from?.ref?.[0]
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
- const getProjectionTarget = entity => entity.projection?.from?.ref?.[0]
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 {object[]} */
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 {object | string} entity - entity to set draftable annotation for.
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(entity, value) {
72
- if (typeof entity === 'string') entity = this.#getDefinition(entity)
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 {object | string} entityOrName - entity to look draftability up for.
134
+ * @param {EntityCSN | string} entityOrFq - entity to look draftability up for.
85
135
  * @returns {boolean}
86
136
  */
87
- #getDraftable(entityOrName) {
88
- const entity = (typeof entityOrName === 'string')
89
- ? this.#getDefinition(entityOrName)
90
- : entityOrName
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 ?? entityOrName
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
- #getDefinition(name) { return this.csn.definitions[name] }
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 {object} entity - entity to pull draftability from its parents.
159
+ * @param {EntityCSN | undefined} entity - entity to pull draftability from its parents.
106
160
  */
107
161
  #propagateInheritance(entity) {
108
- const annotations = (entity?.includes ?? []).map(parent => this.#getDraftable(parent))
109
- annotations.push(entity?.[annotation])
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 {object} entity - entity to propagate all compositions from.
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,