@cap-js/cds-typer 0.7.0 → 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 +23 -1
- package/lib/csn.js +267 -0
- package/lib/file.js +3 -0
- package/lib/util.js +1 -44
- package/lib/visitor.js +25 -6
- package/package.json +3 -7
package/CHANGELOG.md
CHANGED
|
@@ -4,7 +4,7 @@ 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.
|
|
7
|
+
## Version 0.9.1 - TBD
|
|
8
8
|
|
|
9
9
|
### Changed
|
|
10
10
|
|
|
@@ -12,6 +12,28 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/).
|
|
|
12
12
|
|
|
13
13
|
### Fixed
|
|
14
14
|
|
|
15
|
+
## Version 0.9.0 - 2023-09-08
|
|
16
|
+
|
|
17
|
+
### Changed
|
|
18
|
+
|
|
19
|
+
### Added
|
|
20
|
+
- Support for drafts via `@odata.draft.enabled` annotation
|
|
21
|
+
|
|
22
|
+
### Fixed
|
|
23
|
+
- Foreign keys are now propagated more than one level (think: `x_ID_ID_ID`)
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
## Version 0.8.0 - 2023-09-05
|
|
27
|
+
|
|
28
|
+
### Changed
|
|
29
|
+
|
|
30
|
+
### Added
|
|
31
|
+
|
|
32
|
+
### Fixed
|
|
33
|
+
- Foreign keys that are inherited via aspects are now also generated in addition to the resolved property (see 0.7.0)
|
|
34
|
+
- Explicitly annotated `@singular` and `@plural` names are now properly used in generated _index.js_ files
|
|
35
|
+
|
|
36
|
+
|
|
15
37
|
## Version 0.7.0 - 2023-08-22
|
|
16
38
|
|
|
17
39
|
### Changed
|
package/lib/csn.js
ADDED
|
@@ -0,0 +1,267 @@
|
|
|
1
|
+
const annotation = '@odata.draft.enabled'
|
|
2
|
+
|
|
3
|
+
class DraftUnroller {
|
|
4
|
+
/** @type {Set<string>} */
|
|
5
|
+
#positives = new Set()
|
|
6
|
+
/** @type {{[key: string]: boolean}} */
|
|
7
|
+
#draftable = {}
|
|
8
|
+
/** @type {{[key: string]: string}} */
|
|
9
|
+
#projections
|
|
10
|
+
/** @type {object[]} */
|
|
11
|
+
#entities
|
|
12
|
+
#csn
|
|
13
|
+
set csn(c) {
|
|
14
|
+
this.#csn = c
|
|
15
|
+
this.#entities = Object.values(c.definitions)
|
|
16
|
+
this.#projections = this.#entities.reduce((pjs, entity) => {
|
|
17
|
+
if (entity.projection) {
|
|
18
|
+
pjs[entity.name] = entity.projection.from.ref[0]
|
|
19
|
+
}
|
|
20
|
+
return pjs
|
|
21
|
+
}, {})
|
|
22
|
+
}
|
|
23
|
+
get csn() { return this.#csn }
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* @param entity {object | string} - entity to set draftable annotation for.
|
|
27
|
+
* @param value {boolean} - whether the entity is draftable.
|
|
28
|
+
*/
|
|
29
|
+
#setDraftable(entity, value) {
|
|
30
|
+
if (typeof entity === 'string') entity = this.#getDefinition(entity)
|
|
31
|
+
entity[annotation] = value
|
|
32
|
+
this.#draftable[entity.name] = value
|
|
33
|
+
if (value) {
|
|
34
|
+
this.#positives.add(entity.name)
|
|
35
|
+
} else {
|
|
36
|
+
this.#positives.delete(entity.name)
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* @param entity {object | string} - entity to look draftability up for.
|
|
42
|
+
* @returns {boolean}
|
|
43
|
+
*/
|
|
44
|
+
#getDraftable(entity) {
|
|
45
|
+
if (typeof entity === 'string') entity = this.#getDefinition(entity)
|
|
46
|
+
return this.#draftable[entity.name] ??= this.#propagateInheritance(entity)
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* @param name {string} - name of the entity.
|
|
51
|
+
*/
|
|
52
|
+
#getDefinition(name) { return this.csn.definitions[name] }
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* Propagate draft annotations through inheritance (includes).
|
|
56
|
+
* The latest annotation through the inheritance chain "wins".
|
|
57
|
+
* Annotations on the entity itself are always queued last, so they will always be decisive over ancestors.
|
|
58
|
+
*
|
|
59
|
+
* @param entity {object} - entity to pull draftability from its parents.
|
|
60
|
+
*/
|
|
61
|
+
#propagateInheritance(entity) {
|
|
62
|
+
const annotations = (entity.includes ?? []).map(parent => this.#getDraftable(parent))
|
|
63
|
+
annotations.push(entity[annotation])
|
|
64
|
+
this.#setDraftable(entity, annotations.filter(a => a !== undefined).at(-1) ?? false)
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
/**
|
|
68
|
+
* Propagate draft-enablement through projections.
|
|
69
|
+
*/
|
|
70
|
+
#propagateProjections() {
|
|
71
|
+
const propagate = (from, to) => {
|
|
72
|
+
do {
|
|
73
|
+
this.#setDraftable(to, this.#getDraftable(to) || this.#getDraftable(from))
|
|
74
|
+
from = to
|
|
75
|
+
to = this.#projections[to]
|
|
76
|
+
} while (to)
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
for (let [projection, target] of Object.entries(this.#projections)) {
|
|
80
|
+
propagate(projection, target)
|
|
81
|
+
propagate(target, projection)
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
/**
|
|
86
|
+
* If an entity E is draftable and contains any composition of entities,
|
|
87
|
+
* then those entities also become draftable. Recursively.
|
|
88
|
+
*
|
|
89
|
+
* @param entity {object} - entity to propagate all compositions from.
|
|
90
|
+
*/
|
|
91
|
+
#propagateCompositions(entity) {
|
|
92
|
+
if (!this.#getDraftable(entity)) return
|
|
93
|
+
|
|
94
|
+
for (const comp of Object.values(entity.compositions ?? {})) {
|
|
95
|
+
const target = this.#getDefinition(comp.target)
|
|
96
|
+
const current = this.#getDraftable(target)
|
|
97
|
+
if (!current) {
|
|
98
|
+
this.#setDraftable(target, true)
|
|
99
|
+
this.#propagateCompositions(target)
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
unroll(csn) {
|
|
105
|
+
this.csn = csn
|
|
106
|
+
|
|
107
|
+
// inheritance
|
|
108
|
+
for (const entity of this.#entities) {
|
|
109
|
+
this.#propagateInheritance(entity)
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
// transitivity through compositions
|
|
113
|
+
// we have to do this in a second pass, as we only now know which entities are draft-enables themselves
|
|
114
|
+
for (const entity of this.#entities) {
|
|
115
|
+
this.#propagateCompositions(entity)
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
this.#propagateProjections()
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
// note to self: following doc uses @ homoglyph instead of @, as the latter apparently has special semantics in code listings
|
|
123
|
+
/**
|
|
124
|
+
* We are unrolling the @odata.draft.enabled annotations into related entities manually.
|
|
125
|
+
* This includes three scenarios:
|
|
126
|
+
*
|
|
127
|
+
* (a) aspects via `A: B`, where `B` is draft enabled.
|
|
128
|
+
* Note that when an entity extends two other entities of which one has drafts enabled and
|
|
129
|
+
* one has not, then the one that is later in the list of mixins "wins":
|
|
130
|
+
* @example sdasd
|
|
131
|
+
* ```ts
|
|
132
|
+
* @odata.draft.enabled true
|
|
133
|
+
* entity T {}
|
|
134
|
+
* @odata.draft.enabled false
|
|
135
|
+
* entity F {}
|
|
136
|
+
* entity A: T,F {} // draft not enabled
|
|
137
|
+
* entity B: F,T {} // draft enabled
|
|
138
|
+
* ```
|
|
139
|
+
*
|
|
140
|
+
* (b) Draft enabled projections make the entity we project on draft enabled.
|
|
141
|
+
* @example
|
|
142
|
+
* ```ts
|
|
143
|
+
* @odata.draft.enabled: true
|
|
144
|
+
* entity A as projection on B {}
|
|
145
|
+
* entity B {} // draft enabled
|
|
146
|
+
* ```
|
|
147
|
+
*
|
|
148
|
+
* (c) Entities that are draft enabled propagate this property down through compositions:
|
|
149
|
+
*
|
|
150
|
+
* ```ts
|
|
151
|
+
* @odata.draft.enabled: true
|
|
152
|
+
* entity A {
|
|
153
|
+
* b: Composition of B
|
|
154
|
+
* }
|
|
155
|
+
* entity B {} // draft enabled
|
|
156
|
+
* ```
|
|
157
|
+
*/
|
|
158
|
+
function unrollDraftability(csn) {
|
|
159
|
+
new DraftUnroller().unroll(csn)
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
/**
|
|
163
|
+
* Propagates keys elements through the CSN. This includes
|
|
164
|
+
*
|
|
165
|
+
* (a) keys that are explicitly declared as key in an entity
|
|
166
|
+
* (b) keys from aspects the entity extends
|
|
167
|
+
*
|
|
168
|
+
* This explicit propagation is required to add foreign key relations
|
|
169
|
+
* to referring entities.
|
|
170
|
+
*
|
|
171
|
+
* @example
|
|
172
|
+
* ```cds
|
|
173
|
+
* entity A: cuid { key name: String; }
|
|
174
|
+
* entity B { ref: Association to one A }
|
|
175
|
+
* ```
|
|
176
|
+
* must yield
|
|
177
|
+
* ```ts
|
|
178
|
+
* class A {
|
|
179
|
+
* ID: UUID // inherited from cuid
|
|
180
|
+
* name: String;
|
|
181
|
+
* }
|
|
182
|
+
* class B {
|
|
183
|
+
* ref: Association.to<A>
|
|
184
|
+
* ref_ID: UUID
|
|
185
|
+
* ref_name: String;
|
|
186
|
+
* }
|
|
187
|
+
* ```
|
|
188
|
+
* @returns {{[key: string]: object}}
|
|
189
|
+
*/
|
|
190
|
+
function propagateForeignKeys(csn) {
|
|
191
|
+
for (const element of Object.values(csn.definitions)) {
|
|
192
|
+
Object.defineProperty(element, 'keys', {
|
|
193
|
+
get: function () {
|
|
194
|
+
// cached access to all immediately defined _and_ inherited keys.
|
|
195
|
+
// They need to be explicitly accessible in subclasses to generate
|
|
196
|
+
// foreign key fields from Associations/ Compositions.
|
|
197
|
+
if (!Object.hasOwn(this, '__keys')) {
|
|
198
|
+
const ownKeys = Object.entries(this.elements ?? {}).filter(([,el]) => el.key === true)
|
|
199
|
+
const inheritedKeys = this.includes?.flatMap(parent => Object.entries(csn.definitions[parent].keys)) ?? []
|
|
200
|
+
// not sure why, but .associations contains both Associations, as well as Compositions in CSN.
|
|
201
|
+
// (.compositions contains only Compositions, if any)
|
|
202
|
+
const remoteKeys = Object.entries(this.associations ?? {})
|
|
203
|
+
.filter(([,{key}]) => key) // only follow associations that are keys, that way we avoid cycles
|
|
204
|
+
.flatMap(([kname, key]) => Object.entries(csn.definitions[key.target].keys)
|
|
205
|
+
.map(([ckname, ckey]) => [`${kname}_${ckname}`, ckey]))
|
|
206
|
+
|
|
207
|
+
this.__keys = Object.fromEntries(ownKeys
|
|
208
|
+
.concat(inheritedKeys)
|
|
209
|
+
.concat(remoteKeys)
|
|
210
|
+
.filter(([,ckey]) => !ckey.target) // discard keys that are Associations. Those are already part of .elements
|
|
211
|
+
)
|
|
212
|
+
}
|
|
213
|
+
return this.__keys
|
|
214
|
+
}
|
|
215
|
+
})
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
/**
|
|
220
|
+
* Entities inherit their ancestors annotations:
|
|
221
|
+
* https://cap.cloud.sap/docs/cds/cdl#annotation-propagation
|
|
222
|
+
* This is a problem if we annotate @singular/ @plural to an entity A,
|
|
223
|
+
* as we don't want all descendents B, C, ... to share the ancestor's
|
|
224
|
+
* annotated inflexion
|
|
225
|
+
* -> remove all such annotations that appear in a parent as well.
|
|
226
|
+
* BUT: we can't just delete the attributes. Imagine three classes
|
|
227
|
+
* A <- B <- C
|
|
228
|
+
* where A contains a @singular annotation.
|
|
229
|
+
* If we erase the annotation from B, C will still contain it and
|
|
230
|
+
* can not detect that its own annotation was inherited without
|
|
231
|
+
* travelling up the entire inheritance chain up to A.
|
|
232
|
+
* So instead, we monkey patch and maintain a dictionary "erased"
|
|
233
|
+
* when removing an annotation which we also check.
|
|
234
|
+
* @deprecated since we use the xtended flavour for CSN, we don't need to fix this anymore
|
|
235
|
+
*/
|
|
236
|
+
function propagateInflectionAnnotations(csn) {
|
|
237
|
+
const erase = (entity, parent, attr) => {
|
|
238
|
+
if (attr in entity) {
|
|
239
|
+
const ea = entity[attr]
|
|
240
|
+
if (parent[attr] === ea || (parent.erased && parent.erased[attr] === ea)) {
|
|
241
|
+
entity.erased ??= {}
|
|
242
|
+
entity.erased[attr] = ea
|
|
243
|
+
delete entity[attr]
|
|
244
|
+
//this.logger.info(`Removing inherited attribute ${attr} from ${entity.name}.`)
|
|
245
|
+
}
|
|
246
|
+
}
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
for (const entity of Object.values(csn.definitions)) {
|
|
250
|
+
let i = 0
|
|
251
|
+
while (
|
|
252
|
+
(getSingularAnnotation(entity) || getPluralAnnotation(entity)) &&
|
|
253
|
+
i < (entity.includes ?? []).length
|
|
254
|
+
) {
|
|
255
|
+
const parent = csn.definitions[entity.includes[i]]
|
|
256
|
+
Object.values(annotations).flat().forEach(an => erase(entity, parent, an))
|
|
257
|
+
i++
|
|
258
|
+
}
|
|
259
|
+
}
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
function amendCSN(csn) {
|
|
263
|
+
unrollDraftability(csn)
|
|
264
|
+
propagateForeignKeys(csn)
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
module.exports = { amendCSN }
|
package/lib/file.js
CHANGED
|
@@ -335,6 +335,9 @@ class SourceFile extends File {
|
|
|
335
335
|
.flatMap(([singular, plural, original]) => Array.from(new Set([
|
|
336
336
|
`module.exports.${singular} = csn.${original}`,
|
|
337
337
|
`module.exports.${plural} = csn.${original}`,
|
|
338
|
+
// FIXME: we currently produce at most 3 entries.
|
|
339
|
+
// This could be an issue when the user re-used the original name in a @singular/@plural annotation.
|
|
340
|
+
// Seems unlikely, but we have to eliminate the original entry if users start running into this.
|
|
338
341
|
`module.exports.${original} = csn.${original}`
|
|
339
342
|
])))
|
|
340
343
|
) // singular -> plural aliases
|
package/lib/util.js
CHANGED
|
@@ -184,48 +184,6 @@ const parseCommandlineArgs = (argv, validFlags) => {
|
|
|
184
184
|
}
|
|
185
185
|
}
|
|
186
186
|
|
|
187
|
-
/**
|
|
188
|
-
* Entities inherit their ancestors annotations:
|
|
189
|
-
* https://cap.cloud.sap/docs/cds/cdl#annotation-propagation
|
|
190
|
-
* This is a problem if we annotate @singular/ @plural to an entity A,
|
|
191
|
-
* as we don't want all descendents B, C, ... to share the ancestor's
|
|
192
|
-
* annotated inflexion
|
|
193
|
-
* -> remove all such annotations that appear in a parent as well.
|
|
194
|
-
* BUT: we can't just delete the attributes. Imagine three classes
|
|
195
|
-
* A <- B <- C
|
|
196
|
-
* where A contains a @singular annotation.
|
|
197
|
-
* If we erase the annotation from B, C will still contain it and
|
|
198
|
-
* can not detect that its own annotation was inherited without
|
|
199
|
-
* travelling up the entire inheritance chain up to A.
|
|
200
|
-
* So instead, we monkey patch and maintain a dictionary "erased"
|
|
201
|
-
* when removing an annotation which we also check.
|
|
202
|
-
*/
|
|
203
|
-
function fixCSN(csn) {
|
|
204
|
-
const erase = (entity, parent, attr) => {
|
|
205
|
-
if (attr in entity) {
|
|
206
|
-
const ea = entity[attr]
|
|
207
|
-
if (parent[attr] === ea || (parent.erased && parent.erased[attr] === ea)) {
|
|
208
|
-
entity.erased ??= {}
|
|
209
|
-
entity.erased[attr] = ea
|
|
210
|
-
delete entity[attr]
|
|
211
|
-
//this.logger.info(`Removing inherited attribute ${attr} from ${entity.name}.`)
|
|
212
|
-
}
|
|
213
|
-
}
|
|
214
|
-
}
|
|
215
|
-
|
|
216
|
-
for (const entity of Object.values(csn.definitions)) {
|
|
217
|
-
let i = 0
|
|
218
|
-
while (
|
|
219
|
-
(getSingularAnnotation(entity) || getPluralAnnotation(entity)) &&
|
|
220
|
-
i < (entity.includes ?? []).length
|
|
221
|
-
) {
|
|
222
|
-
const parent = csn.definitions[entity.includes[i]]
|
|
223
|
-
Object.values(annotations).flat().forEach(an => erase(entity, parent, an))
|
|
224
|
-
i++
|
|
225
|
-
}
|
|
226
|
-
}
|
|
227
|
-
}
|
|
228
|
-
|
|
229
187
|
module.exports = {
|
|
230
188
|
annotations,
|
|
231
189
|
getSingularAnnotation,
|
|
@@ -234,6 +192,5 @@ module.exports = {
|
|
|
234
192
|
singular4,
|
|
235
193
|
plural4,
|
|
236
194
|
parseCommandlineArgs,
|
|
237
|
-
deepMerge
|
|
238
|
-
fixCSN
|
|
195
|
+
deepMerge
|
|
239
196
|
}
|
package/lib/visitor.js
CHANGED
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
const util = require('./util')
|
|
4
4
|
|
|
5
|
+
const { amendCSN } = require('./csn')
|
|
5
6
|
const { SourceFile, baseDefinitions, Buffer } = require('./file')
|
|
6
7
|
const { FlatInlineDeclarationResolver, StructuredInlineDeclarationResolver } = require('./components/inline')
|
|
7
8
|
const { Resolver } = require('./components/resolver')
|
|
@@ -59,6 +60,7 @@ class Visitor {
|
|
|
59
60
|
* @param {VisitorOptions} options
|
|
60
61
|
*/
|
|
61
62
|
constructor(csn, options = {}, logger = new Logger()) {
|
|
63
|
+
amendCSN(csn)
|
|
62
64
|
this.options = { ...defaults, ...options }
|
|
63
65
|
this.logger = logger
|
|
64
66
|
this.csn = csn
|
|
@@ -127,13 +129,21 @@ class Visitor {
|
|
|
127
129
|
buffer.indent()
|
|
128
130
|
buffer.add(`return class ${clean} extends Base {`)
|
|
129
131
|
buffer.indent()
|
|
132
|
+
|
|
130
133
|
for (const [ename, element] of Object.entries(entity.elements ?? {})) {
|
|
131
134
|
this.visitElement(ename, element, file, buffer)
|
|
135
|
+
|
|
132
136
|
// make foreign keys explicit
|
|
133
|
-
|
|
134
|
-
|
|
137
|
+
if ('target' in element) {
|
|
138
|
+
// lookup in cds.definitions can fail for inline structs.
|
|
139
|
+
// We don't really have to care for this case, as keys from such structs are _not_ propagated to
|
|
140
|
+
// the containing entity.
|
|
141
|
+
for (const [kname, kelement] of Object.entries(this.csn.definitions[element.target]?.keys ?? {})) {
|
|
142
|
+
this.visitElement(`${ename}_${kname}`, kelement, file, buffer)
|
|
143
|
+
}
|
|
135
144
|
}
|
|
136
|
-
}
|
|
145
|
+
}
|
|
146
|
+
|
|
137
147
|
for (const [aname, action] of Object.entries(entity.actions ?? {})) {
|
|
138
148
|
buffer.add(
|
|
139
149
|
SourceFile.stringifyLambda({
|
|
@@ -167,11 +177,19 @@ class Visitor {
|
|
|
167
177
|
`${baseDefinitions.path.asIdentifier()}.Entity`
|
|
168
178
|
)
|
|
169
179
|
|
|
170
|
-
buffer.add(`export class ${identSingular(clean)} extends ${rhs} {}`)
|
|
180
|
+
buffer.add(`export class ${identSingular(clean)} extends ${rhs} {${this.#staticClassContents(clean, entity).join('\n')}}`)
|
|
171
181
|
//buffer.add(`export type ${clean} = InstanceType<typeof ${identSingular(clean)}>`)
|
|
172
182
|
this.contexts.pop()
|
|
173
183
|
}
|
|
174
184
|
|
|
185
|
+
#isDraftEnabled(entity) {
|
|
186
|
+
return entity['@odata.draft.enabled'] === true
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
#staticClassContents(clean, entity) {
|
|
190
|
+
return this.#isDraftEnabled(entity) ? [`static drafts: typeof ${clean}`] : []
|
|
191
|
+
}
|
|
192
|
+
|
|
175
193
|
#printEntity(name, entity) {
|
|
176
194
|
const clean = this.resolver.trimNamespace(name)
|
|
177
195
|
const ns = this.resolver.resolveNamespace(name.split('.'))
|
|
@@ -207,7 +225,8 @@ class Visitor {
|
|
|
207
225
|
// to have Books.texts = Books.text, so we derive the singular once more without cutting off the ns.
|
|
208
226
|
// Directly deriving it from the plural makes sure we retain any parent namespaces of kind "entity",
|
|
209
227
|
// which would not be possible while already in singular form, as "Book.text" could not be resolved in CSN.
|
|
210
|
-
|
|
228
|
+
// edge case: @singular annotation present. singular4 will take care of that.
|
|
229
|
+
file.addInflection(util.singular4(entity, true), plural, clean)
|
|
211
230
|
if ('doc' in entity) {
|
|
212
231
|
docify(entity.doc).forEach((d) => buffer.add(d))
|
|
213
232
|
}
|
|
@@ -221,7 +240,7 @@ class Visitor {
|
|
|
221
240
|
}
|
|
222
241
|
// plural can not be a type alias to $singular[] but needs to be a proper class instead,
|
|
223
242
|
// so it can get passed as value to CQL functions.
|
|
224
|
-
buffer.add(`export class ${plural} extends Array<${singular}> {}`)
|
|
243
|
+
buffer.add(`export class ${plural} extends Array<${singular}> {${this.#staticClassContents(singular, entity).join('\n')}}`)
|
|
225
244
|
buffer.add('')
|
|
226
245
|
}
|
|
227
246
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@cap-js/cds-typer",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.9.0",
|
|
4
4
|
"description": "Generates .ts files for a CDS model to receive code completion in VS Code",
|
|
5
5
|
"main": "index.js",
|
|
6
6
|
"repository": "github:cap-js/cds-typer",
|
|
@@ -22,10 +22,7 @@
|
|
|
22
22
|
"doc:clean": "rm -rf ./doc",
|
|
23
23
|
"doc:prepare": "npm run doc:clean && mkdir -p doc/types",
|
|
24
24
|
"doc:typegen": "./node_modules/.bin/tsc ./lib/*.js --skipLibCheck --declaration --allowJs --emitDeclarationOnly --outDir doc/types && cd doc/types && tsc --init",
|
|
25
|
-
"doc:
|
|
26
|
-
"doc:md": "npm run doc:typegen && ./node_modules/.bin/typedoc --plugin typedoc-plugin-markdown 'doc/types/compile.d.ts' --out doc/md --tsconfig doc/types/tsconfig.json",
|
|
27
|
-
"doc:cli": "npm run cli -- --help > ./doc/cli.txt",
|
|
28
|
-
"doc:full": "npm run doc:prepare && npm run doc:html && npm run doc:cli"
|
|
25
|
+
"doc:cli": "npm run cli -- --help > ./doc/cli.txt"
|
|
29
26
|
},
|
|
30
27
|
"files": [
|
|
31
28
|
"lib/",
|
|
@@ -43,12 +40,11 @@
|
|
|
43
40
|
"@sap/cds": ">=6"
|
|
44
41
|
},
|
|
45
42
|
"devDependencies": {
|
|
43
|
+
"acorn": "^8.10.0",
|
|
46
44
|
"eslint": "^8.15.0",
|
|
47
45
|
"eslint-config-prettier": "^8.5.0",
|
|
48
46
|
"eslint-plugin-prettier": "^4.0.0",
|
|
49
47
|
"jest": "^29",
|
|
50
|
-
"typedoc": "^0.24.8",
|
|
51
|
-
"typedoc-plugin-markdown": "^3.15.3",
|
|
52
48
|
"typescript": ">=4.6.4"
|
|
53
49
|
},
|
|
54
50
|
"jest": {
|