@cap-js/cds-typer 0.30.0 → 0.32.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 +80 -57
- package/lib/components/basedefs.js +3 -1
- package/lib/components/enum.js +3 -3
- package/lib/components/typescript.js +1 -1
- package/lib/config.js +4 -2
- package/lib/csn.js +47 -1
- package/lib/file.js +40 -9
- package/lib/printers/javascript.js +14 -3
- package/lib/printers/wrappers.js +11 -2
- package/lib/resolution/entity.js +18 -0
- package/lib/resolution/resolver.js +19 -20
- package/lib/typedefs.d.ts +5 -2
- package/lib/util.js +2 -2
- package/lib/visitor.js +59 -92
- package/package.json +2 -2
package/CHANGELOG.md
CHANGED
|
@@ -1,12 +1,41 @@
|
|
|
1
|
-
#
|
|
1
|
+
# Changelog
|
|
2
2
|
|
|
3
3
|
All notable changes to this project will be documented in this file.
|
|
4
|
-
This project adheres to [Semantic Versioning](http://semver.org/).
|
|
5
|
-
The format is based on [Keep a Changelog](http://keepachangelog.com/).
|
|
6
4
|
|
|
7
|
-
##
|
|
5
|
+
## [Unreleased]
|
|
8
6
|
|
|
9
|
-
|
|
7
|
+
### Added
|
|
8
|
+
### Changed
|
|
9
|
+
### Deprecated
|
|
10
|
+
### Removed
|
|
11
|
+
### Fixed
|
|
12
|
+
### Security
|
|
13
|
+
|
|
14
|
+
## [0.32.0] - 2025-01-14
|
|
15
|
+
|
|
16
|
+
### Added
|
|
17
|
+
- dedicated classes for inline compositions
|
|
18
|
+
- dedicated text-classes for entities with `localized` elements
|
|
19
|
+
|
|
20
|
+
### Changed
|
|
21
|
+
- prefixed builtin types like `Promise` and `Record` with `globalThis.`, to allow using names of builtin types for entities without collisions
|
|
22
|
+
- bumped peer-dependency to `@cap-js/cds-types` to `>=0.9`
|
|
23
|
+
### Deprecated
|
|
24
|
+
### Removed
|
|
25
|
+
### Fixed
|
|
26
|
+
- referencing another entity's property of type `cds.String` in an enum will now properly quote the generated values
|
|
27
|
+
### Security
|
|
28
|
+
|
|
29
|
+
## [0.31.0] - 2024-12-16
|
|
30
|
+
### Fixed
|
|
31
|
+
- type-referencing a property that is a key no longer breaks the referring property
|
|
32
|
+
- when targeting ESM, all imports within the generated types now add a `/index.js`-suffix to conform to modern module resolution mechanisms
|
|
33
|
+
- leaving `target_module_type` at `'auto'` now properly acts on a detected `"type":"module"`
|
|
34
|
+
|
|
35
|
+
### Added
|
|
36
|
+
- cds aspects now generate a synthetic plural type too, to be used in `composition of many`
|
|
37
|
+
|
|
38
|
+
## [0.30.0] - 2024-12-02
|
|
10
39
|
|
|
11
40
|
### Changed
|
|
12
41
|
- [breaking] when running cds-typer in a CAP project, the default for the `outputDirectory` option will be `./@cds-models` instead of `./`. This default takes the lowest precedence after setting it in the project's `cds.env`, or explicitly as CLI argument.
|
|
@@ -14,20 +43,19 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/).
|
|
|
14
43
|
### Fixed
|
|
15
44
|
- cds-typer no longer ignores the selected `outputDirectory`, which would also cause an issue during build
|
|
16
45
|
|
|
17
|
-
|
|
18
|
-
## Version 0.29.0 - 2024-11-20
|
|
46
|
+
## [0.29.0] - 2024-11-20
|
|
19
47
|
### Added
|
|
20
|
-
- [breaking] cds-typer now tries to automatically detect whether it has to generate ESM or CommonJS in the emitted _index.js_ files. This behaviour can be overridden via the `--targetModuleType` option. _If you rely on these generated index.js files to be CJS despite your project being of ESM type, you need to manually tell cds-typer to generate CJS files!_
|
|
48
|
+
- [breaking] cds-typer now tries to automatically detect whether it has to generate ESM or CommonJS in the emitted _index.js_ files. This behaviour can be overridden via the `--targetModuleType` option. _If you rely on these generated index.js files to be CJS despite your project being of ESM type, you need to manually tell cds-typer to generate CJS files!_
|
|
21
49
|
|
|
22
50
|
### Fixed
|
|
23
51
|
- The static `.keys` property now properly reels in key types from inherited classes.
|
|
24
52
|
|
|
25
|
-
##
|
|
53
|
+
## [0.28.1] - 2024-11-07
|
|
26
54
|
### Fixed
|
|
27
55
|
- `cds build` no longer fails on Windows with an `EINVAL` error.
|
|
28
56
|
- `cds build` also supports custom model paths in `tsconfig.json` that do not end with `/index.ts`. This is the case for projects running with `tsx`.
|
|
29
57
|
|
|
30
|
-
##
|
|
58
|
+
## [0.28.0] - 24-10-24
|
|
31
59
|
### Added
|
|
32
60
|
- Schema definition for `cds.typer` options in `package.json` and `.cdsrc-*.json` files
|
|
33
61
|
- Added a static `elements` property to all entities, which allows access to the `LinkedDefinitions` instance of an entity's elements
|
|
@@ -40,7 +68,7 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/).
|
|
|
40
68
|
- Properly support mandatory (`not null`) action parameters with `array of` types
|
|
41
69
|
- Static property `.drafts` is only create for entity classes that are actually draft enabled
|
|
42
70
|
|
|
43
|
-
##
|
|
71
|
+
## [0.27.0] - 2024-10-02
|
|
44
72
|
### Changed
|
|
45
73
|
- Any configuration variable (via CLI or `cds.env`) can now be passed in snake_case in addition to camelCase
|
|
46
74
|
- Action parameters are now generated as optional by default, which is how the runtime treats them. Mandatory parameters have to be marked as `not null` in CDS/CDL, or `notNull` in CSN.
|
|
@@ -50,7 +78,7 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/).
|
|
|
50
78
|
- Fix a bug where cds-typer would produce redundant type declarations when the model contains an associations to another entity's property
|
|
51
79
|
- Reintroduce default value `'.'` for `--outputDirectory`
|
|
52
80
|
|
|
53
|
-
##
|
|
81
|
+
## [0.26.0] - 2024-09-11
|
|
54
82
|
### Added
|
|
55
83
|
- Added a static `.keys` property in all entities. That property is dictionary which holds all properties as keys that are marked as `key` in CDS
|
|
56
84
|
- Added a CLI option `--useEntitiesProxy`. When set to `true`, all entities are wrapped into `Proxy` objects during runtime, allowing top level imports of entity types.
|
|
@@ -63,8 +91,7 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/).
|
|
|
63
91
|
- Services now have their own export (named like the service itself). The current default export is not usable in some scenarios from CommonJS modules.
|
|
64
92
|
- Enums and operation parameters can have doc comments
|
|
65
93
|
|
|
66
|
-
|
|
67
|
-
## Version 0.25.0 - 2024-08-13
|
|
94
|
+
## [0.25.0] - 2024-08-13
|
|
68
95
|
### Added
|
|
69
96
|
- Declaring a type alias on an enum in cds now also exports it on value level in the resulting type
|
|
70
97
|
|
|
@@ -75,7 +102,7 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/).
|
|
|
75
102
|
- All properties are now preceeded with the `declare` modifier to pass strict tsconfigs using `useDefineForClassFields` or `noImplicitOverride`
|
|
76
103
|
- The static `actions` property of generated classes now includes the types from all inherited classes to also suggest actions defined in a base entity/aspect/type.
|
|
77
104
|
|
|
78
|
-
##
|
|
105
|
+
## [0.24.0] - 2024-07-18
|
|
79
106
|
### Fixed
|
|
80
107
|
- Suppressed an error that would incorrectly point out naming clashes when an entity was named in singular inflection in the model
|
|
81
108
|
- CDS aspects now also generate a aspect-function in singular inflection, similar to how entities do
|
|
@@ -84,14 +111,13 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/).
|
|
|
84
111
|
- Aspects generate named classes again so that tooltips will show more meaningful provenance for properties
|
|
85
112
|
- The TypeScript task for `cds build` no longer looks for tsconfig.json to determine if the project has TS nature and instead checks the dependencies in the project's package.json for an occurrence of `typescript`
|
|
86
113
|
|
|
87
|
-
##
|
|
114
|
+
## [0.23.0] - 2024-07-04
|
|
88
115
|
|
|
89
116
|
### Fixed
|
|
90
117
|
- Plurals no longer have `is_singular` attached in the resulting .js files
|
|
91
118
|
- Properties are properly propagated beyond just one level of inheritance
|
|
92
119
|
|
|
93
|
-
|
|
94
|
-
## Version 0.22.0 - 2024-06-20
|
|
120
|
+
## [0.22.0] - 2024-06-20
|
|
95
121
|
|
|
96
122
|
### Fixed
|
|
97
123
|
- Fixed a bug where keys would sometimes inconsistently become nullable
|
|
@@ -99,16 +125,15 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/).
|
|
|
99
125
|
### Changed
|
|
100
126
|
- Logging now internally uses `cds.log` and pipes output into the `cds-typer` logger, which can be configured via `cds.env` in addition to explicitly passing a `--logLevel` parameter to CLI. Users now have to use the levels defined in [`cds.log.levels`](https://cap.cloud.sap/docs/node.js/cds-log#log-levels). The formerly valid levels `WARNING`, `CRITICAL`, and `NONE` are now deprecated and automatically mapped to valid levels for now.
|
|
101
127
|
|
|
102
|
-
##
|
|
128
|
+
## [0.21.2] - 2024-06-06
|
|
103
129
|
### Fixed
|
|
104
130
|
- The typescript build task will no longer attempt to run unless at least cds 8 is installed
|
|
105
131
|
|
|
106
|
-
##
|
|
132
|
+
## [0.21.1] - 2024-06-03
|
|
107
133
|
### Fixed
|
|
108
134
|
- Added missing _cds-plugin.js_ to exported files to properly enable calling `cds build --for typescript`
|
|
109
135
|
|
|
110
|
-
|
|
111
|
-
## Version 0.21.0 - 2024-05-31
|
|
136
|
+
## [0.21.0] - 2024-05-31
|
|
112
137
|
### Added
|
|
113
138
|
- Added `IEEE754Compatible` flag which, when set to `true`, generates decimal fields as `(number | string)` instead of `number`. This flag will be removed in the long run
|
|
114
139
|
- Added plugin to `cds build` TypeScript projects. Can be explicitly called using `cds build --for typescript`
|
|
@@ -122,15 +147,15 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/).
|
|
|
122
147
|
- Entities ending with an "s" are no longer incorrectly truncated within `extends`-clauses
|
|
123
148
|
- Entity names prefixed with their own namespace (e.g. `Name.Name`, `Name.NameAttachments`) are not stripped of their name prefix
|
|
124
149
|
|
|
125
|
-
##
|
|
150
|
+
## [0.20.2] - 2024-04-29
|
|
126
151
|
### Fixed
|
|
127
152
|
- Referring to a property's type in a function/ action parameter no longer refers to the enclosing entity
|
|
128
153
|
|
|
129
|
-
##
|
|
154
|
+
## [0.20.1] - 2024-04-24
|
|
130
155
|
### Fixed
|
|
131
156
|
- Void actions no longer crash the type generation process
|
|
132
157
|
|
|
133
|
-
##
|
|
158
|
+
## [0.20.0] - 2024-04-23
|
|
134
159
|
### Added
|
|
135
160
|
- Types for actions and functions now expose a `.kind` property which holds the string `'function'` or `'action'` respectively
|
|
136
161
|
- Added the `CdsDate`, `CdsDateTime`, `CdsTime`, `CdsTimestamp` types, which are each represented as a `string`.
|
|
@@ -146,32 +171,32 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/).
|
|
|
146
171
|
- Aspects are now consistently named and called in their singular form
|
|
147
172
|
- Only detect inflection clash if singular and plural share the same namespace. This also no longer reports `sap.common` as erroneous during type creation
|
|
148
173
|
|
|
149
|
-
##
|
|
174
|
+
## [0.19.0] - 2024-03-28
|
|
150
175
|
### Added
|
|
151
176
|
- Support for `cds.Vector`, which will be represented as `string`
|
|
152
177
|
|
|
153
|
-
##
|
|
178
|
+
## [0.18.2] - 2024-03-21
|
|
154
179
|
### Fixed
|
|
155
180
|
- Resolving `@sap/cds` will now look in the CWD first to ensure a consistent use the same CDS version across different setups
|
|
156
181
|
- Types of function parameters starting with `cds.` are not automatically considered builtin anymore and receive a more thorough check against an allow-list
|
|
157
182
|
|
|
158
183
|
|
|
159
|
-
##
|
|
184
|
+
## [0.18.1] - 2024-03-13
|
|
160
185
|
### Fix
|
|
161
186
|
- Remove faulty plural for CDS `type` definitions from the generated _index.js_ files
|
|
162
187
|
|
|
163
|
-
##
|
|
188
|
+
## [0.18.0] - 2024-03-12
|
|
164
189
|
### Added
|
|
165
190
|
- Improved support for projections, including projections on inline definitions, and on views, as well as support for explicit exclusion and selection of properties
|
|
166
191
|
|
|
167
192
|
### Changed
|
|
168
193
|
- [breaking] CDS `type` definitions will not be inflected. Whatever inflection you define them in will be assumed treated as a singular form and will not receive a plural form anymore
|
|
169
194
|
|
|
170
|
-
##
|
|
195
|
+
## [0.17.0] - 2024-03-05
|
|
171
196
|
### Fixed
|
|
172
197
|
- Fixed a bug where refering to an externally defined enum via the `typeof` syntax would crash the type generation
|
|
173
198
|
|
|
174
|
-
##
|
|
199
|
+
## [0.16.0] - 2024-02-01
|
|
175
200
|
### Changed
|
|
176
201
|
- Changed default log level from `NONE` to `ERROR`. See the doc to manually pass in another log level for cds-typer runs
|
|
177
202
|
- Name collisions between automatically generated foreign key fields (`.…_ID`, `.…_code`, etc.) with explicitly named fields will now raise an error
|
|
@@ -180,7 +205,7 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/).
|
|
|
180
205
|
### Fixed
|
|
181
206
|
- Externally defined enums can now be used as parameter types in actions
|
|
182
207
|
|
|
183
|
-
##
|
|
208
|
+
## [0.15.0] - 2023-12-21
|
|
184
209
|
### Added
|
|
185
210
|
- Support for [scoped entities](https://cap.cloud.sap/docs/cds/cdl#scoped-names)
|
|
186
211
|
- Support for [delimited identifiers](https://cap.cloud.sap/docs/cds/cdl#delimited-identifiers)
|
|
@@ -191,18 +216,18 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/).
|
|
|
191
216
|
- Arrays of inline enum values can now be used as action parameters too. But they will only be represented by their enclosing type for now, i.e. `string`, `number`, etc.
|
|
192
217
|
- Foreign keys of projection entities are now propagated as well
|
|
193
218
|
|
|
194
|
-
##
|
|
219
|
+
## [0.14.0] - 2023-12-13
|
|
195
220
|
### Added
|
|
196
221
|
- Entities that are database views now also receive typings
|
|
197
222
|
|
|
198
|
-
##
|
|
223
|
+
## [0.13.0] - 2023-12-06
|
|
199
224
|
### Changed
|
|
200
225
|
- Enums are now generated ecplicitly in the respective _index.js_ files and don't have to extract their values from the model at runtime anymore
|
|
201
226
|
|
|
202
227
|
### Added
|
|
203
228
|
- The `excluding` clause in projections now actually excludes the specified properties in the generated types
|
|
204
229
|
|
|
205
|
-
##
|
|
230
|
+
## [0.12.0] - 2023-11-23
|
|
206
231
|
|
|
207
232
|
### Changed
|
|
208
233
|
- Generate `cds.LargeBinary` as string, buffer, _or readable_ in the case of media content
|
|
@@ -213,7 +238,7 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/).
|
|
|
213
238
|
### Fixed
|
|
214
239
|
- Now using names of enum values in generated _index.js_ files if no explicit value is present
|
|
215
240
|
|
|
216
|
-
##
|
|
241
|
+
## [0.11.1] - 2023-10-12
|
|
217
242
|
|
|
218
243
|
### Changed
|
|
219
244
|
|
|
@@ -221,7 +246,7 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/).
|
|
|
221
246
|
### Fixed
|
|
222
247
|
- Fixed how service names are exported as default export
|
|
223
248
|
|
|
224
|
-
##
|
|
249
|
+
## [0.11.0] - 2023-10-10
|
|
225
250
|
|
|
226
251
|
### Changed
|
|
227
252
|
|
|
@@ -237,7 +262,7 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/).
|
|
|
237
262
|
### Removed
|
|
238
263
|
- `compileFromCSN` is no longer part of the package's API
|
|
239
264
|
|
|
240
|
-
##
|
|
265
|
+
## [0.10.0] - 2023-09-21
|
|
241
266
|
|
|
242
267
|
### Changed
|
|
243
268
|
- Actions and functions are now attached to a static `.actions` property of each generated class. This reflects the runtime behaviour better than the former way of generating instance methods
|
|
@@ -246,7 +271,7 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/).
|
|
|
246
271
|
|
|
247
272
|
### Fixed
|
|
248
273
|
|
|
249
|
-
##
|
|
274
|
+
## [0.9.0] - 2023-09-08
|
|
250
275
|
|
|
251
276
|
### Changed
|
|
252
277
|
|
|
@@ -256,8 +281,7 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/).
|
|
|
256
281
|
### Fixed
|
|
257
282
|
- Foreign keys are now propagated more than one level (think: `x_ID_ID_ID`)
|
|
258
283
|
|
|
259
|
-
|
|
260
|
-
## Version 0.8.0 - 2023-09-05
|
|
284
|
+
## [0.8.0] - 2023-09-05
|
|
261
285
|
|
|
262
286
|
### Changed
|
|
263
287
|
|
|
@@ -267,8 +291,7 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/).
|
|
|
267
291
|
- Foreign keys that are inherited via aspects are now also generated in addition to the resolved property (see 0.7.0)
|
|
268
292
|
- Explicitly annotated `@singular` and `@plural` names are now properly used in generated _index.js_ files
|
|
269
293
|
|
|
270
|
-
|
|
271
|
-
## Version 0.7.0 - 2023-08-22
|
|
294
|
+
## [0.7.0] - 2023-08-22
|
|
272
295
|
|
|
273
296
|
### Changed
|
|
274
297
|
|
|
@@ -277,7 +300,7 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/).
|
|
|
277
300
|
- Foreign keys are now present in the generated types in addition to the resolved property
|
|
278
301
|
|
|
279
302
|
### Fixed
|
|
280
|
-
##
|
|
303
|
+
## [0.6.1] - 2023-08-10
|
|
281
304
|
|
|
282
305
|
### Changed
|
|
283
306
|
|
|
@@ -286,7 +309,7 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/).
|
|
|
286
309
|
### Fixed
|
|
287
310
|
- Removed a warning about circular imports
|
|
288
311
|
|
|
289
|
-
##
|
|
312
|
+
## [0.6.0] - 2023-08-07
|
|
290
313
|
|
|
291
314
|
### Added
|
|
292
315
|
- Support for `event` syntax
|
|
@@ -296,7 +319,7 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/).
|
|
|
296
319
|
- Add leading underscore to appease `noUnusedParameters` in strict tsconfigs
|
|
297
320
|
- No longer inflect `type` definitions when they are referenced within entities or other type definitions
|
|
298
321
|
|
|
299
|
-
##
|
|
322
|
+
## [0.5.0] - 2023-07-25
|
|
300
323
|
|
|
301
324
|
### Changed
|
|
302
325
|
- Facilitate strict property checks. Note: `checkJs: true` must be present in the project's _jsconfig.json_ or _tsconfig.json_ respectively for this feature to become effective
|
|
@@ -308,12 +331,11 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/).
|
|
|
308
331
|
- Generate `string` type for date-related types in CDS definitions
|
|
309
332
|
- Generate `Buffer | string` type for the CDS type `LargeBinary`
|
|
310
333
|
|
|
311
|
-
|
|
312
|
-
## Version 0.4.0 - 2023-07-06
|
|
334
|
+
## [0.4.0] - 2023-07-06
|
|
313
335
|
### Added
|
|
314
336
|
- Support for enums when they are defined separately (not inline in the property type of an entity)
|
|
315
337
|
|
|
316
|
-
##
|
|
338
|
+
## [0.3.0] - 2023-06-26
|
|
317
339
|
### Added
|
|
318
340
|
- Support `function` definitions (apart from `action`s)
|
|
319
341
|
### Changed
|
|
@@ -322,25 +344,25 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/).
|
|
|
322
344
|
### Fixed
|
|
323
345
|
- Properly import CDS `type` definitions when they are referenced elsewhere
|
|
324
346
|
|
|
325
|
-
##
|
|
347
|
+
## [0.2.5-beta.1] - 2023-06-13
|
|
326
348
|
|
|
327
349
|
### Changed
|
|
328
350
|
- Bump version
|
|
329
351
|
|
|
330
|
-
##
|
|
352
|
+
## [0.2.4] - 2023-06-12
|
|
331
353
|
- Enable use of annotated singular/ plural names in associations/ compositions
|
|
332
354
|
- Rename package from `@sap/cds-dts-generator` to `@cap-js/cds-typer`
|
|
333
355
|
|
|
334
|
-
##
|
|
356
|
+
## [0.2.3] - 2023-05-17
|
|
335
357
|
- Add missing library files
|
|
336
358
|
|
|
337
|
-
##
|
|
359
|
+
## [0.2.2] - 2023-05-17
|
|
338
360
|
- Make class hierarchy flatter
|
|
339
361
|
|
|
340
|
-
##
|
|
362
|
+
## [0.2.1] - 2023-05-16
|
|
341
363
|
- Add missing files
|
|
342
364
|
|
|
343
|
-
##
|
|
365
|
+
## [0.2.0] - 2023-05-15
|
|
344
366
|
- use native Typescript AST in unit tests
|
|
345
367
|
- add `propertiesOptional` flag
|
|
346
368
|
- support flat, as well as nested inline declarations
|
|
@@ -351,9 +373,10 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/).
|
|
|
351
373
|
- enable use of additional type libraries (HANA types available as first library)
|
|
352
374
|
- provide proper JSDoc for all modules
|
|
353
375
|
- export entity types for singular variants alongside plural types
|
|
354
|
-
|
|
376
|
+
|
|
377
|
+
## [0.1.1] - 2023-01-26
|
|
355
378
|
- add TL;DR section to README
|
|
356
379
|
- allow multiple positional arguments
|
|
357
380
|
|
|
358
|
-
##
|
|
381
|
+
## [0.1.0] - 2023-01-01
|
|
359
382
|
- initial code base
|
|
@@ -53,7 +53,7 @@ export type DraftOf<T> = { new(...args: any[]): DraftEntity<T> }
|
|
|
53
53
|
export type DraftsOf<T> = typeof Array<DraftEntity<T>>
|
|
54
54
|
|
|
55
55
|
export type DeepRequired<T> = {
|
|
56
|
-
[K in keyof T]: DeepRequired<T[K]
|
|
56
|
+
[K in keyof T]: DeepRequired<Unkey<T[K]>>
|
|
57
57
|
} & Exclude<Required<T>, null>;
|
|
58
58
|
|
|
59
59
|
const key = Symbol('key') // to avoid .key showing up in IDE's auto-completion
|
|
@@ -63,6 +63,8 @@ export type KeysOf<T> = {
|
|
|
63
63
|
[K in keyof T as NonNullable<T[K]> extends Key<unknown> ? K : never]-?: Key<{}> // T[K]
|
|
64
64
|
}
|
|
65
65
|
|
|
66
|
+
export type Unkey<T> = T extends Key<infer U> ? U : T
|
|
67
|
+
|
|
66
68
|
/**
|
|
67
69
|
* Dates and timestamps are strings during runtime, so cds-typer represents them as such.
|
|
68
70
|
*/
|
package/lib/components/enum.js
CHANGED
|
@@ -67,7 +67,7 @@ function printEnum(buffer, name, kvs, options = {}, doc=[]) {
|
|
|
67
67
|
* Converts a CSN type describing an enum into a list of kv-pairs.
|
|
68
68
|
* Values from CSN are unwrapped from their `.val` structure and
|
|
69
69
|
* will fall back to the key if no value is provided.
|
|
70
|
-
* @param {import('../typedefs').resolver.EnumCSN} enumCsn - the CSN type describing the enum
|
|
70
|
+
* @param {import('../typedefs').resolver.EnumCSN & { resolvedType?: string }} enumCsn - the CSN type describing the enum
|
|
71
71
|
* @param {{unwrapVals: boolean} | {}} options - if `unwrapVals` is passed,
|
|
72
72
|
* then the CSN structure `{val:x}` is flattened to just `x`.
|
|
73
73
|
* Retaining `val` is closer to the actual CSN structure and should be used where we want
|
|
@@ -81,10 +81,10 @@ function printEnum(buffer, name, kvs, options = {}, doc=[]) {
|
|
|
81
81
|
* csnToEnumPairs(csn, {unwrapVals: false}) // -> [['X', {val:'a'}], ['Y': {val:'b'}], ['Z':'Z']]
|
|
82
82
|
* ```
|
|
83
83
|
*/
|
|
84
|
-
const csnToEnumPairs = ({enum: enm, type}, options = {}) => {
|
|
84
|
+
const csnToEnumPairs = ({enum: enm, type, resolvedType}, options = {}) => {
|
|
85
85
|
const actualOptions = {...{unwrapVals: true}, ...options}
|
|
86
86
|
return Object.entries(enm).map(([k, v]) => {
|
|
87
|
-
const val = enumVal(k, v.val, type)
|
|
87
|
+
const val = enumVal(k, v.val, resolvedType ?? type) // if type is a ref, prefer the resolvedType to catch references to cds.Strings
|
|
88
88
|
return [k, (actualOptions.unwrapVals ? val : { val })]
|
|
89
89
|
})
|
|
90
90
|
}
|
package/lib/config.js
CHANGED
|
@@ -59,13 +59,15 @@ class Config {
|
|
|
59
59
|
this.proxy = camelSnakeHybrid(this.values)
|
|
60
60
|
if (this.proxy.targetModuleType === 'auto') {
|
|
61
61
|
const type = getProjectTargetType(cds.root)
|
|
62
|
+
let detectedType = 'cjs'
|
|
62
63
|
if (type) {
|
|
63
64
|
LOG.info(`automatically detected module type '${type}' in ${cds.root}`)
|
|
64
|
-
|
|
65
|
+
detectedType = type
|
|
65
66
|
} else {
|
|
66
67
|
LOG.warn(`target module type was set to 'auto', but could not detect module type in ${cds.root}. Falling back to cjs`)
|
|
67
|
-
this.values.targetModuleType = 'cjs'
|
|
68
68
|
}
|
|
69
|
+
this.values.targetModuleType = detectedType
|
|
70
|
+
this.proxy.targetModuleType = detectedType
|
|
69
71
|
}
|
|
70
72
|
}
|
|
71
73
|
|
package/lib/csn.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
const { LOG } = require('./logging')
|
|
2
|
+
const { annotations } = require('./util')
|
|
2
3
|
|
|
3
4
|
const DRAFT_ENABLED_ANNO = '@odata.draft.enabled'
|
|
4
5
|
/** @type {string[]} */
|
|
@@ -292,6 +293,30 @@ function propagateForeignKeys(csn) {
|
|
|
292
293
|
}
|
|
293
294
|
}
|
|
294
295
|
|
|
296
|
+
/**
|
|
297
|
+
* Clears "correct" singular/plural annotations from inferred model
|
|
298
|
+
* copies the ones from the xtended model.
|
|
299
|
+
*
|
|
300
|
+
* This is done to prevent potential duplicate class names because of annotation propagation.
|
|
301
|
+
* @param {{inferred: CSN, xtended: CSN}} csn - CSN models
|
|
302
|
+
*/
|
|
303
|
+
function propagateInflectionAnnotations(csn) {
|
|
304
|
+
const singularAnno = annotations.singular[0]
|
|
305
|
+
const pluralAnno = annotations.plural[0]
|
|
306
|
+
for (const [name, def] of Object.entries(csn.inferred.definitions)) {
|
|
307
|
+
const xtendedDef = csn.xtended.definitions[name]
|
|
308
|
+
// we keep the annotations from definition specific to the inferred model (e.g. inline compositions)
|
|
309
|
+
if (!xtendedDef) continue
|
|
310
|
+
|
|
311
|
+
// clear annotations from inferred definition
|
|
312
|
+
if (Object.hasOwn(def, singularAnno)) delete def[singularAnno]
|
|
313
|
+
if (Object.hasOwn(def, pluralAnno)) delete def[pluralAnno]
|
|
314
|
+
// transfer annotation from xtended if existing
|
|
315
|
+
if (Object.hasOwn(xtendedDef, singularAnno)) def[singularAnno] = xtendedDef[singularAnno]
|
|
316
|
+
if (Object.hasOwn(xtendedDef, pluralAnno)) def[pluralAnno] = xtendedDef[pluralAnno]
|
|
317
|
+
}
|
|
318
|
+
}
|
|
319
|
+
|
|
295
320
|
/**
|
|
296
321
|
* @param {EntityCSN} entity - the entity
|
|
297
322
|
*/
|
|
@@ -311,6 +336,25 @@ const getProjectionAliases = entity => {
|
|
|
311
336
|
return { aliases, all }
|
|
312
337
|
}
|
|
313
338
|
|
|
339
|
+
/**
|
|
340
|
+
* Heuristic way of looking up a reference type.
|
|
341
|
+
* We currently only support up to two segments,
|
|
342
|
+
* the first referring to the entity, a possible second
|
|
343
|
+
* referring to an element of the entity.
|
|
344
|
+
* @param {CSN} csn - CSN
|
|
345
|
+
* @param {string[]} ref - reference
|
|
346
|
+
* @returns {EntityCSN}
|
|
347
|
+
*/
|
|
348
|
+
function lookUpRefType (csn, ref) {
|
|
349
|
+
if (ref.length > 2) throw new Error(`Unsupported reference type ${ref.join('.')} with ${ref.length} segments. Please report this error.`)
|
|
350
|
+
/** @type {EntityCSN | undefined} */
|
|
351
|
+
let result = csn.definitions[ref[0]] // entity
|
|
352
|
+
if (ref.length === 1) return result
|
|
353
|
+
result = result?.elements?.[ref[1]] // property
|
|
354
|
+
if (!result) throw new Error(`Failed to look up reference type ${ref.join('.')}`)
|
|
355
|
+
return result
|
|
356
|
+
}
|
|
357
|
+
|
|
314
358
|
module.exports = {
|
|
315
359
|
collectDraftEnabledEntities,
|
|
316
360
|
isView,
|
|
@@ -326,5 +370,7 @@ module.exports = {
|
|
|
326
370
|
getProjectionAliases,
|
|
327
371
|
getViewTarget,
|
|
328
372
|
propagateForeignKeys,
|
|
329
|
-
|
|
373
|
+
propagateInflectionAnnotations,
|
|
374
|
+
isCsnAny,
|
|
375
|
+
lookUpRefType
|
|
330
376
|
}
|
package/lib/file.js
CHANGED
|
@@ -257,6 +257,7 @@ class SourceFile extends File {
|
|
|
257
257
|
if (!(name in this.namespaces)) {
|
|
258
258
|
const buffer = new Buffer()
|
|
259
259
|
buffer.closed = false
|
|
260
|
+
buffer.namespace = name
|
|
260
261
|
buffer.add(`export namespace ${name} {`)
|
|
261
262
|
buffer.indent()
|
|
262
263
|
this.namespaces[name] = buffer
|
|
@@ -286,6 +287,8 @@ class SourceFile extends File {
|
|
|
286
287
|
* @param {string} entityFqName - name of the entity the enum is attached to with namespace
|
|
287
288
|
* @param {string} propertyName - property to which the enum is attached.
|
|
288
289
|
* @param {[string, string][]} kvs - list of key-value pairs
|
|
290
|
+
* @param {Buffer} [buffer] - if buffer is of subnamespace the enum will be added there,
|
|
291
|
+
* otherwise to the inline enums of the file
|
|
289
292
|
* @param {string[]} doc - the enum docs
|
|
290
293
|
* If given, the enum is considered to be an inline definition of an enum.
|
|
291
294
|
* If not, it is considered to be regular, named enum.
|
|
@@ -310,16 +313,32 @@ class SourceFile extends File {
|
|
|
310
313
|
* }
|
|
311
314
|
* ```
|
|
312
315
|
*/
|
|
313
|
-
addInlineEnum(entityCleanName, entityFqName, propertyName, kvs, doc=[]) {
|
|
316
|
+
addInlineEnum(entityCleanName, entityFqName, propertyName, kvs, buffer, doc=[]) {
|
|
317
|
+
const namespacedEntity = [buffer?.namespace, entityCleanName].filter(Boolean).join('.')
|
|
314
318
|
this.enums.data.push({
|
|
315
|
-
name: `${
|
|
319
|
+
name: `${namespacedEntity}.${propertyName}`,
|
|
316
320
|
property: propertyName,
|
|
317
321
|
kvs,
|
|
318
|
-
fq: `${
|
|
322
|
+
fq: `${namespacedEntity}.${propertyName}`
|
|
319
323
|
})
|
|
320
|
-
const entityProxy = this.entityProxies[
|
|
324
|
+
const entityProxy = this.entityProxies[namespacedEntity] ?? (this.entityProxies[namespacedEntity] = [])
|
|
321
325
|
entityProxy.push(propertyName)
|
|
322
|
-
|
|
326
|
+
|
|
327
|
+
// REVISIT: find a better way to do this???
|
|
328
|
+
const printEnumToBuffer = (/** @type {Buffer} */buffer) => printEnum(buffer, propertyToInlineEnumName(entityCleanName, propertyName), kvs, {export: false}, doc)
|
|
329
|
+
|
|
330
|
+
if (buffer?.namespace) {
|
|
331
|
+
const tempBuffer = new Buffer()
|
|
332
|
+
// we want to put the enums on class level
|
|
333
|
+
tempBuffer.indent()
|
|
334
|
+
printEnumToBuffer(tempBuffer)
|
|
335
|
+
|
|
336
|
+
// we want to write the enums at the beginning of the namespace
|
|
337
|
+
const [first,...rest] = buffer.parts
|
|
338
|
+
buffer.parts = [first, ...tempBuffer.parts, ...rest]
|
|
339
|
+
} else {
|
|
340
|
+
printEnumToBuffer(this.inlineEnums.buffer)
|
|
341
|
+
}
|
|
323
342
|
}
|
|
324
343
|
|
|
325
344
|
/**
|
|
@@ -405,9 +424,13 @@ class SourceFile extends File {
|
|
|
405
424
|
// currently only needed to extend cds.Service and would trigger unused-variable-errors in strict configs
|
|
406
425
|
buffer.add('import cds from \'@sap/cds\'') // TODO should go to visitor#printService, but can't express this as Path
|
|
407
426
|
}
|
|
427
|
+
const file = configuration.targetModuleType === 'esm'
|
|
428
|
+
? '/index.js'
|
|
429
|
+
: ''
|
|
408
430
|
for (const imp of Object.values(this.imports)) {
|
|
409
431
|
if (!imp.isCwd(this.path.asDirectory())) {
|
|
410
|
-
|
|
432
|
+
const from = imp.asDirectory({relative: this.path.asDirectory()}) + file
|
|
433
|
+
buffer.add(`import * as ${imp.asIdentifier()} from '${from}';`)
|
|
411
434
|
}
|
|
412
435
|
}
|
|
413
436
|
buffer.blankLine()
|
|
@@ -486,12 +509,15 @@ class SourceFile extends File {
|
|
|
486
509
|
|
|
487
510
|
return {
|
|
488
511
|
singularRhs: `createEntityProxy(['${namespace}', '${original}'], { target: { is_singular: true }${customPropsStr} })`,
|
|
489
|
-
pluralRhs: `createEntityProxy(['${namespace}', '${original}'])`,
|
|
512
|
+
pluralRhs: `createEntityProxy(['${namespace}', '${original}'], { target: { is_singular: false }})`,
|
|
490
513
|
}
|
|
491
514
|
} else {
|
|
515
|
+
// standard entity: csn.Books
|
|
516
|
+
// inline entity: csn['Books.texts']
|
|
517
|
+
const csnAccess = original.includes('.') ? `csn['${original}']` : `csn.${original}`
|
|
492
518
|
return {
|
|
493
|
-
singularRhs: `{ is_singular: true, __proto__:
|
|
494
|
-
pluralRhs:
|
|
519
|
+
singularRhs: `{ is_singular: true, __proto__: ${csnAccess} }`,
|
|
520
|
+
pluralRhs: csnAccess
|
|
495
521
|
}
|
|
496
522
|
}
|
|
497
523
|
}
|
|
@@ -585,6 +611,11 @@ class Buffer {
|
|
|
585
611
|
* @type {boolean}
|
|
586
612
|
*/
|
|
587
613
|
this.closed = false
|
|
614
|
+
/**
|
|
615
|
+
* Required for inline enums of inline compositions or text entities
|
|
616
|
+
* @type {string | undefined}
|
|
617
|
+
*/
|
|
618
|
+
this.namespace = undefined
|
|
588
619
|
}
|
|
589
620
|
|
|
590
621
|
/**
|
|
@@ -1,3 +1,11 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Forcefully overrides the .name property of a class.
|
|
3
|
+
* See: https://github.com/microsoft/TypeScript/issues/442
|
|
4
|
+
* @param {string} clazz - the class to override the name property for
|
|
5
|
+
* @param {string} content - the content to set the name property to
|
|
6
|
+
*/
|
|
7
|
+
const overrideNameProperty = (clazz, content) => `Object.defineProperty(${clazz}, 'name', { value: '${content}' })`
|
|
8
|
+
|
|
1
9
|
class JavaScriptPrinter {
|
|
2
10
|
/**
|
|
3
11
|
* @param {string} text - comment text
|
|
@@ -76,12 +84,14 @@ class ESMPrinter extends JavaScriptPrinter {
|
|
|
76
84
|
|
|
77
85
|
/** @type {JavaScriptPrinter['printDeconstructedImport']} */
|
|
78
86
|
printDeconstructedImport (imports, from) {
|
|
79
|
-
return `import { ${imports.join(', ')} } from '${from}'`
|
|
87
|
+
return `import { ${imports.join(', ')} } from '${from}/index.js'`
|
|
80
88
|
}
|
|
81
89
|
|
|
82
90
|
/** @type {JavaScriptPrinter['printExport']} */
|
|
83
91
|
printExport (name, value) {
|
|
84
|
-
return
|
|
92
|
+
return name.includes('.')
|
|
93
|
+
? `${name} = ${value}`
|
|
94
|
+
: `export const ${name} = ${value}`
|
|
85
95
|
}
|
|
86
96
|
|
|
87
97
|
/** @type {JavaScriptPrinter['printDefaultExport']} */
|
|
@@ -127,5 +137,6 @@ class CJSPrinter extends JavaScriptPrinter {
|
|
|
127
137
|
|
|
128
138
|
module.exports = {
|
|
129
139
|
CJSPrinter,
|
|
130
|
-
ESMPrinter
|
|
140
|
+
ESMPrinter,
|
|
141
|
+
overrideNameProperty
|
|
131
142
|
}
|
package/lib/printers/wrappers.js
CHANGED
|
@@ -103,7 +103,7 @@ const createIntersectionOf = (...types) => types.join(' & ')
|
|
|
103
103
|
* createPromiseOf('string') // -> 'Promise<string>'
|
|
104
104
|
* ```
|
|
105
105
|
*/
|
|
106
|
-
const createPromiseOf = t => `Promise<${t}>`
|
|
106
|
+
const createPromiseOf = t => `globalThis.Promise<${t}>`
|
|
107
107
|
|
|
108
108
|
/**
|
|
109
109
|
* Wraps type into a deep require (removes all posibilities of undefined recursively).
|
|
@@ -113,6 +113,14 @@ const createPromiseOf = t => `Promise<${t}>`
|
|
|
113
113
|
*/
|
|
114
114
|
const deepRequire = (t, lookup = '') => `${base}.DeepRequired<${t}>${lookup}`
|
|
115
115
|
|
|
116
|
+
/**
|
|
117
|
+
* Wraps a type into an unkey type (removes they Key<> type).
|
|
118
|
+
* This is needed for type references, where Foo:id should result in `typeof id`, not `Key<typeof id>`.
|
|
119
|
+
* @param {string} t - the type to wrap.
|
|
120
|
+
* @returns {string}
|
|
121
|
+
*/
|
|
122
|
+
const unkey = t => `${base}.Unkey<${t}>`
|
|
123
|
+
|
|
116
124
|
/**
|
|
117
125
|
* Puts a passed string in docstring format.
|
|
118
126
|
* @param {string | undefined} doc - raw string to docify. May contain linebreaks.
|
|
@@ -150,5 +158,6 @@ module.exports = {
|
|
|
150
158
|
createCompositionOfMany,
|
|
151
159
|
deepRequire,
|
|
152
160
|
docify,
|
|
153
|
-
stringIdent
|
|
161
|
+
stringIdent,
|
|
162
|
+
unkey
|
|
154
163
|
}
|
package/lib/resolution/entity.js
CHANGED
|
@@ -61,6 +61,22 @@ class EntityInfo {
|
|
|
61
61
|
/** @type {import('../typedefs').resolver.EntityCSN | undefined} */
|
|
62
62
|
#csn
|
|
63
63
|
|
|
64
|
+
/** @type {Set<string> | undefined} */
|
|
65
|
+
#inheritedElements
|
|
66
|
+
|
|
67
|
+
/** @returns set of inherited elements (e.g. ID of aspect cuid) */
|
|
68
|
+
get inheritedElements() {
|
|
69
|
+
if (this.#inheritedElements) return this.#inheritedElements
|
|
70
|
+
this.#inheritedElements = new Set()
|
|
71
|
+
for (const parentName of this.csn.includes ?? []) {
|
|
72
|
+
const parent = this.#repository.getByFq(parentName)
|
|
73
|
+
for (const element of Object.keys(parent?.csn?.elements ?? {})) {
|
|
74
|
+
this.#inheritedElements.add(element)
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
return this.#inheritedElements
|
|
78
|
+
}
|
|
79
|
+
|
|
64
80
|
/** @returns the **inferred** csn for this entity. */
|
|
65
81
|
get csn () {
|
|
66
82
|
return this.#csn ??= this.#resolver.csn.definitions[this.fullyQualifiedName]
|
|
@@ -189,6 +205,8 @@ function asIdentifier ({info, wrapper = undefined, relative = undefined}) {
|
|
|
189
205
|
: `${info.namespace.asIdentifier()}.${wrapped}`
|
|
190
206
|
}
|
|
191
207
|
|
|
208
|
+
/** @typedef {EntityInfo} Info */
|
|
209
|
+
|
|
192
210
|
module.exports = {
|
|
193
211
|
EntityRepository,
|
|
194
212
|
asIdentifier
|
|
@@ -24,7 +24,7 @@ const { configuration } = require('../config')
|
|
|
24
24
|
/** @typedef {{typeName: string, typeInfo: TypeResolveInfo & { inflection: Inflection }}} ResolveAndRequireInfo */
|
|
25
25
|
|
|
26
26
|
class Resolver {
|
|
27
|
-
get csn() { return this.visitor.csn
|
|
27
|
+
get csn() { return this.visitor.csn }
|
|
28
28
|
|
|
29
29
|
/** @param {Visitor} visitor - the visitor */
|
|
30
30
|
constructor(visitor) {
|
|
@@ -165,11 +165,13 @@ class Resolver {
|
|
|
165
165
|
*/
|
|
166
166
|
const isPropertyOf = (property, entity) => property && Object.hasOwn(entity?.elements ?? {}, property)
|
|
167
167
|
|
|
168
|
-
const defs = this.visitor.csn.
|
|
168
|
+
const defs = this.visitor.csn.definitions
|
|
169
|
+
|
|
170
|
+
// check if name is already an entity, then we do not have a property access, but a nested entity
|
|
171
|
+
if (defs[p]?.kind === 'entity') return []
|
|
172
|
+
|
|
169
173
|
// assume parts to contain [Namespace, Service, Entity1, Entity2, Entity3, property1, property2]
|
|
170
|
-
/** @type {string} */
|
|
171
|
-
// @ts-expect-error - nope, we know there is at least one element
|
|
172
|
-
let qualifier = parts.shift()
|
|
174
|
+
let qualifier = /** @type {string} */ (parts.shift())
|
|
173
175
|
// find first entity from left (Entity1)
|
|
174
176
|
while ((!defs[qualifier] || !isEntity(defs[qualifier])) && parts.length) {
|
|
175
177
|
qualifier += `.${parts.shift()}`
|
|
@@ -240,6 +242,8 @@ class Resolver {
|
|
|
240
242
|
} else {
|
|
241
243
|
// TODO: make sure the resolution still works. Currently, we only cut off the namespace!
|
|
242
244
|
plural = util.getPluralAnnotation(typeInfo.csn) ?? typeInfo.plainName
|
|
245
|
+
// remove leading entity name
|
|
246
|
+
if (plural.includes('.')) plural = last(plural)
|
|
243
247
|
singular = util.getSingularAnnotation(typeInfo.csn) ?? util.singular4(typeInfo.csn, true) // util.singular4(typeInfo.csn, true) // can not use `plural` to honor possible @singular annotation
|
|
244
248
|
|
|
245
249
|
// don't slice off namespace if it isn't part of the inflected name.
|
|
@@ -311,18 +315,6 @@ class Resolver {
|
|
|
311
315
|
} else {
|
|
312
316
|
let { singular, plural } = targetTypeInfo.typeInfo.inflection
|
|
313
317
|
|
|
314
|
-
// FIXME: super hack!!
|
|
315
|
-
// Inflection currently does not retain the scope of the entity.
|
|
316
|
-
// But we can't just fix it in inflection(...), as that would break several other things
|
|
317
|
-
// So we bandaid-fix it back here, as it is the least intrusive place -- but this should get fixed asap!
|
|
318
|
-
if (target.type) {
|
|
319
|
-
const untangled = this.visitor.entityRepository.getByFqOrThrow(target.type)
|
|
320
|
-
const scope = untangled.scope.join('.')
|
|
321
|
-
if (scope && !singular.startsWith(scope)) {
|
|
322
|
-
singular = `${scope}.${singular}`
|
|
323
|
-
}
|
|
324
|
-
}
|
|
325
|
-
|
|
326
318
|
typeName = cardinality > 1
|
|
327
319
|
? toMany(plural)
|
|
328
320
|
: toOne(this.visitor.isSelfReference(target) ? 'this' : singular)
|
|
@@ -370,8 +362,14 @@ class Resolver {
|
|
|
370
362
|
// handle typeof (unless it has already been handled above)
|
|
371
363
|
const target = element.target?.name ?? element.type?.ref?.join('.') ?? element.type
|
|
372
364
|
if (target && !typeInfo.isDeepRequire) {
|
|
373
|
-
const { propertyAccess } = this.visitor.entityRepository.getByFq(target) ?? {}
|
|
374
|
-
if (
|
|
365
|
+
const { propertyAccess, scope } = this.visitor.entityRepository.getByFq(target) ?? {}
|
|
366
|
+
if (scope?.length) {
|
|
367
|
+
// update inflections with proper prefix, e.g. Books.text, Books.texts
|
|
368
|
+
typeInfo.inflection = {
|
|
369
|
+
singular: [...scope, typeInfo.inflection?.singular].join('.'),
|
|
370
|
+
plural: [...scope, typeInfo.inflection?.plural].join('.')
|
|
371
|
+
}
|
|
372
|
+
} else if (propertyAccess?.length) {
|
|
375
373
|
const element = target.slice(0, -propertyAccess.join('.').length - 1)
|
|
376
374
|
const access = this.visitor.inlineDeclarationResolver.getTypeLookup(propertyAccess)
|
|
377
375
|
// singular, as we have to access the property of the entity
|
|
@@ -452,6 +450,7 @@ class Resolver {
|
|
|
452
450
|
|
|
453
451
|
const cardinality = getMaxCardinality(element)
|
|
454
452
|
|
|
453
|
+
/** @type {TypeResolveInfo} */
|
|
455
454
|
const result = {
|
|
456
455
|
isBuiltin: false, // will be rectified in the corresponding handlers, if needed
|
|
457
456
|
isInlineDeclaration: false,
|
|
@@ -569,7 +568,7 @@ class Resolver {
|
|
|
569
568
|
* @returns @see resolveType
|
|
570
569
|
*/
|
|
571
570
|
resolveTypeName(t, into) {
|
|
572
|
-
const result = into ?? {}
|
|
571
|
+
const result = into ?? /** @type {TypeResolveInfo} */({})
|
|
573
572
|
const path = t.split('.')
|
|
574
573
|
const builtin = this.builtinResolver.resolveBuiltin(path)
|
|
575
574
|
if (builtin === undefined) {
|
package/lib/typedefs.d.ts
CHANGED
|
@@ -13,7 +13,7 @@ export module resolver {
|
|
|
13
13
|
compositions?: { target: string }[]
|
|
14
14
|
doc?: string,
|
|
15
15
|
elements?: { [key: string]: EntityCSN }
|
|
16
|
-
key?:
|
|
16
|
+
key?: boolean // custom!!
|
|
17
17
|
keys?: { [key:string]: any }
|
|
18
18
|
kind: string,
|
|
19
19
|
includes?: string[]
|
|
@@ -25,6 +25,8 @@ export module resolver {
|
|
|
25
25
|
target?: string,
|
|
26
26
|
type: string | ref,
|
|
27
27
|
name: string,
|
|
28
|
+
'@singular'?: string,
|
|
29
|
+
'@plural'?: string,
|
|
28
30
|
'@odata.draft.enabled'?: boolean // custom!
|
|
29
31
|
_unresolved?: boolean
|
|
30
32
|
isRefNotNull?: boolean // custom!
|
|
@@ -46,7 +48,8 @@ export module resolver {
|
|
|
46
48
|
|
|
47
49
|
|
|
48
50
|
export type EnumCSN = EntityCSN & {
|
|
49
|
-
enum: {[key:name]: string}
|
|
51
|
+
enum: {[key:name]: string},
|
|
52
|
+
resolvedType?: string // custom property! When .type points to a ref, the visitor will resolve the ref into this property
|
|
50
53
|
}
|
|
51
54
|
|
|
52
55
|
export type CSN = {
|
package/lib/util.js
CHANGED
|
@@ -15,10 +15,10 @@ if (process.version.startsWith('v14')) {
|
|
|
15
15
|
|
|
16
16
|
const last = /\w+$/
|
|
17
17
|
|
|
18
|
-
const annotations = {
|
|
18
|
+
const annotations = /** @type {const} */ ({
|
|
19
19
|
singular: ['@singular'],
|
|
20
20
|
plural: ['@plural'],
|
|
21
|
-
}
|
|
21
|
+
})
|
|
22
22
|
|
|
23
23
|
/**
|
|
24
24
|
* Converts a camelCase string to snake_case.
|
package/lib/visitor.js
CHANGED
|
@@ -1,8 +1,6 @@
|
|
|
1
1
|
'use strict'
|
|
2
2
|
|
|
3
|
-
const
|
|
4
|
-
|
|
5
|
-
const { isView, isUnresolved, propagateForeignKeys, collectDraftEnabledEntities, isDraftEnabled, isType, isProjection, getMaxCardinality, isViewOrProjection, isEnum, isEntity } = require('./csn')
|
|
3
|
+
const { propagateForeignKeys, propagateInflectionAnnotations, collectDraftEnabledEntities, isDraftEnabled, isType, getMaxCardinality, isViewOrProjection, isEnum, isEntity, lookUpRefType } = require('./csn')
|
|
6
4
|
// eslint-disable-next-line no-unused-vars
|
|
7
5
|
const { SourceFile, FileRepository, Buffer, Path } = require('./file')
|
|
8
6
|
const { FlatInlineDeclarationResolver, StructuredInlineDeclarationResolver } = require('./components/inline')
|
|
@@ -18,6 +16,7 @@ const { last } = require('./components/identifier')
|
|
|
18
16
|
const { getPropertyModifiers } = require('./components/property')
|
|
19
17
|
const { configuration } = require('./config')
|
|
20
18
|
const { createMember } = require('./components/class')
|
|
19
|
+
const { overrideNameProperty } = require('./printers/javascript')
|
|
21
20
|
|
|
22
21
|
/** @typedef {import('./file').File} File */
|
|
23
22
|
/** @typedef {import('./typedefs').visitor.Context} Context */
|
|
@@ -27,6 +26,7 @@ const { createMember } = require('./components/class')
|
|
|
27
26
|
/** @typedef {import('./typedefs').resolver.EntityCSN} EntityCSN */
|
|
28
27
|
/** @typedef {import('./typedefs').resolver.EnumCSN} EnumCSN */
|
|
29
28
|
|
|
29
|
+
|
|
30
30
|
class Visitor {
|
|
31
31
|
/**
|
|
32
32
|
* Gathers all files that are supposed to be written to
|
|
@@ -42,11 +42,12 @@ class Visitor {
|
|
|
42
42
|
* @param {{xtended: CSN, inferred: CSN}} csn - root CSN
|
|
43
43
|
*/
|
|
44
44
|
constructor(csn) {
|
|
45
|
-
propagateForeignKeys(csn.xtended)
|
|
46
45
|
propagateForeignKeys(csn.inferred)
|
|
47
|
-
|
|
46
|
+
propagateInflectionAnnotations(csn)
|
|
48
47
|
collectDraftEnabledEntities(csn.inferred)
|
|
49
|
-
|
|
48
|
+
|
|
49
|
+
// xtendend csn not required after this point -> continue with inferred
|
|
50
|
+
this.csn = csn.inferred
|
|
50
51
|
|
|
51
52
|
/** @type {Context[]} **/
|
|
52
53
|
this.contexts = []
|
|
@@ -72,41 +73,8 @@ class Visitor {
|
|
|
72
73
|
* Visits all definitions within the CSN definitions.
|
|
73
74
|
*/
|
|
74
75
|
visitDefinitions() {
|
|
75
|
-
for (const [name, entity] of Object.entries(this.csn.
|
|
76
|
-
|
|
77
|
-
this.visitEntity(name, this.csn.inferred.definitions[name])
|
|
78
|
-
} else if (isProjection(entity) || !isUnresolved(entity)) {
|
|
79
|
-
this.visitEntity(name, entity)
|
|
80
|
-
} else {
|
|
81
|
-
LOG.warn(`Skipping unresolved entity: ${name}`)
|
|
82
|
-
}
|
|
83
|
-
}
|
|
84
|
-
// FIXME: optimise
|
|
85
|
-
// We are currently working with two flavours of CSN:
|
|
86
|
-
// xtended, as it is as close as possible to an OOP class hierarchy
|
|
87
|
-
// inferred, as it contains information missing in xtended
|
|
88
|
-
// This is less than optimal and has to be revisited at some point!
|
|
89
|
-
const handledKeys = new Set(Object.keys(this.csn.xtended.definitions))
|
|
90
|
-
// we are looking for autoexposed entities in services
|
|
91
|
-
const missing = Object.entries(this.csn.inferred.definitions).filter(([key]) => !key.endsWith('.texts') &&!handledKeys.has(key))
|
|
92
|
-
for (const [name, entity] of missing) {
|
|
93
|
-
// instead of using the definition from inferred CSN, we refer to the projected entity from xtended CSN instead.
|
|
94
|
-
// The latter contains the CSN fixes (propagated foreign keys, etc) and none of the localised fields we don't handle yet.
|
|
95
|
-
if (entity.projection) {
|
|
96
|
-
const targetName = entity.projection.from.ref[0]
|
|
97
|
-
// FIXME: references to types of entity properties may be missing from xtendend flavour (see #103)
|
|
98
|
-
// this should be revisted once we settle on a single flavour.
|
|
99
|
-
const target = this.csn.xtended.definitions[targetName] ?? this.csn.inferred.definitions[targetName]
|
|
100
|
-
if (target.kind !== 'type') {
|
|
101
|
-
// skip if the target is a property, like in:
|
|
102
|
-
// books: Association to many Author.books ...
|
|
103
|
-
// as this would result in a type definition that
|
|
104
|
-
// name-clashes with the actual declaration of Author
|
|
105
|
-
this.visitEntity(name, target)
|
|
106
|
-
}
|
|
107
|
-
} else {
|
|
108
|
-
LOG.error(`Expecting an autoexposed projection within a service. Skipping ${name}`)
|
|
109
|
-
}
|
|
76
|
+
for (const [name, entity] of Object.entries(this.csn.definitions)) {
|
|
77
|
+
this.visitEntity(name, entity)
|
|
110
78
|
}
|
|
111
79
|
}
|
|
112
80
|
|
|
@@ -117,15 +85,8 @@ class Visitor {
|
|
|
117
85
|
* @returns {[string, object][]} array of key name and key element pairs
|
|
118
86
|
*/
|
|
119
87
|
#keys(fq) {
|
|
120
|
-
// FIXME: this is actually pretty bad, as not only have to propagate keys through
|
|
121
|
-
// both flavours of CSN (see constructor), but we are now also collecting them from
|
|
122
|
-
// both flavours and deduplicating them.
|
|
123
|
-
// xtended contains keys that have been inherited from parents
|
|
124
|
-
// inferred contains keys from queried entities (thing `entity Foo as select from Bar`, where Bar has keys)
|
|
125
|
-
// So we currently need them both.
|
|
126
88
|
return Object.entries({
|
|
127
|
-
...this.csn.
|
|
128
|
-
...this.csn.xtended.definitions[fq]?.keys ?? {}
|
|
89
|
+
...this.csn.definitions[fq]?.keys ?? {}
|
|
129
90
|
})
|
|
130
91
|
}
|
|
131
92
|
|
|
@@ -231,7 +192,7 @@ class Visitor {
|
|
|
231
192
|
// FIXME: replace with resolution/entity::asIdentifier
|
|
232
193
|
const toLocalIdent = ({ns, clean, fq}) => {
|
|
233
194
|
// types are not inflected, so don't change those to singular
|
|
234
|
-
const csn = this.csn.
|
|
195
|
+
const csn = this.csn.definitions[fq]
|
|
235
196
|
const ident = isType(csn)
|
|
236
197
|
? clean
|
|
237
198
|
: this.resolver.inflect({csn, plainName: clean}).singular
|
|
@@ -261,6 +222,7 @@ class Visitor {
|
|
|
261
222
|
.reverse() // reverse so that own aspect A is applied before extensions B,C: B(C(A(Entity)))
|
|
262
223
|
.reduce((wrapped, ancestor) => `${asIdentifier({info: ancestor, wrapper: name => `_${name}Aspect`, relative: file.path})}(${wrapped})`, 'Base')
|
|
263
224
|
|
|
225
|
+
const inheritedElements = !isViewOrProjection(entity) ? info.inheritedElements : null
|
|
264
226
|
this.contexts.push({ entity: fq })
|
|
265
227
|
|
|
266
228
|
// CLASS ASPECT
|
|
@@ -272,10 +234,7 @@ class Visitor {
|
|
|
272
234
|
const resolverOptions = { forceInlineStructs: isEntity(entity) && configuration.inlineDeclarations === 'flat'}
|
|
273
235
|
|
|
274
236
|
for (let [ename, element] of Object.entries(entity.elements ?? [])) {
|
|
275
|
-
if (
|
|
276
|
-
LOG.warn(`referring to .texts property in ${fq}. This is currently not supported and will be ignored.`)
|
|
277
|
-
continue
|
|
278
|
-
}
|
|
237
|
+
if (inheritedElements?.has(ename)) continue
|
|
279
238
|
this.visitElement({name: ename, element, file, buffer, resolverOptions})
|
|
280
239
|
|
|
281
240
|
// make foreign keys explicit
|
|
@@ -290,7 +249,8 @@ class Visitor {
|
|
|
290
249
|
LOG.error(`Attempting to generate a foreign key reference called '${foreignKey}' in type definition for entity ${fq}. But a property of that name is already defined explicitly. Consider renaming that property.`)
|
|
291
250
|
} else {
|
|
292
251
|
const kelement = Object.assign(Object.create(originalKeyElement), {
|
|
293
|
-
isRefNotNull: !!element.notNull || !!element.key
|
|
252
|
+
isRefNotNull: !!element.notNull || !!element.key,
|
|
253
|
+
key: element.key
|
|
294
254
|
})
|
|
295
255
|
this.visitElement({name: foreignKey, element: kelement, file, buffer, resolverOptions})
|
|
296
256
|
}
|
|
@@ -299,7 +259,7 @@ class Visitor {
|
|
|
299
259
|
}
|
|
300
260
|
|
|
301
261
|
// store inline enums for later handling, as they have to go into one common "static elements" wrapper
|
|
302
|
-
if (isInlineEnumType(element, this.csn
|
|
262
|
+
if (isInlineEnumType(element, this.csn)) {
|
|
303
263
|
enums.push(element)
|
|
304
264
|
}
|
|
305
265
|
}
|
|
@@ -312,7 +272,10 @@ class Visitor {
|
|
|
312
272
|
initialiser: propertyToInlineEnumName(clean, e.name),
|
|
313
273
|
isStatic: true,
|
|
314
274
|
}))
|
|
315
|
-
|
|
275
|
+
if (typeof e?.type !== 'string' && e?.type?.ref) {
|
|
276
|
+
e.resolvedType = /** @type {string} */(lookUpRefType(this.csn, e.type.ref)?.type)
|
|
277
|
+
}
|
|
278
|
+
file.addInlineEnum(clean, fq, e.name, csnToEnumPairs(e, {unwrapVals: true}), buffer, eDoc)
|
|
316
279
|
}
|
|
317
280
|
|
|
318
281
|
if ('kind' in entity) {
|
|
@@ -354,13 +317,8 @@ class Visitor {
|
|
|
354
317
|
* @param {EntityCSN} entity - the entity to print
|
|
355
318
|
*/
|
|
356
319
|
#printEntity(fq, entity) {
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
* @param {string} clazz - the class to override the name property for
|
|
360
|
-
* @param {string} content - the content to set the name property to
|
|
361
|
-
*/
|
|
362
|
-
const overrideNameProperty = (clazz, content) => `Object.defineProperty(${clazz}, 'name', { value: '${content}' })`
|
|
363
|
-
const { namespace: ns, entityName: clean, inflection } = this.entityRepository.getByFqOrThrow(fq)
|
|
320
|
+
const info = this.entityRepository.getByFqOrThrow(fq)
|
|
321
|
+
const { namespace: ns, entityName: clean, inflection, scope } = info
|
|
364
322
|
const file = this.fileRepository.getNamespaceFile(ns)
|
|
365
323
|
let { singular, plural } = inflection
|
|
366
324
|
|
|
@@ -376,7 +334,7 @@ class Visitor {
|
|
|
376
334
|
// as types are not inflected, their singular will always clash and there is also no plural for them anyway -> skip
|
|
377
335
|
// if the user defined their entities in singular form we would also have a false positive here -> skip
|
|
378
336
|
const namespacedSingular = `${ns.asNamespace()}.${singular}`
|
|
379
|
-
if (!isType(entity) && namespacedSingular !== fq && namespacedSingular in this.csn.
|
|
337
|
+
if (!isType(entity) && namespacedSingular !== fq && namespacedSingular in this.csn.definitions) {
|
|
380
338
|
LOG.error(
|
|
381
339
|
`Derived singular '${singular}' for your entity '${fq}', already exists. The resulting types will be erronous. Consider using '@singular:'/ '@plural:' annotations in your model or move the offending declarations into different namespaces to resolve this collision.`
|
|
382
340
|
)
|
|
@@ -389,43 +347,48 @@ class Visitor {
|
|
|
389
347
|
? file.getSubNamespace(this.resolver.trimNamespace(parent.name))
|
|
390
348
|
: file.classes
|
|
391
349
|
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
// in case of projections `entity` is empty -> retrieve from inferred csn where the actual properties are rolled out
|
|
401
|
-
const target = isProjection(entity) || isView(entity)
|
|
402
|
-
? this.csn.inferred.definitions[fq]
|
|
403
|
-
: entity
|
|
350
|
+
if (scope?.length > 0) {
|
|
351
|
+
/** @param {string} n - name of entity */
|
|
352
|
+
const scoped = n => [...scope, n].join('.')
|
|
353
|
+
file.addInflection(scoped(singular), scoped(plural), scoped(clean))
|
|
354
|
+
} else {
|
|
355
|
+
file.addInflection(singular, plural, clean)
|
|
356
|
+
}
|
|
404
357
|
|
|
405
|
-
this.#aspectify(fq,
|
|
358
|
+
this.#aspectify(fq, entity, buffer, { cleanName: singular })
|
|
406
359
|
|
|
407
360
|
buffer.add(overrideNameProperty(singular, entity.name))
|
|
408
361
|
buffer.add(`Object.defineProperty(${singular}, 'is_singular', { value: true })`)
|
|
409
362
|
|
|
410
363
|
// PLURAL
|
|
411
|
-
|
|
412
364
|
// types do not receive a plural
|
|
413
365
|
if (!isType(entity)) {
|
|
414
|
-
|
|
415
|
-
// Foo.text -> namespace Foo { class text { ... }}
|
|
416
|
-
plural = last(plural)
|
|
417
|
-
}
|
|
418
|
-
// plural can not be a type alias to $singular[] but needs to be a proper class instead,
|
|
419
|
-
// so it can get passed as value to CQL functions.
|
|
420
|
-
const additionalProperties = this.#staticClassContents(fq, singular, true)
|
|
421
|
-
additionalProperties.push('$count?: number')
|
|
422
|
-
buffer.add(docify(entity.doc))
|
|
423
|
-
buffer.add(`export class ${plural} extends Array<${singular}> {${additionalProperties.join('\n')}}`)
|
|
424
|
-
buffer.add(overrideNameProperty(plural, entity.name))
|
|
366
|
+
this.#printPlural(info, buffer)
|
|
425
367
|
}
|
|
426
368
|
buffer.blankLine()
|
|
427
369
|
}
|
|
428
370
|
|
|
371
|
+
/**
|
|
372
|
+
* @param {import('./resolution/entity').Info} info - the entity info
|
|
373
|
+
* @param {import('./file').Buffer} buffer - buffer to write the plural into
|
|
374
|
+
*/
|
|
375
|
+
#printPlural(info, buffer) {
|
|
376
|
+
const { fullyQualifiedName: fq, csn: entity } = info
|
|
377
|
+
let { singular, plural } = info.inflection
|
|
378
|
+
|
|
379
|
+
if (plural.includes('.')) {
|
|
380
|
+
// Foo.text -> namespace Foo { class text { ... }}
|
|
381
|
+
plural = last(plural)
|
|
382
|
+
}
|
|
383
|
+
// plural can not be a type alias to $singular[] but needs to be a proper class instead,
|
|
384
|
+
// so it can get passed as value to CQL functions.
|
|
385
|
+
const additionalProperties = this.#staticClassContents(fq, singular, true)
|
|
386
|
+
additionalProperties.push('$count?: number')
|
|
387
|
+
buffer.add(docify(entity.doc))
|
|
388
|
+
buffer.add(`export class ${plural} extends Array<${singular}> {${additionalProperties.join('\n')}}`)
|
|
389
|
+
buffer.add(overrideNameProperty(plural, entity.name))
|
|
390
|
+
}
|
|
391
|
+
|
|
429
392
|
/**
|
|
430
393
|
* Stringifies function parameters in preparation of passing them to {@link SourceFile.stringifyLambda}.
|
|
431
394
|
* Resolves all parameters to a pair of parameter name and name of the resolved type.
|
|
@@ -509,7 +472,7 @@ class Visitor {
|
|
|
509
472
|
if (isEnum(type) && !isReferenceType(type) && this.resolver.builtinResolver.resolveBuiltin(type.type)) {
|
|
510
473
|
file.addEnum(fq, entityName, csnToEnumPairs(type), docify(type.doc))
|
|
511
474
|
} else {
|
|
512
|
-
const isEnumReference = typeof type.type === 'string' && isEnum(this.csn.
|
|
475
|
+
const isEnumReference = typeof type.type === 'string' && isEnum(this.csn.definitions[type?.type])
|
|
513
476
|
// alias
|
|
514
477
|
file.addType(fq, entityName, this.resolver.resolveAndRequire(type, file).typeName, isEnumReference)
|
|
515
478
|
}
|
|
@@ -522,7 +485,8 @@ class Visitor {
|
|
|
522
485
|
*/
|
|
523
486
|
#printAspect(fq, aspect) {
|
|
524
487
|
LOG.debug(`Printing aspect ${fq}`)
|
|
525
|
-
const
|
|
488
|
+
const info = this.entityRepository.getByFqOrThrow(fq)
|
|
489
|
+
const { namespace, entityName, inflection } = info
|
|
526
490
|
const file = this.fileRepository.getNamespaceFile(namespace)
|
|
527
491
|
// aspects are technically classes and can therefore be added to the list of defined classes.
|
|
528
492
|
// Still, when using them as mixins for a class, they need to already be defined.
|
|
@@ -530,6 +494,9 @@ class Visitor {
|
|
|
530
494
|
file.addClass(entityName, fq)
|
|
531
495
|
file.aspects.add(`// the following represents the CDS aspect '${entityName}'`)
|
|
532
496
|
this.#aspectify(fq, aspect, file.aspects, { cleanName: inflection.singular })
|
|
497
|
+
if (!isType(aspect)) {
|
|
498
|
+
this.#printPlural(info, file.aspects)
|
|
499
|
+
}
|
|
533
500
|
}
|
|
534
501
|
|
|
535
502
|
/**
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@cap-js/cds-typer",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.32.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",
|
|
@@ -42,7 +42,7 @@
|
|
|
42
42
|
"cds-typer": "./lib/cli.js"
|
|
43
43
|
},
|
|
44
44
|
"peerDependencies": {
|
|
45
|
-
"@cap-js/cds-types": ">=0.
|
|
45
|
+
"@cap-js/cds-types": ">=0.9",
|
|
46
46
|
"@sap/cds": ">=8"
|
|
47
47
|
},
|
|
48
48
|
"devDependencies": {
|